diff --git a/package.json b/package.json index 63c02c0..21d8ed4 100644 --- a/package.json +++ b/package.json @@ -42,6 +42,7 @@ "@sentry/minimal": "^6.19.7", "@sentry/node": "^7.57.0", "ajv": "^8.12.0", + "axios": "^1.7.9", "class-transformer": "^0.5.1", "class-validator": "^0.14.0", "crypto-js": "^4.1.1", diff --git a/src/api/api.controller.ts b/src/api/api.controller.ts index a5ca575..ea515e2 100644 --- a/src/api/api.controller.ts +++ b/src/api/api.controller.ts @@ -14,6 +14,8 @@ import { ValidationPipe, All, Req, + HttpException, + HttpStatus, } from '@nestjs/common'; import { SignupResponse, @@ -43,6 +45,8 @@ import { GupshupWhatsappService } from './sms/gupshupWhatsapp/gupshupWhatsapp.se import { TelemetryService } from 'src/telemetry/telemetry.service'; // eslint-disable-next-line @typescript-eslint/no-var-requires const CryptoJS = require('crypto-js'); +import { InjectRedis } from '@nestjs-modules/ioredis'; +import Redis from 'ioredis'; CryptoJS.lib.WordArray.words; @@ -56,7 +60,8 @@ export class ApiController { private readonly apiService: ApiService, private readonly configResolverService: ConfigResolverService, private readonly gupshupWhatsappService: GupshupWhatsappService, - private readonly telemetryService: TelemetryService + private readonly telemetryService: TelemetryService, + @InjectRedis() private readonly redis: Redis ) {} @Get() @@ -438,6 +443,122 @@ export class ApiController { return await this.apiService.logout(body.token); } + @Get('user/search') + async searchUserFA( + @Headers('Authorization') authorization: string, + @Headers('X-FusionAuth-Application-Id') appId: string, + @Query() query: any, + @Param() params: any, + ) { + if (!authorization || !appId) { + throw new HttpException( + 'Authorization and X-FusionAuth-Application-Id headers are required', + HttpStatus.BAD_REQUEST, + ); + } + + const fusionAuthBaseUrl = this.configService.get('FUSIONAUTH_BASE_URL'); + const url = new URL(`${fusionAuthBaseUrl}/api/user/search`); + // Add query params to URL + if (query) { + Object.keys(query).forEach(key => { + url.searchParams.append(key, query[key]); + }); + } + + // Add params to URL + if (params) { + Object.keys(params).forEach(key => { + url.searchParams.append(key, params[key]); + }); + } + + const cacheKey = `search_${url}`; + const cachedData = await this.redis.get(cacheKey); + if (cachedData) { + return JSON.parse(cachedData); + } + + const searchData = await this.searchUserData(url, authorization, appId); + + await this.redis.set(cacheKey, JSON.stringify(searchData)); + return searchData; + } + + @Get('user/:id') + async getUserById( + @Param('id') id: string, + @Headers('Authorization') authorization: string, + @Headers('X-FusionAuth-Application-Id') appId: string, + ) { + if (!authorization || !appId) { + throw new HttpException( + 'Authorization and X-FusionAuth-Application-Id headers are required', + HttpStatus.BAD_REQUEST, + ); + } + + const cacheKey = `user_${id}`; + const cachedData = await this.redis.get(cacheKey); + if (cachedData) { + return JSON.parse(cachedData); + } + + const userData = await this.fetchUserDataFromService(id, authorization, appId); + + await this.redis.set(cacheKey, JSON.stringify(userData)); + return userData; + } + + private async fetchUserDataFromService(id: string, authorization: string, appId: string) { + try { + const fusionAuthBaseUrl = this.configService.get('FUSIONAUTH_BASE_URL'); + const url = new URL(`${fusionAuthBaseUrl}/api/user/${id}`); + const response = await fetch( + url, + { + method: "GET", + headers: { + Authorization: authorization, + 'Content-Type': 'application/json', + 'X-FusionAuth-Application-Id': appId, + }, + }, + ); + return response.json(); + } catch (error) { + throw new HttpException( + `Failed to fetch user data: ${error.message}`, + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + + private async searchUserData( + url: URL, + authorization: string, + appId: string, + ) { + try { + const response = await fetch( + url, + { + headers: { + Authorization: authorization, + 'Content-Type': 'application/json', + 'X-FusionAuth-Application-Id': appId, + } + }, + ); + return response.json(); + } catch (error) { + throw new HttpException( + `Failed to search user data: ${error.message}`, + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + @All('*') async defaultRoute( @Req() request: Request, diff --git a/src/api/sms/gupshupWhatsapp/gupshupWhatsapp.service.ts b/src/api/sms/gupshupWhatsapp/gupshupWhatsapp.service.ts index 0c15830..b92563b 100644 --- a/src/api/sms/gupshupWhatsapp/gupshupWhatsapp.service.ts +++ b/src/api/sms/gupshupWhatsapp/gupshupWhatsapp.service.ts @@ -2,7 +2,7 @@ import { SMSData, SMSProvider, SMSResponse, SMSResponseStatus } from "../sms.int import { InjectRedis } from '@nestjs-modules/ioredis'; import Redis from 'ioredis'; import { Injectable } from "@nestjs/common"; - +import axios from 'axios'; @Injectable() export class GupshupWhatsappService { @@ -41,8 +41,7 @@ export class GupshupWhatsappService { let optinURL = process.env.GUPSHUP_WHATSAPP_BASEURL + '?' + optInParams.toString(); - await fetch(optinURL, { - method: 'GET', + await axios.get(optinURL, { headers: { 'Content-Type': 'application/json' } @@ -65,8 +64,7 @@ export class GupshupWhatsappService { let sendOtpURL = process.env.GUPSHUP_WHATSAPP_BASEURL + '?' + sendOtpParams.toString(); - const response = await fetch(sendOtpURL, { - method: 'GET', + const response = await axios.get(sendOtpURL, { headers: { 'Content-Type': 'application/json' } @@ -76,7 +74,7 @@ export class GupshupWhatsappService { // Store OTP in Redis with 30 minute expiry await this.redis.set(`whatsapp_otp:${smsData.phone}`, otp.toString(), 'EX', 1800); - status.providerSuccessResponse = await response.text(); + status.providerSuccessResponse = JSON.stringify(response.data); status.status = SMSResponseStatus.success; status.messageID = otp.toString(); } @@ -107,7 +105,6 @@ export class GupshupWhatsappService { try { // Get stored OTP from Redis const storedOTP = await this.redis.get(`whatsapp_otp:${phone}`); - console.log("storedOTP",storedOTP) if (!storedOTP) { status.error = {