From f2795d1d86425dcafeeb44dd2f235ffed3a8de5c Mon Sep 17 00:00:00 2001 From: Asin-Junior-Honore Date: Thu, 8 Aug 2024 11:02:37 +0100 Subject: [PATCH 1/7] feat: added logic to update a user testimonial --- package-lock.json | 10 ++- .../dto/update-testimonial.dto.ts | 14 ++++ .../dto/update-testimonial.response.dto.ts | 6 ++ .../testimonials/testimonials.controller.ts | 26 +++++- .../testimonials/testimonials.service.ts | 20 +++++ .../testimonials/tests/update.service.spec.ts | 84 +++++++++++++++++++ 6 files changed, 157 insertions(+), 3 deletions(-) create mode 100644 src/modules/testimonials/dto/update-testimonial.dto.ts create mode 100644 src/modules/testimonials/dto/update-testimonial.response.dto.ts create mode 100644 src/modules/testimonials/tests/update.service.spec.ts diff --git a/package-lock.json b/package-lock.json index 12718e2c4..82dfa542f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,6 +26,7 @@ "bcryptjs": "^2.4.3", "class-transformer": "^0.5.1", "class-validator": "^0.14.1", + "csv-writer": "^1.6.0", "date-fns": "^3.6.0", "google-auth-library": "^9.13.0", "handlebars": "^4.7.8", @@ -3980,9 +3981,8 @@ "version": "1.7.3", "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.3.tgz", "integrity": "sha512-Ar7ND9pU99eJ9GpoGQKhKf58GpUOgnzuaB7ueNQ5BMi0p+LZ5oaEnfF999fAArcTIBwXTCHAmGcHOZJaWPq9Nw==", - "peer": true, - "version": "0.27.2", "license": "MIT", + "peer": true, "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.0", @@ -5319,6 +5319,12 @@ "url": "https://github.com/sponsors/fb55" } }, + "node_modules/csv-writer": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/csv-writer/-/csv-writer-1.6.0.tgz", + "integrity": "sha512-NOx7YDFWEsM/fTRAJjRpPp8t+MKRVvniAg9wQlUKx20MFrPs73WLJhFf5iteqrxNYnsy924K3Iroh3yNHeYd2g==", + "license": "MIT" + }, "node_modules/dargs": { "version": "8.1.0", "dev": true, diff --git a/src/modules/testimonials/dto/update-testimonial.dto.ts b/src/modules/testimonials/dto/update-testimonial.dto.ts new file mode 100644 index 000000000..7e19ec19e --- /dev/null +++ b/src/modules/testimonials/dto/update-testimonial.dto.ts @@ -0,0 +1,14 @@ +import { IsString, IsOptional } from 'class-validator'; +import { ApiPropertyOptional } from '@nestjs/swagger'; + +export class UpdateTestimonialDto { + @ApiPropertyOptional({ description: 'Updated content of the testimonial' }) + @IsString() + @IsOptional() + content?: string; + + @ApiPropertyOptional({ description: 'Updated name associated with the testimonial' }) + @IsString() + @IsOptional() + name?: string; +} diff --git a/src/modules/testimonials/dto/update-testimonial.response.dto.ts b/src/modules/testimonials/dto/update-testimonial.response.dto.ts new file mode 100644 index 000000000..485584491 --- /dev/null +++ b/src/modules/testimonials/dto/update-testimonial.response.dto.ts @@ -0,0 +1,6 @@ +export class UpdateTestimonialResponseDto { + status: string; + message: string; + data: any; + } + \ No newline at end of file diff --git a/src/modules/testimonials/testimonials.controller.ts b/src/modules/testimonials/testimonials.controller.ts index fc0cbcac2..b3726a949 100644 --- a/src/modules/testimonials/testimonials.controller.ts +++ b/src/modules/testimonials/testimonials.controller.ts @@ -1,4 +1,4 @@ -import { Body, Controller, Post, Req, Get, Param, DefaultValuePipe, Query, ParseIntPipe } from '@nestjs/common'; +import { Body, Controller, Post, Req, Get, Param, DefaultValuePipe, Query, ParseIntPipe, Patch } from '@nestjs/common'; import { ApiBearerAuth, ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'; import { UserPayload } from '../user/interfaces/user-payload.interface'; import UserService from '../user/user.service'; @@ -10,6 +10,8 @@ import { GetTestimonials400ErrorResponseDto, GetTestimonials404ErrorResponseDto, } from './dto/get-testimonials.dto'; +import { UpdateTestimonialDto } from './dto/update-testimonial.dto'; +import { UpdateTestimonialResponseDto } from './dto/update-testimonial.response.dto'; @ApiBearerAuth() @ApiTags('Testimonials') @@ -68,4 +70,26 @@ export class TestimonialsController { ) { return this.testimonialsService.getAllTestimonials(userId, page, page_size); } + + @Patch(':id') +@ApiOperation({ summary: 'Update Testimonial' }) +@ApiResponse({ status: 200, description: 'Testimonial updated successfully' }) +@ApiResponse({ status: 404, description: 'Testimonial not found' }) +@ApiResponse({ status: 401, description: 'Unauthorized' }) +@ApiResponse({ status: 500, description: 'Internal Server Error' }) +async update( + @Param('id') id: string, + @Body() updateTestimonialDto: UpdateTestimonialDto, + @Req() req: { user: UserPayload } +): Promise { + const userId = req.user.id; + + const data = await this.testimonialsService.updateTestimonial(id, updateTestimonialDto, userId); + + return { + status: 'success', + message: 'Testimonial updated successfully', + data, + } } +} \ No newline at end of file diff --git a/src/modules/testimonials/testimonials.service.ts b/src/modules/testimonials/testimonials.service.ts index 8883fe39c..d6131c6d1 100644 --- a/src/modules/testimonials/testimonials.service.ts +++ b/src/modules/testimonials/testimonials.service.ts @@ -13,6 +13,7 @@ import * as SYS_MSG from '../../helpers/SystemMessages'; import { CustomHttpException } from '../../helpers/custom-http-filter'; import UserService from '../user/user.service'; import { TestimonialMapper } from './mappers/testimonial.mapper'; +import { UpdateTestimonialDto } from './dto/update-testimonial.dto'; @Injectable() export class TestimonialsService { @@ -91,4 +92,23 @@ export class TestimonialsService { }, }; } + + async updateTestimonial(id: string, updateTestimonialDto: UpdateTestimonialDto, userId: string) { + const testimonial = await this.testimonialRepository.findOne({ where: { id, user: { id: userId } } }); + + if (!testimonial) { + throw new CustomHttpException('Testimonial not found', HttpStatus.NOT_FOUND); + } + + Object.assign(testimonial, updateTestimonialDto); + await this.testimonialRepository.save(testimonial); + + return { + id: testimonial.id, + user_id: userId, + content: testimonial.content, + name: testimonial.name, + updated_at: new Date(), + }; + } } diff --git a/src/modules/testimonials/tests/update.service.spec.ts b/src/modules/testimonials/tests/update.service.spec.ts new file mode 100644 index 000000000..82186adcd --- /dev/null +++ b/src/modules/testimonials/tests/update.service.spec.ts @@ -0,0 +1,84 @@ +import { InternalServerErrorException, NotFoundException, HttpStatus } from '@nestjs/common'; +import { Test, TestingModule } from '@nestjs/testing'; +import { getRepositoryToken } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; +import { Profile } from '../../profile/entities/profile.entity'; +import { User } from '../../user/entities/user.entity'; +import UserService from '../../user/user.service'; +import { UpdateTestimonialDto } from '../dto/update-testimonial.dto'; +import { Testimonial } from '../entities/testimonials.entity'; +import { TestimonialsService } from '../testimonials.service'; + +describe('TestimonialsService', () => { + let service: TestimonialsService; + let userService: UserService; + let testimonialRepository: Repository; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [ + TestimonialsService, + UserService, + { + provide: getRepositoryToken(Testimonial), + useClass: Repository, + }, + { + provide: getRepositoryToken(User), + useClass: Repository, + }, + { + provide: getRepositoryToken(Profile), + useClass: Repository, + }, + ], + }).compile(); + + service = module.get(TestimonialsService); + userService = module.get(UserService); + testimonialRepository = module.get>(getRepositoryToken(Testimonial)); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); + + describe('updateTestimonial', () => { + it('should successfully update a testimonial', async () => { + const id = 'testimonial_id'; + const updateTestimonialDto: UpdateTestimonialDto = { + name: 'Updated Name', + content: 'Updated content!', + }; + const userId = 'user_id'; + const testimonial = { id, user: { id: userId }, ...updateTestimonialDto } as Testimonial; + + jest.spyOn(testimonialRepository, 'findOne').mockResolvedValue(testimonial); + jest.spyOn(testimonialRepository, 'save').mockResolvedValue(testimonial); + + const result = await service.updateTestimonial(id, updateTestimonialDto, userId); + + expect(result).toEqual({ + id, + user_id: userId, + ...updateTestimonialDto, + updated_at: expect.any(Date), + }); + }); + + it('should throw a NotFoundException if testimonial is not found', async () => { + const id = 'testimonial_id'; + const updateTestimonialDto: UpdateTestimonialDto = { + name: 'Updated Name', + content: 'Updated content!', + }; + const userId = 'user_id'; + + jest.spyOn(testimonialRepository, 'findOne').mockResolvedValue(null); + + await expect(service.updateTestimonial(id, updateTestimonialDto, userId)).rejects.toThrow( + new NotFoundException('Testimonial not found') + ); + }); + }); +}); From 7eb02c16ada8d8770a0053d38875ec98dfc41104 Mon Sep 17 00:00:00 2001 From: Asin-Junior-Honore Date: Thu, 8 Aug 2024 12:24:16 +0100 Subject: [PATCH 2/7] fix: added check before deleting or updating an help topic --- .../help-center/help-center.service.ts | 32 +++++++++++++++++-- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/src/modules/help-center/help-center.service.ts b/src/modules/help-center/help-center.service.ts index 0e5a6a710..e89c2deaa 100644 --- a/src/modules/help-center/help-center.service.ts +++ b/src/modules/help-center/help-center.service.ts @@ -1,4 +1,4 @@ -import { HttpStatus, Injectable, NotFoundException } from '@nestjs/common'; +import { HttpException, HttpStatus, Injectable, NotFoundException } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { HelpCenterEntity } from '../help-center/entities/help-center.entity'; @@ -88,16 +88,42 @@ export class HelpCenterService { } async updateTopic(id: string, updateHelpCenterDto: UpdateHelpCenterDto) { + const existingTopic = await this.helpCenterRepository.findOneBy({ id }); + if (!existingTopic) { + throw new HttpException( + { + status: 'error', + message: 'Topic not found, please check and try again', + status_code: HttpStatus.NOT_FOUND, + }, + HttpStatus.NOT_FOUND + ); + } + await this.helpCenterRepository.update(id, updateHelpCenterDto); - const query = await this.helpCenterRepository.findOneBy({ id }); + const updatedTopic = await this.helpCenterRepository.findOneBy({ id }); + return { status_code: HttpStatus.OK, message: REQUEST_SUCCESSFUL, - data: query, + data: updatedTopic, }; } + async removeTopic(id: string): Promise { + const existingTopic = await this.helpCenterRepository.findOneBy({ id }); + if (!existingTopic) { + throw new HttpException( + { + status: 'error', + message: 'Topic not found, unable to delete', + status_code: HttpStatus.NOT_FOUND, + }, + HttpStatus.NOT_FOUND + ); + } await this.helpCenterRepository.delete(id); } + } From eeb4fc0f3255b0de93be838e4d924228e55ff8d3 Mon Sep 17 00:00:00 2001 From: Asin-Junior-Honore Date: Thu, 8 Aug 2024 23:35:08 +0100 Subject: [PATCH 3/7] feat: added logic to update user testimonial --- package-lock.json | 50 +++++++------------ .../testimonials/testimonials.controller.ts | 23 +++++++++ .../testimonials/testimonials.service.ts | 21 ++++++++ 3 files changed, 61 insertions(+), 33 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6c4d7f5ac..3dfbee9b5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1124,37 +1124,6 @@ "@css-inline/css-inline-win32-x64-msvc": "0.14.1" } }, - "node_modules/@css-inline/css-inline-linux-x64-gnu": { - "version": "0.14.1", - "resolved": "https://registry.npmjs.org/@css-inline/css-inline-linux-x64-gnu/-/css-inline-linux-x64-gnu-0.14.1.tgz", - "integrity": "sha512-yubbEye+daDY/4vXnyASAxH88s256pPati1DfVoZpU1V0+KP0BZ1dByZOU1ktExurbPH3gZOWisAnBE9xon0Uw==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@css-inline/css-inline/node_modules/@css-inline/css-inline-android-arm-eabi": { - "version": "0.14.1", - "resolved": "https://registry.npmjs.org/@css-inline/css-inline-android-arm-eabi/-/css-inline-android-arm-eabi-0.14.1.tgz", - "integrity": "sha512-LNUR8TY4ldfYi0mi/d4UNuHJ+3o8yLQH9r2Nt6i4qeg1i7xswfL3n/LDLRXvGjBYqeEYNlhlBQzbPwMX1qrU6A==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 10" - } - }, "node_modules/@css-inline/css-inline-android-arm64": { "version": "0.14.1", "resolved": "https://registry.npmjs.org/@css-inline/css-inline-android-arm64/-/css-inline-android-arm64-0.14.1.tgz", @@ -1299,6 +1268,22 @@ "node": ">= 10" } }, + "node_modules/@css-inline/css-inline/node_modules/@css-inline/css-inline-android-arm-eabi": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/@css-inline/css-inline-android-arm-eabi/-/css-inline-android-arm-eabi-0.14.1.tgz", + "integrity": "sha512-LNUR8TY4ldfYi0mi/d4UNuHJ+3o8yLQH9r2Nt6i4qeg1i7xswfL3n/LDLRXvGjBYqeEYNlhlBQzbPwMX1qrU6A==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", @@ -14522,10 +14507,9 @@ } }, "node_modules/luxon": { - "version": "3.4.4", + "version": "3.5.0", "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.4.4.tgz", "integrity": "sha512-zobTr7akeGHnv7eBOXcRgMeCP6+uyYsczwmeRCauvpvaAltgNyTbLH/+VaEAPUeWBT+1GuNmz4wC/6jtQzbbVA==", - "version": "3.5.0", "license": "MIT", "engines": { "node": ">=12" diff --git a/src/modules/testimonials/testimonials.controller.ts b/src/modules/testimonials/testimonials.controller.ts index 41dada356..1d538835f 100644 --- a/src/modules/testimonials/testimonials.controller.ts +++ b/src/modules/testimonials/testimonials.controller.ts @@ -11,6 +11,7 @@ import { ParseUUIDPipe, HttpStatus, Delete, + Patch, } from '@nestjs/common'; import { ApiBearerAuth, ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'; import { UserPayload } from '../user/interfaces/user-payload.interface'; @@ -117,4 +118,26 @@ export class TestimonialsController { async deleteTestimonial(@Param('id') id: string) { return this.testimonialsService.deleteTestimonial(id); } + + @Patch(':id') + @ApiOperation({ summary: 'Update Testimonial' }) + @ApiResponse({ status: 200, description: 'Testimonial updated successfully' }) + @ApiResponse({ status: 404, description: 'Testimonial not found' }) + @ApiResponse({ status: 401, description: 'Unauthorized' }) + @ApiResponse({ status: 500, description: 'Internal Server Error' }) + async update( + @Param('id') id: string, + @Body() updateTestimonialDto: UpdateTestimonialDto, + @Req() req: { user: UserPayload } + ): Promise { + const userId = req.user.id; + + const data = await this.testimonialsService.updateTestimonial(id, updateTestimonialDto, userId); + + return { + status: 'success', + message: 'Testimonial updated successfully', + data, + } + } } diff --git a/src/modules/testimonials/testimonials.service.ts b/src/modules/testimonials/testimonials.service.ts index 5b198b8a0..c6e5d97d2 100644 --- a/src/modules/testimonials/testimonials.service.ts +++ b/src/modules/testimonials/testimonials.service.ts @@ -15,6 +15,7 @@ import UserService from '../user/user.service'; import { TestimonialMapper } from './mappers/testimonial.mapper'; import { TestimonialResponseMapper } from './mappers/testimonial-response.mapper'; import { TestimonialResponse } from './interfaces/testimonial-response.interface'; +import { UpdateTestimonialDto } from './dto/update-testimonial.dto'; @Injectable() export class TestimonialsService { @@ -106,6 +107,26 @@ export class TestimonialsService { return TestimonialResponseMapper.mapToEntity(testimonial); } + + async updateTestimonial(id: string, updateTestimonialDto: UpdateTestimonialDto, userId: string) { + const testimonial = await this.testimonialRepository.findOne({ where: { id, user: { id: userId } } }); + + if (!testimonial) { + throw new CustomHttpException('Testimonial not found', HttpStatus.NOT_FOUND); + } + + Object.assign(testimonial, updateTestimonialDto); + await this.testimonialRepository.save(testimonial); + + return { + id: testimonial.id, + user_id: userId, + content: testimonial.content, + name: testimonial.name, + updated_at: new Date(), + }; + } + async deleteTestimonial(id: string) { const testimonial = await this.testimonialRepository.findOne({ where: { id } }); if (!testimonial) { From df72bf3e8199d707bcb8f1eb1502ea60ed2327d2 Mon Sep 17 00:00:00 2001 From: Asin-Junior-Honore Date: Tue, 13 Aug 2024 23:04:19 +0100 Subject: [PATCH 4/7] feat: added functionality formultilanguage --- package.json | 1 + src/helpers/language.decorator.ts | 8 ++ .../help-center/help-center.controller.ts | 32 +++++--- .../help-center/help-center.service.ts | 80 +++++++++++++++---- src/translation.service.ts | 22 +++++ 5 files changed, 119 insertions(+), 24 deletions(-) create mode 100644 src/helpers/language.decorator.ts create mode 100644 src/translation.service.ts diff --git a/package.json b/package.json index b96023fb0..cee892eff 100644 --- a/package.json +++ b/package.json @@ -49,6 +49,7 @@ "@nestjs/typeorm": "^10.0.2", "@types/nodemailer": "^6.4.15", "@types/speakeasy": "^2.0.10", + "@vitalets/google-translate-api": "^9.2.0", "bcrypt": "^5.1.1", "bcryptjs": "^2.4.3", "bull": "^4.16.0", diff --git a/src/helpers/language.decorator.ts b/src/helpers/language.decorator.ts new file mode 100644 index 000000000..e784b0902 --- /dev/null +++ b/src/helpers/language.decorator.ts @@ -0,0 +1,8 @@ +import { createParamDecorator, ExecutionContext } from '@nestjs/common'; + +export const Language = createParamDecorator( + (data: string | undefined, ctx: ExecutionContext) => { + const request = ctx.switchToHttp().getRequest(); + return request.headers['accept-language'] || 'en'; + }, +); diff --git a/src/modules/help-center/help-center.controller.ts b/src/modules/help-center/help-center.controller.ts index e2e486af7..98a09a069 100644 --- a/src/modules/help-center/help-center.controller.ts +++ b/src/modules/help-center/help-center.controller.ts @@ -27,6 +27,7 @@ import { } from './dto/help-center.response.dto'; import { SuperAdminGuard } from '../../guards/super-admin.guard'; import { User } from '../user/entities/user.entity'; +import { Language } from '../../helpers/language.decorator'; @ApiTags('help-center') @Controller('help-center') @@ -42,18 +43,19 @@ export class HelpCenterController { @ApiResponse({ status: 400, description: 'This question already exists.' }) async create( @Body() createHelpCenterDto: CreateHelpCenterDto, - @Req() req: { user: User } + @Req() req: { user: User }, + @Language() language: string ): Promise { const user: User = req.user; - return this.helpCenterService.create(createHelpCenterDto, user); + return this.helpCenterService.create(createHelpCenterDto, user, language); } @skipAuth() @Get('topics') @ApiOperation({ summary: 'Get all help center topics' }) @ApiResponse({ status: 200, description: 'The found records' }) - async findAll(): Promise { - return this.helpCenterService.findAll(); + async findAll(@Language() language: string): Promise { + return this.helpCenterService.findAll(language); } @skipAuth() @@ -61,8 +63,11 @@ export class HelpCenterController { @ApiOperation({ summary: 'Get a help center topic by ID' }) @ApiResponse({ status: 200, description: 'The found record' }) @ApiResponse({ status: 404, description: 'Topic not found' }) - async findOne(@Param() params: GetHelpCenterDto): Promise { - const helpCenter = await this.helpCenterService.findOne(params.id); + async findOne( + @Param() params: GetHelpCenterDto, + @Language() language: string + ): Promise { + const helpCenter = await this.helpCenterService.findOne(params.id, language); if (!helpCenter) { throw new NotFoundException(`Help center topic with ID ${params.id} not found`); } @@ -74,8 +79,11 @@ export class HelpCenterController { @ApiOperation({ summary: 'Search help center topics' }) @ApiResponse({ status: 200, description: 'The found records' }) @ApiResponse({ status: 422, description: 'Invalid search criteria.' }) - async search(@Query() query: SearchHelpCenterDto): Promise { - return this.helpCenterService.search(query); + async search( + @Query() query: SearchHelpCenterDto, + @Language() language: string + ): Promise { + return this.helpCenterService.search(query, language); } @ApiBearerAuth() @@ -86,9 +94,13 @@ export class HelpCenterController { @ApiResponse({ status: 403, description: 'Access denied, only authorized users can access this endpoint' }) @ApiResponse({ status: 404, description: 'Topic not found, please check and try again' }) @ApiResponse({ status: 500, description: 'Internal Server Error' }) - async update(@Param('id') id: string, @Body() updateHelpCenterDto: UpdateHelpCenterDto) { + async update( + @Param('id') id: string, + @Body() updateHelpCenterDto: UpdateHelpCenterDto, + @Language() language: string + ) { try { - const updatedHelpCenter = await this.helpCenterService.updateTopic(id, updateHelpCenterDto); + const updatedHelpCenter = await this.helpCenterService.updateTopic(id, updateHelpCenterDto, language); return { success: true, message: 'Topic updated successfully', diff --git a/src/modules/help-center/help-center.service.ts b/src/modules/help-center/help-center.service.ts index e89c2deaa..2ec9c0158 100644 --- a/src/modules/help-center/help-center.service.ts +++ b/src/modules/help-center/help-center.service.ts @@ -8,6 +8,8 @@ import { SearchHelpCenterDto } from './dto/search-help-center.dto'; import { REQUEST_SUCCESSFUL, QUESTION_ALREADY_EXISTS, USER_NOT_FOUND } from '../../helpers/SystemMessages'; import { CustomHttpException } from '../../helpers/custom-http-filter'; import { User } from '../user/entities/user.entity'; +import { translateFields } from '../../translation.service'; +import { Language } from '../../helpers/language.decorator'; @Injectable() export class HelpCenterService { @@ -18,7 +20,7 @@ export class HelpCenterService { private userRepository: Repository ) {} - async create(createHelpCenterDto: CreateHelpCenterDto, user: User) { + async create(createHelpCenterDto: CreateHelpCenterDto, user: User, language?: string) { const existingTopic = await this.helpCenterRepository.findOne({ where: { title: createHelpCenterDto.title }, }); @@ -42,36 +44,65 @@ export class HelpCenterService { }); const newEntity = await this.helpCenterRepository.save(helpCenter); + const { translatedTitle, translatedContent } = await translateFields(newEntity.title, newEntity.content, language); + return { status_code: HttpStatus.CREATED, message: 'Request successful', - data: newEntity, + data: { + ...newEntity, + title: translatedTitle, + content: translatedContent, + }, }; } - async findAll(): Promise { + async findAll(language?: string): Promise { const centres = await this.helpCenterRepository.find(); + const translatedCentres = await Promise.all( + centres.map(async centre => { + const { translatedTitle, translatedContent } = await translateFields(centre.title, centre.content, language); + + return { + ...centre, + title: translatedTitle, + content: translatedContent, + }; + }) + ); + return { - data: centres, + data: translatedCentres, status_code: HttpStatus.OK, - message: REQUEST_SUCCESSFUL, + message: 'Request completed successfully', }; } - async findOne(id: string) { + async findOne(id: string, language?: string) { const helpCenter = await this.helpCenterRepository.findOne({ where: { id } }); if (!helpCenter) { throw new NotFoundException(`Help center topic with ID ${id} not found`); } + + const { translatedTitle, translatedContent } = await translateFields( + helpCenter.title, + helpCenter.content, + language + ); + return { status_code: HttpStatus.OK, message: REQUEST_SUCCESSFUL, - data: helpCenter, + data: { + ...helpCenter, + title: translatedTitle, + content: translatedContent, + }, }; } - async search(criteria: SearchHelpCenterDto) { + async search(criteria: SearchHelpCenterDto, language?: string) { const queryBuilder = this.helpCenterRepository.createQueryBuilder('help_center'); if (criteria.title) { queryBuilder.andWhere('help_center.title LIKE :title', { title: `%${criteria.title}%` }); @@ -80,14 +111,27 @@ export class HelpCenterService { queryBuilder.andWhere('help_center.content LIKE :content', { content: `%${criteria.content}%` }); } const query = await queryBuilder.getMany(); + + const translatedQuery = await Promise.all( + query.map(async centre => { + const { translatedTitle, translatedContent } = await translateFields(centre.title, centre.content, language); + + return { + ...centre, + title: translatedTitle, + content: translatedContent, + }; + }) + ); + return { status_code: HttpStatus.OK, message: REQUEST_SUCCESSFUL, - data: query, + data: translatedQuery, }; } - async updateTopic(id: string, updateHelpCenterDto: UpdateHelpCenterDto) { + async updateTopic(id: string, updateHelpCenterDto: UpdateHelpCenterDto, language?: string) { const existingTopic = await this.helpCenterRepository.findOneBy({ id }); if (!existingTopic) { throw new HttpException( @@ -102,14 +146,23 @@ export class HelpCenterService { await this.helpCenterRepository.update(id, updateHelpCenterDto); const updatedTopic = await this.helpCenterRepository.findOneBy({ id }); - + + const { translatedTitle, translatedContent } = await translateFields( + updatedTopic.title, + updatedTopic.content, + language + ); + return { status_code: HttpStatus.OK, message: REQUEST_SUCCESSFUL, - data: updatedTopic, + data: { + ...updatedTopic, + title: translatedTitle, + content: translatedContent, + }, }; } - async removeTopic(id: string): Promise { const existingTopic = await this.helpCenterRepository.findOneBy({ id }); @@ -125,5 +178,4 @@ export class HelpCenterService { } await this.helpCenterRepository.delete(id); } - } diff --git a/src/translation.service.ts b/src/translation.service.ts new file mode 100644 index 000000000..c02e7e0d0 --- /dev/null +++ b/src/translation.service.ts @@ -0,0 +1,22 @@ +const { translate } = require('@vitalets/google-translate-api'); +export async function translateFields( + title: string, + content: string, + targetLanguage: string, +): Promise<{ translatedTitle: string; translatedContent: string }> { + try { + const translatedTitle = await translate(title, { to: targetLanguage }); + const translatedContent = await translate(content, { to: targetLanguage }); + + return { + translatedTitle: translatedTitle.text, + translatedContent: translatedContent.text, + }; + } catch (err) { + console.error('Translation error:', err); + return { + translatedTitle: title, + translatedContent: content, + }; + } +} From 791452ffe97a4de659bac0de659421307212df8c Mon Sep 17 00:00:00 2001 From: Asin-Junior-Honore Date: Tue, 13 Aug 2024 23:13:37 +0100 Subject: [PATCH 5/7] feat: added functionality for multilanguage --- src/modules/help-center/help-center.service.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/modules/help-center/help-center.service.ts b/src/modules/help-center/help-center.service.ts index 2ec9c0158..e8a968e3e 100644 --- a/src/modules/help-center/help-center.service.ts +++ b/src/modules/help-center/help-center.service.ts @@ -9,7 +9,6 @@ import { REQUEST_SUCCESSFUL, QUESTION_ALREADY_EXISTS, USER_NOT_FOUND } from '../ import { CustomHttpException } from '../../helpers/custom-http-filter'; import { User } from '../user/entities/user.entity'; import { translateFields } from '../../translation.service'; -import { Language } from '../../helpers/language.decorator'; @Injectable() export class HelpCenterService { From 5b693a8ba5375f7e5ba594d615b70c4c334831ad Mon Sep 17 00:00:00 2001 From: Asin-Junior-Honore Date: Wed, 14 Aug 2024 08:35:33 +0100 Subject: [PATCH 6/7] feat: added functionality for multilanguage --- package-lock.json | 24 ++++++++++++++++++- .../help-center/help-center.service.ts | 2 +- src/{ => translation}/translation.service.ts | 2 +- 3 files changed, 25 insertions(+), 3 deletions(-) rename src/{ => translation}/translation.service.ts (90%) diff --git a/package-lock.json b/package-lock.json index 1467deb4c..2814bc273 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25,6 +25,7 @@ "@nestjs/typeorm": "^10.0.2", "@types/nodemailer": "^6.4.15", "@types/speakeasy": "^2.0.10", + "@vitalets/google-translate-api": "^9.2.0", "bcrypt": "^5.1.1", "bcryptjs": "^2.4.3", "bull": "^4.16.0", @@ -4493,6 +4494,26 @@ "dev": true, "license": "ISC" }, + "node_modules/@vitalets/google-translate-api": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/@vitalets/google-translate-api/-/google-translate-api-9.2.0.tgz", + "integrity": "sha512-w98IPWGuexlGmh8Y19AxF6cgWT0U5JLevVNDKEuFpTWtBC9z3YtDWKTDxF3nPP1k9bWicuB1V7I7YfHoZiDScw==", + "license": "MIT", + "dependencies": { + "@types/http-errors": "^1.8.2", + "http-errors": "^2.0.0", + "node-fetch": "^2.6.7" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@vitalets/google-translate-api/node_modules/@types/http-errors": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-1.8.2.tgz", + "integrity": "sha512-EqX+YQxINb+MeXaIqYDASb6U6FCHbWjkj4a1CKDBks3d/QiB2+PqBLyO72vLDgAO1wUI4O+9gweRcQK11bTL/w==", + "license": "MIT" + }, "node_modules/@webassemblyjs/ast": { "version": "1.12.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.12.1.tgz", @@ -8619,7 +8640,8 @@ "node_modules/file-type-mime": { "version": "0.4.3", "resolved": "https://registry.npmjs.org/file-type-mime/-/file-type-mime-0.4.3.tgz", - "integrity": "sha512-yumBt0l9E03Oyk3KZyq9KTM9LF0XClKWtVU+bDEOl+tbIlUr/Jnl0ZjkF/r6KmqmjJgGaWhUDSdG2HUvLJ3kNA==" + "integrity": "sha512-yumBt0l9E03Oyk3KZyq9KTM9LF0XClKWtVU+bDEOl+tbIlUr/Jnl0ZjkF/r6KmqmjJgGaWhUDSdG2HUvLJ3kNA==", + "license": "MIT" }, "node_modules/filelist": { "version": "1.0.4", diff --git a/src/modules/help-center/help-center.service.ts b/src/modules/help-center/help-center.service.ts index e8a968e3e..69fdbe9df 100644 --- a/src/modules/help-center/help-center.service.ts +++ b/src/modules/help-center/help-center.service.ts @@ -8,7 +8,7 @@ import { SearchHelpCenterDto } from './dto/search-help-center.dto'; import { REQUEST_SUCCESSFUL, QUESTION_ALREADY_EXISTS, USER_NOT_FOUND } from '../../helpers/SystemMessages'; import { CustomHttpException } from '../../helpers/custom-http-filter'; import { User } from '../user/entities/user.entity'; -import { translateFields } from '../../translation.service'; +import { translateFields } from '../../translation/translation.service'; @Injectable() export class HelpCenterService { diff --git a/src/translation.service.ts b/src/translation/translation.service.ts similarity index 90% rename from src/translation.service.ts rename to src/translation/translation.service.ts index c02e7e0d0..c834ba620 100644 --- a/src/translation.service.ts +++ b/src/translation/translation.service.ts @@ -1,4 +1,4 @@ -const { translate } = require('@vitalets/google-translate-api'); +import { translate } from '@vitalets/google-translate-api' export async function translateFields( title: string, content: string, From fc886ddee925f59d100b5e659b912087508fa7c7 Mon Sep 17 00:00:00 2001 From: Asin-Junior-Honore Date: Wed, 14 Aug 2024 09:25:16 +0100 Subject: [PATCH 7/7] feat: added functionality for updating testimonial --- src/helpers/language.decorator.ts | 8 -- .../help-center/help-center.controller.ts | 34 +++------ .../help-center/help-center.service.ts | 75 +++---------------- src/translation/translation.service.ts | 22 ------ 4 files changed, 22 insertions(+), 117 deletions(-) delete mode 100644 src/helpers/language.decorator.ts delete mode 100644 src/translation/translation.service.ts diff --git a/src/helpers/language.decorator.ts b/src/helpers/language.decorator.ts deleted file mode 100644 index e784b0902..000000000 --- a/src/helpers/language.decorator.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { createParamDecorator, ExecutionContext } from '@nestjs/common'; - -export const Language = createParamDecorator( - (data: string | undefined, ctx: ExecutionContext) => { - const request = ctx.switchToHttp().getRequest(); - return request.headers['accept-language'] || 'en'; - }, -); diff --git a/src/modules/help-center/help-center.controller.ts b/src/modules/help-center/help-center.controller.ts index 98a09a069..62c730b02 100644 --- a/src/modules/help-center/help-center.controller.ts +++ b/src/modules/help-center/help-center.controller.ts @@ -27,7 +27,6 @@ import { } from './dto/help-center.response.dto'; import { SuperAdminGuard } from '../../guards/super-admin.guard'; import { User } from '../user/entities/user.entity'; -import { Language } from '../../helpers/language.decorator'; @ApiTags('help-center') @Controller('help-center') @@ -43,19 +42,18 @@ export class HelpCenterController { @ApiResponse({ status: 400, description: 'This question already exists.' }) async create( @Body() createHelpCenterDto: CreateHelpCenterDto, - @Req() req: { user: User }, - @Language() language: string + @Req() req: { user: User } ): Promise { const user: User = req.user; - return this.helpCenterService.create(createHelpCenterDto, user, language); + return this.helpCenterService.create(createHelpCenterDto, user); } @skipAuth() @Get('topics') @ApiOperation({ summary: 'Get all help center topics' }) @ApiResponse({ status: 200, description: 'The found records' }) - async findAll(@Language() language: string): Promise { - return this.helpCenterService.findAll(language); + async findAll(): Promise { + return this.helpCenterService.findAll(); } @skipAuth() @@ -63,11 +61,8 @@ export class HelpCenterController { @ApiOperation({ summary: 'Get a help center topic by ID' }) @ApiResponse({ status: 200, description: 'The found record' }) @ApiResponse({ status: 404, description: 'Topic not found' }) - async findOne( - @Param() params: GetHelpCenterDto, - @Language() language: string - ): Promise { - const helpCenter = await this.helpCenterService.findOne(params.id, language); + async findOne(@Param() params: GetHelpCenterDto): Promise { + const helpCenter = await this.helpCenterService.findOne(params.id); if (!helpCenter) { throw new NotFoundException(`Help center topic with ID ${params.id} not found`); } @@ -79,11 +74,8 @@ export class HelpCenterController { @ApiOperation({ summary: 'Search help center topics' }) @ApiResponse({ status: 200, description: 'The found records' }) @ApiResponse({ status: 422, description: 'Invalid search criteria.' }) - async search( - @Query() query: SearchHelpCenterDto, - @Language() language: string - ): Promise { - return this.helpCenterService.search(query, language); + async search(@Query() query: SearchHelpCenterDto): Promise { + return this.helpCenterService.search(query); } @ApiBearerAuth() @@ -94,13 +86,9 @@ export class HelpCenterController { @ApiResponse({ status: 403, description: 'Access denied, only authorized users can access this endpoint' }) @ApiResponse({ status: 404, description: 'Topic not found, please check and try again' }) @ApiResponse({ status: 500, description: 'Internal Server Error' }) - async update( - @Param('id') id: string, - @Body() updateHelpCenterDto: UpdateHelpCenterDto, - @Language() language: string - ) { + async update(@Param('id') id: string, @Body() updateHelpCenterDto: UpdateHelpCenterDto) { try { - const updatedHelpCenter = await this.helpCenterService.updateTopic(id, updateHelpCenterDto, language); + const updatedHelpCenter = await this.helpCenterService.updateTopic(id, updateHelpCenterDto); return { success: true, message: 'Topic updated successfully', @@ -202,4 +190,4 @@ export class HelpCenterController { } } } -} +} \ No newline at end of file diff --git a/src/modules/help-center/help-center.service.ts b/src/modules/help-center/help-center.service.ts index 69fdbe9df..dc65405a2 100644 --- a/src/modules/help-center/help-center.service.ts +++ b/src/modules/help-center/help-center.service.ts @@ -8,7 +8,6 @@ import { SearchHelpCenterDto } from './dto/search-help-center.dto'; import { REQUEST_SUCCESSFUL, QUESTION_ALREADY_EXISTS, USER_NOT_FOUND } from '../../helpers/SystemMessages'; import { CustomHttpException } from '../../helpers/custom-http-filter'; import { User } from '../user/entities/user.entity'; -import { translateFields } from '../../translation/translation.service'; @Injectable() export class HelpCenterService { @@ -19,7 +18,7 @@ export class HelpCenterService { private userRepository: Repository ) {} - async create(createHelpCenterDto: CreateHelpCenterDto, user: User, language?: string) { + async create(createHelpCenterDto: CreateHelpCenterDto, user: User) { const existingTopic = await this.helpCenterRepository.findOne({ where: { title: createHelpCenterDto.title }, }); @@ -43,65 +42,36 @@ export class HelpCenterService { }); const newEntity = await this.helpCenterRepository.save(helpCenter); - const { translatedTitle, translatedContent } = await translateFields(newEntity.title, newEntity.content, language); - return { status_code: HttpStatus.CREATED, message: 'Request successful', - data: { - ...newEntity, - title: translatedTitle, - content: translatedContent, - }, + data: newEntity, }; } - async findAll(language?: string): Promise { + async findAll(): Promise { const centres = await this.helpCenterRepository.find(); - const translatedCentres = await Promise.all( - centres.map(async centre => { - const { translatedTitle, translatedContent } = await translateFields(centre.title, centre.content, language); - - return { - ...centre, - title: translatedTitle, - content: translatedContent, - }; - }) - ); - return { - data: translatedCentres, + data: centres, status_code: HttpStatus.OK, - message: 'Request completed successfully', + message: REQUEST_SUCCESSFUL, }; } - async findOne(id: string, language?: string) { + async findOne(id: string) { const helpCenter = await this.helpCenterRepository.findOne({ where: { id } }); if (!helpCenter) { throw new NotFoundException(`Help center topic with ID ${id} not found`); } - - const { translatedTitle, translatedContent } = await translateFields( - helpCenter.title, - helpCenter.content, - language - ); - return { status_code: HttpStatus.OK, message: REQUEST_SUCCESSFUL, - data: { - ...helpCenter, - title: translatedTitle, - content: translatedContent, - }, + data: helpCenter, }; } - async search(criteria: SearchHelpCenterDto, language?: string) { + async search(criteria: SearchHelpCenterDto) { const queryBuilder = this.helpCenterRepository.createQueryBuilder('help_center'); if (criteria.title) { queryBuilder.andWhere('help_center.title LIKE :title', { title: `%${criteria.title}%` }); @@ -110,27 +80,14 @@ export class HelpCenterService { queryBuilder.andWhere('help_center.content LIKE :content', { content: `%${criteria.content}%` }); } const query = await queryBuilder.getMany(); - - const translatedQuery = await Promise.all( - query.map(async centre => { - const { translatedTitle, translatedContent } = await translateFields(centre.title, centre.content, language); - - return { - ...centre, - title: translatedTitle, - content: translatedContent, - }; - }) - ); - return { status_code: HttpStatus.OK, message: REQUEST_SUCCESSFUL, - data: translatedQuery, + data: query, }; } - async updateTopic(id: string, updateHelpCenterDto: UpdateHelpCenterDto, language?: string) { + async updateTopic(id: string, updateHelpCenterDto: UpdateHelpCenterDto) { const existingTopic = await this.helpCenterRepository.findOneBy({ id }); if (!existingTopic) { throw new HttpException( @@ -146,20 +103,10 @@ export class HelpCenterService { await this.helpCenterRepository.update(id, updateHelpCenterDto); const updatedTopic = await this.helpCenterRepository.findOneBy({ id }); - const { translatedTitle, translatedContent } = await translateFields( - updatedTopic.title, - updatedTopic.content, - language - ); - return { status_code: HttpStatus.OK, message: REQUEST_SUCCESSFUL, - data: { - ...updatedTopic, - title: translatedTitle, - content: translatedContent, - }, + data: updatedTopic, }; } diff --git a/src/translation/translation.service.ts b/src/translation/translation.service.ts deleted file mode 100644 index c834ba620..000000000 --- a/src/translation/translation.service.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { translate } from '@vitalets/google-translate-api' -export async function translateFields( - title: string, - content: string, - targetLanguage: string, -): Promise<{ translatedTitle: string; translatedContent: string }> { - try { - const translatedTitle = await translate(title, { to: targetLanguage }); - const translatedContent = await translate(content, { to: targetLanguage }); - - return { - translatedTitle: translatedTitle.text, - translatedContent: translatedContent.text, - }; - } catch (err) { - console.error('Translation error:', err); - return { - translatedTitle: title, - translatedContent: content, - }; - } -}