From d791fbccda91a546c876c1c9d773f92b0554b276 Mon Sep 17 00:00:00 2001 From: sanjay-k1910 Date: Thu, 22 Feb 2024 18:24:58 +0530 Subject: [PATCH] fix: search and sorting issue in ecosystem member list API Signed-off-by: sanjay-k1910 Signed-off-by: bhavanakarwade --- .../src/authz/guards/ecosystem-roles.guard.ts | 16 +-- .../src/ecosystem/dtos/get-members.dto.ts | 29 +--- .../src/ecosystem/ecosystem.controller.ts | 13 +- .../src/ecosystem/ecosystem.service.ts | 9 +- .../interfaces/ecosystemMembers.interface.ts | 1 + apps/ecosystem/src/ecosystem.controller.ts | 2 +- apps/ecosystem/src/ecosystem.repository.ts | 134 +++++++++--------- apps/ecosystem/src/ecosystem.service.ts | 7 +- libs/enum/src/enum.ts | 7 + libs/http-exception.filter.ts | 4 + 10 files changed, 110 insertions(+), 112 deletions(-) diff --git a/apps/api-gateway/src/authz/guards/ecosystem-roles.guard.ts b/apps/api-gateway/src/authz/guards/ecosystem-roles.guard.ts index 9f011b92c..099f1146a 100644 --- a/apps/api-gateway/src/authz/guards/ecosystem-roles.guard.ts +++ b/apps/api-gateway/src/authz/guards/ecosystem-roles.guard.ts @@ -1,8 +1,4 @@ -import { BadRequestException, CanActivate, ExecutionContext, ForbiddenException, Logger } from '@nestjs/common'; - -import { HttpException } from '@nestjs/common'; -import { HttpStatus } from '@nestjs/common'; -import { Injectable } from '@nestjs/common'; +import { BadRequestException, CanActivate, ExecutionContext, ForbiddenException, Logger, Injectable } from '@nestjs/common'; import { ECOSYSTEM_ROLES_KEY } from '../decorators/roles.decorator'; import { Reflector } from '@nestjs/core'; import { EcosystemService } from '../../ecosystem/ecosystem.service'; @@ -54,19 +50,17 @@ export class EcosystemRolesGuard implements CanActivate { const ecosystemOrgData = await this.ecosystemService.fetchEcosystemOrg(ecosystemId, orgId); if (!ecosystemOrgData) { - throw new HttpException('Organization does not match', HttpStatus.FORBIDDEN); + throw new ForbiddenException('Organization does not match'); } - const {response} = ecosystemOrgData; - - user.ecosystemOrgRole = response['ecosystemRole']['name']; + user.ecosystemOrgRole = ecosystemOrgData['ecosystemRole']['name']; if (!user.ecosystemOrgRole) { - throw new HttpException('Ecosystem role not match', HttpStatus.FORBIDDEN); + throw new ForbiddenException('Ecosystem role not match'); } } else { - throw new HttpException('organization & ecosystem is required', HttpStatus.BAD_REQUEST); + throw new BadRequestException('organization & ecosystem is required'); } // Sending user friendly message if a user attempts to access an API that is inaccessible to their role diff --git a/apps/api-gateway/src/ecosystem/dtos/get-members.dto.ts b/apps/api-gateway/src/ecosystem/dtos/get-members.dto.ts index 3a7e78f78..f12af3ea4 100644 --- a/apps/api-gateway/src/ecosystem/dtos/get-members.dto.ts +++ b/apps/api-gateway/src/ecosystem/dtos/get-members.dto.ts @@ -1,34 +1,20 @@ -import { Transform, Type } from 'class-transformer'; +import { Transform } from 'class-transformer'; import { trim } from '@credebl/common/cast.helper'; import { ApiProperty } from '@nestjs/swagger'; import { IsEnum, IsOptional } from 'class-validator'; -import { SortFields } from 'apps/connection/src/enum/connection.enum'; -import { SortValue } from '@credebl/enum/enum'; - -export class GetAllEcosystemMembersDto { - - @ApiProperty({ required: false, example: '1' }) - @IsOptional() - pageNumber: number; - - @ApiProperty({ required: false, example: '10' }) - @IsOptional() - pageSize: number; - - @ApiProperty({ required: false }) - @IsOptional() - @Transform(({ value }) => trim(value)) - @Type(() => String) - search: string = ''; +import { SortMembers, SortValue } from '@credebl/enum/enum'; +import { PaginationDto } from '@credebl/common/dtos/pagination.dto'; +export class GetAllEcosystemMembersDto extends PaginationDto { @ApiProperty({ + enum: [SortMembers.CREATED_DATE_TIME, SortMembers.ID, SortMembers.ORGANIZATION, SortMembers.STATUS], required: false }) @Transform(({ value }) => trim(value)) @IsOptional() - @IsEnum(SortFields) - sortField: string = SortFields.CREATED_DATE_TIME; + @IsEnum(SortMembers) + sortField: string = SortMembers.CREATED_DATE_TIME; @ApiProperty({ enum: [SortValue.DESC, SortValue.ASC], @@ -38,5 +24,4 @@ export class GetAllEcosystemMembersDto { @IsOptional() @IsEnum(SortValue) sortBy: string = SortValue.DESC; - } diff --git a/apps/api-gateway/src/ecosystem/ecosystem.controller.ts b/apps/api-gateway/src/ecosystem/ecosystem.controller.ts index bfc3371c8..05a0ba0b6 100644 --- a/apps/api-gateway/src/ecosystem/ecosystem.controller.ts +++ b/apps/api-gateway/src/ecosystem/ecosystem.controller.ts @@ -1,6 +1,6 @@ import { ApiBearerAuth, ApiExcludeEndpoint, ApiForbiddenResponse, ApiOperation, ApiQuery, ApiResponse, ApiTags, ApiUnauthorizedResponse } from '@nestjs/swagger'; import { EcosystemService } from './ecosystem.service'; -import { Controller, UseFilters, Put, Post, Get, Body, Param, UseGuards, Query, BadRequestException, Delete, HttpStatus, Res } from '@nestjs/common'; +import { Controller, UseFilters, Put, Post, Get, Body, Param, UseGuards, Query, BadRequestException, Delete, HttpStatus, Res, ParseUUIDPipe } from '@nestjs/common'; import { RequestCredDefDto, RequestSchemaDto } from './dtos/request-schema.dto'; import IResponse from '@credebl/common/interfaces/response.interface'; import { Response } from 'express'; @@ -256,7 +256,7 @@ export class EcosystemController { @EcosystemsRoles(EcosystemRoles.ECOSYSTEM_OWNER, EcosystemRoles.ECOSYSTEM_LEAD, EcosystemRoles.ECOSYSTEM_MEMBER) @ApiBearerAuth() @UseGuards(AuthGuard('jwt'), EcosystemRolesGuard, OrgRolesGuard) - @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) + @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) @ApiOperation({ summary: 'Get ecosystem members list', description: 'Get ecosystem members list.' }) @ApiQuery({ name: 'pageNumber', @@ -274,18 +274,19 @@ export class EcosystemController { required: false }) async getEcosystemMembers( - @Param('ecosystemId') ecosystemId: string, + @Param('ecosystemId', new ParseUUIDPipe({exceptionFactory: (): Error => { throw new BadRequestException(`Invalid format for ecosystemId`); }})) ecosystemId: string, @Param('orgId') orgId: string, @Query() getEcosystemMembers: GetAllEcosystemMembersDto, @Res() res: Response): Promise { + const members = await this.ecosystemService.getEcosystemMembers(ecosystemId, getEcosystemMembers); const finalResponse: IResponse = { - statusCode: 200, + statusCode: HttpStatus.OK, message: ResponseMessages.ecosystem.success.fetchMembers, - data: members?.response + data: members }; - return res.status(200).json(finalResponse); + return res.status(HttpStatus.OK).json(finalResponse); } @Post('/:ecosystemId/:orgId/transaction/schema') diff --git a/apps/api-gateway/src/ecosystem/ecosystem.service.ts b/apps/api-gateway/src/ecosystem/ecosystem.service.ts index 2494fee1b..92822bf1d 100644 --- a/apps/api-gateway/src/ecosystem/ecosystem.service.ts +++ b/apps/api-gateway/src/ecosystem/ecosystem.service.ts @@ -93,11 +93,10 @@ export class EcosystemService extends BaseService { */ async getEcosystemMembers( ecosystemId: string, - getEcosystemMembers: GetAllEcosystemMembersDto + payload: GetAllEcosystemMembersDto ): Promise<{ response: object }> { - const { pageNumber, pageSize, search, sortBy } = getEcosystemMembers; - const payload = { ecosystemId, pageNumber, pageSize, search, sortBy }; - return this.sendNats(this.serviceProxy, 'fetch-ecosystem-members', payload); + payload['ecosystemId'] = ecosystemId; + return this.sendNatsMessage(this.serviceProxy, 'fetch-ecosystem-members', payload); } /** @@ -128,7 +127,7 @@ export class EcosystemService extends BaseService { async fetchEcosystemOrg(ecosystemId: string, orgId: string): Promise<{ response: object }> { const payload = { ecosystemId, orgId }; - return this.sendNats(this.serviceProxy, 'fetch-ecosystem-org-data', payload); + return this.sendNatsMessage(this.serviceProxy, 'fetch-ecosystem-org-data', payload); } async getEndorsementTranasactions( diff --git a/apps/ecosystem/interfaces/ecosystemMembers.interface.ts b/apps/ecosystem/interfaces/ecosystemMembers.interface.ts index 8ea39f7cf..58b12fd4b 100644 --- a/apps/ecosystem/interfaces/ecosystemMembers.interface.ts +++ b/apps/ecosystem/interfaces/ecosystemMembers.interface.ts @@ -5,4 +5,5 @@ export interface EcosystemMembersPayload { pageSize: number; search: string; sortBy: string; + sortField: string; } \ No newline at end of file diff --git a/apps/ecosystem/src/ecosystem.controller.ts b/apps/ecosystem/src/ecosystem.controller.ts index db3ab3281..4797821ed 100644 --- a/apps/ecosystem/src/ecosystem.controller.ts +++ b/apps/ecosystem/src/ecosystem.controller.ts @@ -79,7 +79,7 @@ export class EcosystemController { */ @MessagePattern({ cmd: 'fetch-ecosystem-members' }) async getEcosystemMembers(@Body() payload: EcosystemMembersPayload): Promise { - return this.ecosystemService.getEcoystemMembers(payload); + return this.ecosystemService.getEcosystemMembers(payload); } /** diff --git a/apps/ecosystem/src/ecosystem.repository.ts b/apps/ecosystem/src/ecosystem.repository.ts index da17cd2f0..98be181dc 100644 --- a/apps/ecosystem/src/ecosystem.repository.ts +++ b/apps/ecosystem/src/ecosystem.repository.ts @@ -9,6 +9,7 @@ import { ResponseMessages } from '@credebl/common/response-messages'; import { NotFoundException } from '@nestjs/common'; import { CommonConstants } from '@credebl/common/common.constant'; import { GetAllSchemaList } from '../interfaces/endorsements.interface'; +import { SortValue } from '@credebl/enum/enum'; // eslint-disable-next-line camelcase @Injectable() @@ -160,7 +161,7 @@ export class EcosystemRepository { } }) ]); - + return { ecosystemDetails, totalCount: ecosystemCount @@ -170,7 +171,7 @@ export class EcosystemRepository { throw error; } } - + /** * @@ -486,62 +487,67 @@ export class EcosystemRepository { * @returns users list */ -async findEcosystemMembers( - ecosystemId: string, - pageNumber: number, - pageSize: number, - search: string, - sortBy: string -): Promise { - try { - const result = await this.prisma.$transaction([ - this.prisma.ecosystem_orgs.findMany({ - where: { - ecosystemId, - OR: [ - { - organisation: { - name: { contains: search, mode: 'insensitive' }, - // eslint-disable-next-line camelcase - org_agents: { - some: { - orgDid: { contains: search, mode: 'insensitive' } + async findEcosystemMembers( + ecosystemId: string, + pageNumber: number, + pageSize: number, + search: string, + sortBy: string, + sortField: string + ): Promise { + try { + const result = await this.prisma.$transaction([ + this.prisma.ecosystem_orgs.findMany({ + where: { + ecosystemId, + OR: [ + { + organisation: { + name: { contains: search, mode: 'insensitive' } + } + }, + { + organisation: { + // eslint-disable-next-line camelcase + org_agents: { + some: { + orgDid: { contains: search, mode: 'insensitive' } + } } } } + ] + }, + include: { + ecosystem: true, + ecosystemRole: true, + organisation: { + select: { + name: true, + orgSlug: true, + // eslint-disable-next-line camelcase + org_agents: true + } } - ] - }, - include: { - ecosystem: true, - ecosystemRole: true, - organisation: { - select: { - name: true, - orgSlug: true, - // eslint-disable-next-line camelcase - org_agents: true - } + }, + take: Number(pageSize), + skip: (pageNumber - 1) * pageSize, + orderBy: { + [sortField]: SortValue.ASC === sortBy ? 'asc' : 'desc' } - }, - take: Number(pageSize), - skip: (pageNumber - 1) * pageSize, - orderBy: { - createDateTime: 'asc' === sortBy ? 'asc' : 'desc' - } - }), - this.prisma.ecosystem_orgs.count({ - where: { - ecosystemId - } - }) - ]); - return result; - } catch (error) { - this.logger.error(`error: ${JSON.stringify(error)}`); - throw error; + }), + this.prisma.ecosystem_orgs.count({ + where: { + ecosystemId + } + }) + ]); + return result; + } catch (error) { + this.logger.error(`error: ${JSON.stringify(error)}`); + throw error; + } } -} async getEcosystemInvitationsPagination(queryObject: object, pageNumber: number, pageSize: number): Promise { try { @@ -590,18 +596,18 @@ async findEcosystemMembers( async fetchEcosystemOrg( payload: object ): Promise { - + return this.prisma.ecosystem_orgs.findFirst({ - where: { - ...payload - }, - select: { - ecosystem: true, - ecosystemRole: true, - organisation: true - } - }); - + where: { + ...payload + }, + select: { + ecosystem: true, + ecosystemRole: true, + organisation: true + } + }); + } @@ -833,7 +839,7 @@ async findEcosystemMembers( schemaTransactionResponse: SchemaTransactionResponse, requestBody: object, type: endorsementTransactionType - // eslint-disable-next-line camelcase + // eslint-disable-next-line camelcase ): Promise { try { const { endorserDid, authorDid, requestPayload, status, ecosystemOrgId, userId } = schemaTransactionResponse; diff --git a/apps/ecosystem/src/ecosystem.service.ts b/apps/ecosystem/src/ecosystem.service.ts index 40c540167..97fb35a47 100644 --- a/apps/ecosystem/src/ecosystem.service.ts +++ b/apps/ecosystem/src/ecosystem.service.ts @@ -1109,15 +1109,16 @@ export class EcosystemService { * @returns Ecosystem members list */ - async getEcoystemMembers(payload: EcosystemMembersPayload): Promise { + async getEcosystemMembers(payload: EcosystemMembersPayload): Promise { try { - const { ecosystemId, pageNumber, pageSize, search, sortBy } = payload; + const { ecosystemId, pageNumber, pageSize, search, sortBy, sortField } = payload; const getEcosystemMember = await this.ecosystemRepository.findEcosystemMembers( ecosystemId, pageNumber, pageSize, search, - sortBy + sortBy, + sortField ); const ecosystemMemberResponse = { diff --git a/libs/enum/src/enum.ts b/libs/enum/src/enum.ts index 2dd5470ed..3536f0bbc 100644 --- a/libs/enum/src/enum.ts +++ b/libs/enum/src/enum.ts @@ -70,6 +70,13 @@ export enum AutoAccept { Never = "never" } +export enum SortMembers { + CREATED_DATE_TIME = 'createDateTime', + STATUS = 'status', + ID = 'id', + ORGANIZATION = 'organization' +} + const transitionMap: { [key in Invitation]: Invitation[] } = { [Invitation.PENDING]: [Invitation.ACCEPTED, Invitation.REJECTED], [Invitation.ACCEPTED]: [], diff --git a/libs/http-exception.filter.ts b/libs/http-exception.filter.ts index 645b20b3a..c7dc008fb 100644 --- a/libs/http-exception.filter.ts +++ b/libs/http-exception.filter.ts @@ -39,6 +39,10 @@ export class HttpExceptionFilter implements ExceptionFilter { httpStatus = HttpStatus.BAD_REQUEST; message = exception?.response?.message || exception?.message; break; + case 'P2023': // Inconsistent column data: {message} + httpStatus = HttpStatus.BAD_REQUEST; + message = exception?.meta?.message || exception?.message; + break; case 'P2018': // The required connected records were not found. {details} case 'P2025': // An operation failed because it depends on one or more records that were required but not found. {cause} case 'P2015': // A related record could not be found. {details}