From 99dc0c676f23bb4f0daaa6e0dd33c2ded26e194a Mon Sep 17 00:00:00 2001 From: bhavanakarwade <137506897+bhavanakarwade@users.noreply.github.com> Date: Wed, 19 Jun 2024 18:33:32 +0530 Subject: [PATCH] feat: delete ecosystem by org Id (#768) * feat: delete ecosystem members Signed-off-by: bhavanakarwade * wip: delete ecosystem Signed-off-by: bhavanakarwade * fix: eslint issues Signed-off-by: bhavanakarwade * fix: resolved trimmedstring issue Signed-off-by: bhavanakarwade * refactor: interface type Signed-off-by: bhavanakarwade * refactor: get organization details function Signed-off-by: bhavanakarwade * fix: resolved conflicts Signed-off-by: bhavanakarwade * refactor: made description field required Signed-off-by: bhavanakarwade * refactor: function name Signed-off-by: bhavanakarwade --------- Signed-off-by: bhavanakarwade Signed-off-by: KulkarniShashank --- .../src/ecosystem/ecosystem.controller.ts | 25 ++- .../src/ecosystem/ecosystem.service.ts | 15 +- .../dtos/create-organization-dto.ts | 2 +- .../interfaces/ecosystem.interfaces.ts | 37 ++++- apps/ecosystem/src/ecosystem.controller.ts | 11 +- apps/ecosystem/src/ecosystem.module.ts | 5 +- apps/ecosystem/src/ecosystem.repository.ts | 119 +++++++++++++- apps/ecosystem/src/ecosystem.service.ts | 147 +++++++++++++++++- .../DeleteEcosystemMemberTemplate.ts | 50 ++++++ apps/issuance/src/issuance.repository.ts | 16 +- .../src/organization.controller.ts | 5 + apps/organization/src/organization.service.ts | 9 ++ apps/user/repositories/user.repository.ts | 19 +++ apps/user/src/user.controller.ts | 6 + apps/user/src/user.service.ts | 11 ++ .../repositories/verification.repository.ts | 18 ++- .../src/interfaces/ecosystem.interface.ts | 10 +- .../src/interfaces/organization.interface.ts | 8 + libs/common/src/response-messages/index.ts | 14 +- libs/enum/src/enum.ts | 2 +- 20 files changed, 500 insertions(+), 29 deletions(-) create mode 100644 apps/ecosystem/templates/DeleteEcosystemMemberTemplate.ts diff --git a/apps/api-gateway/src/ecosystem/ecosystem.controller.ts b/apps/api-gateway/src/ecosystem/ecosystem.controller.ts index fbe6a47e7..d9bb589f0 100644 --- a/apps/api-gateway/src/ecosystem/ecosystem.controller.ts +++ b/apps/api-gateway/src/ecosystem/ecosystem.controller.ts @@ -527,11 +527,11 @@ export class EcosystemController { @Roles(OrgRoles.OWNER, OrgRoles.ADMIN) async addOrganizationsInEcosystem( @Body() addOrganizationsDto: AddOrganizationsDto, - @Param('ecosystemId', new ParseUUIDPipe({exceptionFactory: (): Error => { throw new BadRequestException(ResponseMessages.ecosystem.error.invalidEcosystemId); }})) ecosystemId: string, + @Param('ecosystemId', new ParseUUIDPipe({exceptionFactory: (): Error => { throw new BadRequestException(ResponseMessages.ecosystem.error.invalidEcosystemId); }}), TrimStringParamPipe) ecosystemId: string, @Param('orgId', new ParseUUIDPipe({exceptionFactory: (): Error => { throw new BadRequestException(ResponseMessages.organisation.error.invalidOrgId); }})) orgId: string, @User() user: user, @Res() res: Response - ): Promise { + ): Promise { addOrganizationsDto.ecosystemId = ecosystemId; addOrganizationsDto.orgId = orgId; @@ -668,6 +668,27 @@ export class EcosystemController { return res.status(HttpStatus.OK).json(finalResponse); } + @Delete('/:orgId/ecosystems') + @ApiOperation({ summary: 'Delete ecosystems', description: 'Delete ecosystems by orgId' }) + @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) + @UseGuards(AuthGuard('jwt'), OrgRolesGuard) + @Roles(OrgRoles.OWNER) + @ApiBearerAuth() + async deleteEcosystemAsMember( + @Param('orgId', new ParseUUIDPipe({exceptionFactory: (): Error => { throw new BadRequestException(ResponseMessages.organisation.error.invalidOrgId); }})) orgId: string, + @Res() res: Response, + @User() user: user + ): Promise { + + await this.ecosystemService.deleteEcosystemAsMember(orgId, user); + + const finalResponse: IResponse = { + statusCode: HttpStatus.OK, + message: ResponseMessages.ecosystem.success.deleteEcosystemMember + }; + return res.status(HttpStatus.OK).json(finalResponse); + } + @Delete('/:ecosystemId/:orgId/invitations/:invitationId') @ApiOperation({ summary: 'Delete ecosystem pending invitations', description: 'Delete ecosystem pending invitations' }) diff --git a/apps/api-gateway/src/ecosystem/ecosystem.service.ts b/apps/api-gateway/src/ecosystem/ecosystem.service.ts index 9298e620f..779060b7c 100644 --- a/apps/api-gateway/src/ecosystem/ecosystem.service.ts +++ b/apps/api-gateway/src/ecosystem/ecosystem.service.ts @@ -1,5 +1,4 @@ -import { Inject } from '@nestjs/common'; -import { Injectable } from '@nestjs/common'; +import { Inject, Injectable } from '@nestjs/common'; import { ClientProxy } from '@nestjs/microservices'; import { BaseService } from 'libs/service/base.service'; import { BulkEcosystemInvitationDto } from './dtos/send-invitation.dto'; @@ -13,9 +12,10 @@ import { CreateEcosystemDto } from './dtos/create-ecosystem-dto'; import { EditEcosystemDto } from './dtos/edit-ecosystem-dto'; import { IEcosystemDashboard, IEcosystemInvitation, IEcosystemInvitations, IEcosystem, IEditEcosystem, IEndorsementTransaction, ISchemaResponse } from 'apps/ecosystem/interfaces/ecosystem.interfaces'; import { PaginationDto } from '@credebl/common/dtos/pagination.dto'; -import { IEcosystemDetails } from '@credebl/common/interfaces/ecosystem.interface'; +import { IEcosystemDataDeletionResults, IEcosystemDetails } from '@credebl/common/interfaces/ecosystem.interface'; import { AddOrganizationsDto } from './dtos/add-organizations.dto'; import { schemaRequestType } from '@credebl/enum/enum'; +import { user } from '@prisma/client'; @Injectable() export class EcosystemService extends BaseService { @@ -130,6 +130,7 @@ export class EcosystemService extends BaseService { const payload = { invitationId }; return this.sendNats(this.serviceProxy, 'delete-ecosystem-invitations', payload); } + async acceptRejectEcosystemInvitaion( acceptRejectInvitation: AcceptRejectEcosystemInvitationDto, userEmail: string @@ -205,4 +206,10 @@ export class EcosystemService extends BaseService { const payload = { ecosystemId, endorsementId, orgId }; return this.sendNatsMessage(this.serviceProxy, 'decline-endorsement-transaction', payload); } -} + + async deleteEcosystemAsMember(orgId: string, userDetails: user): Promise { + const payload = { orgId, userDetails }; + return this.sendNats(this.serviceProxy, 'delete-ecosystems-as-member', payload); + } + +} \ No newline at end of file diff --git a/apps/api-gateway/src/organization/dtos/create-organization-dto.ts b/apps/api-gateway/src/organization/dtos/create-organization-dto.ts index 20b0eb442..f68ff2ee4 100644 --- a/apps/api-gateway/src/organization/dtos/create-organization-dto.ts +++ b/apps/api-gateway/src/organization/dtos/create-organization-dto.ts @@ -15,7 +15,7 @@ export class CreateOrganizationDto { @IsNotSQLInjection({ message: 'Organization name is required.' }) name: string; - @ApiPropertyOptional() + @ApiProperty() @Transform(({ value }) => trim(value)) @IsNotEmpty({ message: 'Description is required.' }) @MinLength(2, { message: 'Description must be at least 2 characters.' }) diff --git a/apps/ecosystem/interfaces/ecosystem.interfaces.ts b/apps/ecosystem/interfaces/ecosystem.interfaces.ts index 43fcd265a..8d18a8c5c 100644 --- a/apps/ecosystem/interfaces/ecosystem.interfaces.ts +++ b/apps/ecosystem/interfaces/ecosystem.interfaces.ts @@ -413,7 +413,40 @@ export interface IEcosystemOrgDetails { ecosystemOrgs: IEcosystemOrgsData[]; } - export interface IEcosystemEndorsementFlag { autoEndorsement: boolean; -} \ No newline at end of file +} + + +interface IEcosystemRole { + id: string; + name: string; + description: string; + createDateTime: Date; + lastChangedDateTime: Date; + deletedAt: Date; +} + +interface IEcosystemMemberOrgs extends IEcosystemOrgs{ + id: string; + createDateTime: Date; + lastChangedDateTime: Date; + deletedAt: Date; + ecosystemRole: IEcosystemRole; +} + +export interface IEcosystemData { + id: string; + name: string; + description: string; + tags: string; + createDateTime: Date; + createdBy: string; + lastChangedDateTime: Date; + lastChangedBy: string; + deletedAt: Date; + logoUrl: string; + autoEndorsement: boolean; + ledgers: Prisma.JsonValue; + ecosystemOrgs: IEcosystemMemberOrgs[]; +} diff --git a/apps/ecosystem/src/ecosystem.controller.ts b/apps/ecosystem/src/ecosystem.controller.ts index 730e7b6c9..3b1452ef7 100644 --- a/apps/ecosystem/src/ecosystem.controller.ts +++ b/apps/ecosystem/src/ecosystem.controller.ts @@ -9,8 +9,9 @@ import { FetchInvitationsPayload } from '../interfaces/invitations.interface'; import { EcosystemMembersPayload } from '../interfaces/ecosystemMembers.interface'; import { GetEndorsementsPayload, ISchemasResponse } from '../interfaces/endorsements.interface'; import { IEcosystemDashboard, RequestCredDeffEndorsement, IEcosystem, IEcosystemInvitation, IEcosystemInvitations, IEditEcosystem, IEndorsementTransaction, IEcosystemList, IEcosystemLeadOrgs, IRequestSchemaEndorsement, IRequestW3CSchemaEndorsement } from '../interfaces/ecosystem.interfaces'; -import { IEcosystemDetails } from '@credebl/common/interfaces/ecosystem.interface'; +import { IEcosystemDataDeletionResults, IEcosystemDetails } from '@credebl/common/interfaces/ecosystem.interface'; import { schemaRequestType } from '@credebl/enum/enum'; +import { user } from '@prisma/client'; // eslint-disable-next-line camelcase @Controller() @@ -235,4 +236,10 @@ export class EcosystemController { async declineEndorsementRequestByLead(payload: { ecosystemId: string; endorsementId: string }): Promise { return this.ecosystemService.declineEndorsementRequestByLead(payload.ecosystemId, payload.endorsementId); } -} + + @MessagePattern({ cmd: 'delete-ecosystems-as-member' }) + async deleteEcosystemAsMember(payload: { orgId: string, userDetails: user}): Promise { + const { orgId, userDetails } = payload; + return this.ecosystemService.deleteEcosystemAsMember(orgId, userDetails); + } +} \ No newline at end of file diff --git a/apps/ecosystem/src/ecosystem.module.ts b/apps/ecosystem/src/ecosystem.module.ts index 97cf805b3..91db49725 100644 --- a/apps/ecosystem/src/ecosystem.module.ts +++ b/apps/ecosystem/src/ecosystem.module.ts @@ -6,6 +6,9 @@ import { CommonModule} from '@credebl/common'; import { EcosystemRepository } from './ecosystem.repository'; import { PrismaService } from '@credebl/prisma-service'; import { CacheModule } from '@nestjs/cache-manager'; +import { getNatsOptions } from '@credebl/common/nats.config'; +import { UserActivityRepository } from 'libs/user-activity/repositories'; + @Module({ imports: [ ClientsModule.register([ @@ -20,6 +23,6 @@ import { CacheModule } from '@nestjs/cache-manager'; CacheModule.register() ], controllers: [EcosystemController], - providers: [EcosystemService, PrismaService, Logger, EcosystemRepository] + providers: [EcosystemService, UserActivityRepository, PrismaService, Logger, EcosystemRepository] }) export class EcosystemModule { } \ No newline at end of file diff --git a/apps/ecosystem/src/ecosystem.repository.ts b/apps/ecosystem/src/ecosystem.repository.ts index 2b77a9237..993b2219c 100644 --- a/apps/ecosystem/src/ecosystem.repository.ts +++ b/apps/ecosystem/src/ecosystem.repository.ts @@ -4,12 +4,13 @@ import { PrismaService } from '@credebl/prisma-service'; import { Prisma, credential_definition, ecosystem, ecosystem_config, ecosystem_invitations, ecosystem_orgs, ecosystem_roles, endorsement_transaction, org_agents, org_roles, organisation, platform_config, schema, user_org_roles } from '@prisma/client'; import { DeploymentModeType, EcosystemInvitationStatus, EcosystemOrgStatus, EcosystemRoles, endorsementTransactionStatus, endorsementTransactionType } from '../enums/ecosystem.enum'; import { updateEcosystemOrgsDto } from '../dtos/update-ecosystemOrgs.dto'; -import { CreateEcosystem, IEcosystemInvitation, SaveSchema, SchemaTransactionResponse, saveCredDef } from '../interfaces/ecosystem.interfaces'; +import { CreateEcosystem, IEcosystemData, IEcosystemInvitation, IEcosystemOrgs, IEcosystemOrgsData, SaveSchema, SchemaTransactionResponse, saveCredDef } from '../interfaces/ecosystem.interfaces'; import { ResponseMessages } from '@credebl/common/response-messages'; import { NotFoundException } from '@nestjs/common'; import { CommonConstants } from '@credebl/common/common.constant'; import { GetAllSchemaList, ISchemasResponse } from '../interfaces/endorsements.interface'; import { SortValue } from '@credebl/enum/enum'; +import { IEcosystemDataDeletionResults } from '@credebl/common/interfaces/ecosystem.interface'; // eslint-disable-next-line camelcase @Injectable() @@ -520,11 +521,12 @@ export class EcosystemRepository { // eslint-disable-next-line camelcase async getEcosystemRole(name: string): Promise { try { - return this.prisma.ecosystem_roles.findFirst({ + const getEcosytemRoles = this.prisma.ecosystem_roles.findFirst({ where: { name } }); + return getEcosytemRoles; } catch (error) { this.logger.error(`getEcosystemRole: ${JSON.stringify(error)}`); throw error; @@ -740,7 +742,6 @@ export class EcosystemRepository { } - async getEndorsementsWithPagination(queryObject: object, pageNumber: number, pageSize: number): Promise { try { const result = await this.prisma.$transaction([ @@ -1374,4 +1375,114 @@ export class EcosystemRepository { throw error; } } -} + + async getEcosystemsByOrgId(orgId: string): Promise { + try { + + const ecosystemsDetails = await this.prisma.ecosystem.findMany({ + where: { + ecosystemOrgs: { + some: { + orgId + } + } + + }, + include: { + ecosystemOrgs: { + include: { + ecosystemRole: true + } + } + } + }); + + return ecosystemsDetails; + } catch (error) { + this.logger.error(`Error in getting ecosystems: ${error.message}`); + throw error; + } + } + + async getOrgName(orgId: string): Promise<{ + name: string; + }> { + try { + const orgName = await this.prisma.organisation.findUnique({ + where: { + id: orgId + }, + select: { + name: true + } + }); + + return orgName; + } catch (error) { + this.logger.error(`Error in getting organization names: ${error.message}`); + throw error; + } + } + + async deleteEcosystemInvitations(orgId: string): Promise { + try { + const deletedEcosystemInvitations = await this.prisma.ecosystem_invitations.deleteMany({ + where: { + orgId + } + }); + + return deletedEcosystemInvitations; + } catch (error) { + this.logger.error(`Error in deleting ecosystem invitations: ${error.message}`); + throw error; + } + } + + async deleteEcosystemAsMember(orgId: string): Promise { + try { + return await this.prisma.$transaction(async (prisma) => { + const deletedEcosystemUsers = await prisma.ecosystem_users.deleteMany({ + where: { + ecosystem: { + ecosystemOrgs: { + some: { + orgId + } + } + } + } + }); + + const deleteEndorsementTransactions = await prisma.endorsement_transaction.deleteMany({ + where: { + ecosystemOrgs: { + orgId + } + } + }); + + const deletedEcosystemOrgs = await prisma.ecosystem_orgs.deleteMany({ + where: { + orgId + } + }); + + const deletedEcosystems = await prisma.ecosystem.deleteMany({ + where: { + ecosystemOrgs: { + some: { + orgId + } + } + } + }); + + return { deletedEcosystemUsers, deleteEndorsementTransactions, deletedEcosystemOrgs, deletedEcosystems }; + }); + } catch (error) { + this.logger.error(`Error in deleting ecosystems: ${error.message}`); + throw error; + } + } +} \ No newline at end of file diff --git a/apps/ecosystem/src/ecosystem.service.ts b/apps/ecosystem/src/ecosystem.service.ts index ab1f201a3..057cd8583 100644 --- a/apps/ecosystem/src/ecosystem.service.ts +++ b/apps/ecosystem/src/ecosystem.service.ts @@ -57,6 +57,7 @@ import { GetAllSchemaList, GetEndorsementsPayload, ISchemasResponse } from '../i import { CommonConstants } from '@credebl/common/common.constant'; // eslint-disable-next-line camelcase import { + RecordType, // eslint-disable-next-line camelcase credential_definition, // eslint-disable-next-line camelcase @@ -71,15 +72,19 @@ import { import { Cache } from 'cache-manager'; import { CACHE_MANAGER } from '@nestjs/cache-manager'; import { updateEcosystemOrgsDto } from '../dtos/update-ecosystemOrgs.dto'; -import { IEcosystemDetails } from '@credebl/common/interfaces/ecosystem.interface'; +import { IEcosystemDataDeletionResults, IEcosystemDetails } from '@credebl/common/interfaces/ecosystem.interface'; import { W3CSchemaPayload } from 'apps/ledger/src/schema/interfaces/schema-payload.interface'; import { OrgRoles } from 'libs/org-roles/enums'; +import { DeleteEcosystemMemberTemplate } from '../templates/DeleteEcosystemMemberTemplate'; +import { UserActivityRepository } from 'libs/user-activity/repositories'; +import { IOrgData } from '@credebl/common/interfaces/organization.interface'; @Injectable() export class EcosystemService { constructor( @Inject('NATS_CLIENT') private readonly ecosystemServiceProxy: ClientProxy, private readonly ecosystemRepository: EcosystemRepository, + private readonly userActivityRepository: UserActivityRepository, private readonly logger: Logger, private readonly prisma: PrismaService, @Inject(CACHE_MANAGER) private cacheService: Cache @@ -1452,7 +1457,7 @@ export class EcosystemService { } // eslint-disable-next-line camelcase - async getEcosystemLeadAgentDetails(ecosystemId): Promise { + async getEcosystemLeadAgentDetails(ecosystemId: string): Promise { const getEcosystemLeadDetails = await this.ecosystemRepository.getEcosystemLeadDetails(ecosystemId); return this.ecosystemRepository.getAgentDetails(getEcosystemLeadDetails.orgId); } @@ -2031,4 +2036,140 @@ export class EcosystemService { ); } } -} + + async sendMailToEcosystemMembers(email: string, orgName: string, ecosystemName: string): Promise { + const platformConfigData = await this.prisma.platform_config.findMany(); + + const urlEmailTemplate = new DeleteEcosystemMemberTemplate(); + const emailData = new EmailDto(); + emailData.emailFrom = platformConfigData[0].emailFrom; + emailData.emailTo = email; + emailData.emailSubject = `Removal of participation of “${orgName}” from ${ecosystemName}`; + + emailData.emailHtml = urlEmailTemplate.sendDeleteMemberEmailTemplate(email, orgName, ecosystemName); + + const isEmailSent = await sendEmail(emailData); + + return isEmailSent; + } + + async _getOrgData(orgId: string): Promise { + const pattern = { cmd: 'get-organization-details' }; + const payload = { orgId }; + const orgData = await this.ecosystemServiceProxy + .send(pattern, payload) + .toPromise() + .catch((error) => { + this.logger.error(`catch: ${JSON.stringify(error)}`); + throw new HttpException( + { + status: error.status, + error: error.message + }, + error.status + ); + }); + return orgData; + } + + async _getUsersDetails(userId: string): Promise { + const pattern = { cmd: 'get-user-details-by-userId' }; + const payload = { userId }; + const userData = await this.ecosystemServiceProxy + .send(pattern, payload) + .toPromise() + .catch((error) => { + this.logger.error(`catch: ${JSON.stringify(error)}`); + throw new HttpException( + { + status: error.status, + error: error.message + }, + error.status + ); + }); + return userData; + } + + async deleteEcosystemAsMember( + orgId: string, + userDetails: user + ): Promise { + try { + const getEcosystems = await this.ecosystemRepository.getEcosystemsByOrgId(orgId); + + const ecosystemLeadRoleOrgs = []; + const ecosystemMemberRoleOrgs = []; + + getEcosystems.forEach((ecosystem) => { + ecosystem.ecosystemOrgs.forEach((org) => { + if (EcosystemRoles.ECOSYSTEM_LEAD === org?.ecosystemRole?.name) { + ecosystemLeadRoleOrgs.push(org); + } else if (EcosystemRoles.ECOSYSTEM_MEMBER === org?.ecosystemRole?.name) { + ecosystemMemberRoleOrgs.push(org); + } + }); + }); + + const getEcosystemLeadRoleOrgsIds = ecosystemLeadRoleOrgs?.map((org) => org?.orgId); + const getEcosystemMemberRoleOrgIds = ecosystemMemberRoleOrgs?.map((org) => org?.orgId); + + if (getEcosystemLeadRoleOrgsIds?.includes(orgId)) { + throw new BadRequestException(ResponseMessages.ecosystem.error.notAbleToDeleteEcosystem); + } + + let ecosystemId; + let getEcosystemDetails; + let getEcosystemLeadDetails; + + getEcosystems.forEach((ecosystem) => { + ecosystem.ecosystemOrgs.forEach((org) => { + if (org.ecosystemRole && org.ecosystemRole.name === EcosystemRoles.ECOSYSTEM_LEAD) { + ecosystemId = org?.ecosystemId; + } + }); + }); + + if (ecosystemId) { + getEcosystemDetails = await this.ecosystemRepository.getEcosystemDetails(ecosystemId); + getEcosystemLeadDetails = await this.ecosystemRepository.getEcosystemLeadDetails(ecosystemId); + } else { + throw new NotFoundException(ResponseMessages.ecosystem.error.ecosystemNotExists); + } + + const getLeadUserId = getEcosystemLeadDetails?.createdBy; + + const getLeadEmailId = await this._getUsersDetails(getLeadUserId); + + const getOrgName = await this._getOrgData(orgId); + + let deleteEcosystems; + if (getEcosystemMemberRoleOrgIds?.includes(orgId)) { + deleteEcosystems = await this.ecosystemRepository.deleteEcosystemAsMember(orgId); + await this.ecosystemRepository.deleteEcosystemInvitations(orgId); + await this.sendMailToEcosystemMembers(getLeadEmailId, getOrgName?.['name'], getEcosystemDetails?.name); + } + + const ecosystemDataCount = { + deletedEndorsementTransactionsCount: deleteEcosystems?.deleteEndorsementTransactions?.count + }; + + const deletedEcosystemsData = { + deletedEcosystemCount: deleteEcosystems?.deletedEcosystemOrgs?.count, + deletedEcosystemDataCount: ecosystemDataCount + }; + + await this.userActivityRepository._orgDeletedActivity( + orgId, + userDetails, + deletedEcosystemsData, + RecordType.ECOSYSTEM_MEMBER + ); + return deleteEcosystems; + } catch (error) { + this.logger.error(`In delete ecosystems: ${JSON.stringify(error)}`); + throw new RpcException(error.response ? error.response : error); + } + } + +} \ No newline at end of file diff --git a/apps/ecosystem/templates/DeleteEcosystemMemberTemplate.ts b/apps/ecosystem/templates/DeleteEcosystemMemberTemplate.ts new file mode 100644 index 000000000..cfe35fcfc --- /dev/null +++ b/apps/ecosystem/templates/DeleteEcosystemMemberTemplate.ts @@ -0,0 +1,50 @@ +export class DeleteEcosystemMemberTemplate { + + public sendDeleteMemberEmailTemplate( + email: string, + orgName: string, + ecosystemName: string + ): string { + + return ` + + + + + + + + + +
+
+ ${process.env.PLATFORM_NAME} logo +
+ +
+

+ Hello ${email}, +

+

+ We would like to inform you that the organization“${orgName}”, has removed their participation as a member from the "${ecosystemName}" ecsosystem on CREDEBL. + +


+
+
+ For any assistance or questions while accessing your account, please do not hesitate to contact the support team at ${process.env.PUBLIC_PLATFORM_SUPPORT_EMAIL}. +
+

+ © ${process.env.POWERED_BY} +

+
+
+
+ + + `; + + } + + +} \ No newline at end of file diff --git a/apps/issuance/src/issuance.repository.ts b/apps/issuance/src/issuance.repository.ts index 9ee3e41fe..f257f01f9 100644 --- a/apps/issuance/src/issuance.repository.ts +++ b/apps/issuance/src/issuance.repository.ts @@ -592,7 +592,7 @@ export class IssuanceRepository { async deleteIssuanceRecordsByOrgId(orgId: string): Promise { try { - const tablesToCheck = [`${PrismaTables.PRESENTATIONS}`]; + const tablesToCheck = [`${PrismaTables.PRESENTATIONS}`, `${PrismaTables.ECOSYSTEM_ORGS}`]; const referenceCounts = await Promise.all( tablesToCheck.map((table) => this.prisma[table].count({ where: { orgId } })) @@ -603,7 +603,19 @@ export class IssuanceRepository { .filter(Boolean); if (0 < referencedTables.length) { - throw new ConflictException(`Organization ID ${orgId} is referenced in the following table(s): ${referencedTables.join(', ')}`); + let errorMessage = `Organization ID ${orgId} is referenced in the following table(s): ${referencedTables.join(', ')}`; + + if (1 === referencedTables.length) { + if (referencedTables.includes(`${PrismaTables.PRESENTATIONS}`)) { + errorMessage += `, ${ResponseMessages.verification.error.removeVerificationData}`; + } else if (referencedTables.includes(`${PrismaTables.ECOSYSTEM_ORGS}`)) { + errorMessage += `, ${ResponseMessages.ecosystem.error.removeEcosystemData}`; + } + } else if (2 === referencedTables.length) { + errorMessage += ', first you have to remove verification data and ecosystem data'; + } + + throw new ConflictException(errorMessage); } return await this.prisma.$transaction(async (prisma) => { diff --git a/apps/organization/src/organization.controller.ts b/apps/organization/src/organization.controller.ts index 49f573c98..ea5daada5 100644 --- a/apps/organization/src/organization.controller.ts +++ b/apps/organization/src/organization.controller.ts @@ -229,6 +229,11 @@ export class OrganizationController { return this.organizationService.fetchOrgCredentials(payload.orgId); } + @MessagePattern({ cmd: 'get-organization-details' }) + async getOrgData(payload: { orgId: string; }): Promise { + return this.organizationService.getOrgDetails(payload.orgId); + } + @MessagePattern({ cmd: 'delete-organization' }) async deleteOrganization(payload: { orgId: string, user: user }): Promise { return this.organizationService.deleteOrganization(payload.orgId, payload.user); diff --git a/apps/organization/src/organization.service.ts b/apps/organization/src/organization.service.ts index 4ad4a8b32..431da9755 100644 --- a/apps/organization/src/organization.service.ts +++ b/apps/organization/src/organization.service.ts @@ -1447,6 +1447,15 @@ export class OrganizationService { } } + async getOrgDetails(orgId: string): Promise { + try { + const orgDetails = await this.organizationRepository.getOrganizationDetails(orgId); + return orgDetails; + } catch (error) { + this.logger.error(`in getting organization details : ${JSON.stringify(error)}`); + throw new RpcException(error.response ? error.response : error); + } + } async getOrgOwner(orgId: string): Promise { try { diff --git a/apps/user/repositories/user.repository.ts b/apps/user/repositories/user.repository.ts index d6cd99c1e..0eea50490 100644 --- a/apps/user/repositories/user.repository.ts +++ b/apps/user/repositories/user.repository.ts @@ -779,6 +779,25 @@ export class UserRepository { } } + async getUserDetailsByUserId(userId: string): Promise<{ + email: string; + }> { + try { + const getUserDetails = await this.prisma.user.findUnique({ + where: { + id: userId + }, + select: { + email: true + } + }); + return getUserDetails; + } catch (error) { + this.logger.error(`Error in getting user details: ${error} `); + throw error; + } + } + async getUserKeycloak(userEmails: string[]): Promise { try { const users = await this.prisma.user.findMany({ diff --git a/apps/user/src/user.controller.ts b/apps/user/src/user.controller.ts index 95bb51464..89eaf3102 100644 --- a/apps/user/src/user.controller.ts +++ b/apps/user/src/user.controller.ts @@ -214,6 +214,12 @@ export class UserController { return this.userService.updateOrgDeletedActivity(payload.orgId, payload.userId, payload.deletedBy, payload.recordType, payload.userEmail, payload.txnMetadata); } + @MessagePattern({ cmd: 'get-user-details-by-userId' }) + async getUserDetailsByUserId(payload: { userId: string }): Promise { + const { userId } = payload; + return this.userService.getUserDetails(userId); + } + @MessagePattern({ cmd: 'get-user-keycloak-id' }) async getUserKeycloakIdByEmail(userEmails: string[]): Promise { return this.userService.getUserKeycloakIdByEmail(userEmails); diff --git a/apps/user/src/user.service.ts b/apps/user/src/user.service.ts index 4065f501f..6a1bfe6b8 100644 --- a/apps/user/src/user.service.ts +++ b/apps/user/src/user.service.ts @@ -1217,6 +1217,17 @@ export class UserService { } } + async getUserDetails(userId: string): Promise { + try { + const getUserDetails = await this.userRepository.getUserDetailsByUserId(userId); + const userEmail = getUserDetails.email; + return userEmail; + } catch (error) { + this.logger.error(`In get user details by user Id : ${JSON.stringify(error)}`); + throw error; + } + } + async getUserKeycloakIdByEmail(userEmails: string[]): Promise { try { diff --git a/apps/verification/src/repositories/verification.repository.ts b/apps/verification/src/repositories/verification.repository.ts index afe97e8d1..1b67e3a2a 100644 --- a/apps/verification/src/repositories/verification.repository.ts +++ b/apps/verification/src/repositories/verification.repository.ts @@ -1,12 +1,12 @@ import { ResponseMessages } from '@credebl/common/response-messages'; import { PrismaService } from '@credebl/prisma-service'; -import { Injectable, Logger, NotFoundException } from '@nestjs/common'; +import { ConflictException, Injectable, Logger, NotFoundException } from '@nestjs/common'; // eslint-disable-next-line camelcase import { agent_invitations, org_agents, organisation, platform_config, presentations } from '@prisma/client'; import { IProofPresentation, IProofRequestSearchCriteria } from '../interfaces/verification.interface'; import { IUserRequest } from '@credebl/user-request/user-request.interface'; import { IProofPresentationsListCount, IVerificationRecords } from '@credebl/common/interfaces/verification.interface'; -import { SortValue } from '@credebl/enum/enum'; +import { PrismaTables, SortValue } from '@credebl/enum/enum'; @Injectable() export class VerificationRepository { @@ -218,6 +218,20 @@ export class VerificationRepository { async deleteVerificationRecordsByOrgId(orgId: string): Promise { try { + const tablesToCheck = [`${PrismaTables.ECOSYSTEM_ORGS}`]; + + const referenceCounts = await Promise.all( + tablesToCheck.map((table) => this.prisma[table].count({ where: { orgId } })) + ); + + const referencedTables = referenceCounts + .map((count, index) => (0 < count ? tablesToCheck[index] : null)) + .filter(Boolean); + + if (0 < referencedTables.length) { + throw new ConflictException(`Organization ID ${orgId} is referenced in the following table(s): ${referencedTables.join(', ')}, first you have to remove ecosystem data`); + } + return await this.prisma.$transaction(async (prisma) => { const recordsToDelete = await this.prisma.presentations.findMany({ diff --git a/libs/common/src/interfaces/ecosystem.interface.ts b/libs/common/src/interfaces/ecosystem.interface.ts index aba5bf110..3e35440e3 100644 --- a/libs/common/src/interfaces/ecosystem.interface.ts +++ b/libs/common/src/interfaces/ecosystem.interface.ts @@ -1,3 +1,5 @@ +import { Prisma } from "@prisma/client"; + interface EcosystemRole { id: string; name: string; @@ -50,4 +52,10 @@ interface Ecosystem { createdBy: string; type?: string; } - \ No newline at end of file + + export interface IEcosystemDataDeletionResults { + deletedEcosystemUsers: Prisma.BatchPayload; + deleteEndorsementTransactions: Prisma.BatchPayload; + deletedEcosystemOrgs: Prisma.BatchPayload; + deletedEcosystems: Prisma.BatchPayload; + } \ No newline at end of file diff --git a/libs/common/src/interfaces/organization.interface.ts b/libs/common/src/interfaces/organization.interface.ts index 29105d304..829f79737 100644 --- a/libs/common/src/interfaces/organization.interface.ts +++ b/libs/common/src/interfaces/organization.interface.ts @@ -93,6 +93,14 @@ export interface IOrganizationDashboard { clientId: string; clientSecret: string; } + + export interface IOrgData extends IDeleteOrganization{ + registrationNumber: string, + country: string, + city: string, + state: string + } + export interface IOrgActivityCount { verificationRecordsCount: number; issuanceRecordsCount: number; diff --git a/libs/common/src/response-messages/index.ts b/libs/common/src/response-messages/index.ts index d2baa3241..6599eaa7e 100644 --- a/libs/common/src/response-messages/index.ts +++ b/libs/common/src/response-messages/index.ts @@ -274,7 +274,7 @@ export const ResponseMessages = { connectionNotFound: 'Connection not found', agentEndPointNotFound: 'agentEndPoint Not Found', agentUrlNotFound: 'agent url not found', - connectionRecordNotFound: 'Connection records not found' + connectionRecordNotFound: 'Connection records does not exists' } }, issuance: { @@ -321,7 +321,7 @@ export const ResponseMessages = { missingRequestId: 'Param requestId is missing from the request.', cachedData: 'Cached data does not exist', cachedfileData: 'Cached file data does not exist', - issuanceRecordsNotFound: 'Issuance records not found' + issuanceRecordsNotFound: 'Issuance records does not exists' } }, verification: { @@ -346,7 +346,8 @@ export const ResponseMessages = { platformConfigNotFound: 'Platform config not found', batchEmailSend: 'Unable to send email in batches', emailSend: 'Unable to send email to the user', - verificationRecordsNotFound: 'Verification records not found' + verificationRecordsNotFound: 'Verification records does not exists', + removeVerificationData: 'First you have to remove verification data' } }, ecosystem: { @@ -365,6 +366,7 @@ export const ResponseMessages = { submit: 'Endorsement request submitted to ledger', invitationReject: 'Ecosystem invitation rejected', invitationAccept: 'Ecosystem invitation accepted successfully', + deleteEcosystemMember: 'You are deleted as a ecosystem member', fetchEndorsors: 'Endorser transactions fetched successfully', DeclineEndorsementTransaction: 'Endorsement request declined', AutoEndorsementTransaction: 'The flag for transactions has been successfully set', @@ -424,7 +426,11 @@ export const ResponseMessages = { invalidMessage: 'Invalid transaction details. Missing "message" property.', invalidTransactionMessage: 'Invalid transaction details', ecosystemRoleNotMatch: 'Ecosystem role not match', - orgEcoIdRequired: 'OrgId & EcosystemId is required' + orgEcoIdRequired: 'OrgId & EcosystemId is required', + ecosystemMembersNotExists: 'Ecosystem members does not exists', + notAbleToDeleteEcosystem: 'You cannot delete the ecosystem, because you are the ecosystem lead', + ecosystemNotExists: 'Ecosystem does not exists', + removeEcosystemData: 'First you have to remove ecosystem data' } }, bulkIssuance: { diff --git a/libs/enum/src/enum.ts b/libs/enum/src/enum.ts index 86ec84164..9de93b4ea 100644 --- a/libs/enum/src/enum.ts +++ b/libs/enum/src/enum.ts @@ -150,12 +150,12 @@ export enum PromiseResult { export enum PrismaTables { PRESENTATIONS = 'presentations', CREDENTIALS = 'credentials', + ECOSYSTEM_ORGS = 'ecosystem_orgs', ORG_AGENTS = 'org_agents', ORG_DIDS = 'org_dids', AGENT_INVITATIONS = 'agent_invitations', CONNECTIONS = 'connections', ECOSYSTEM_INVITATIONS = 'ecosystem_invitations', - ECOSYSTEM_ORGS = 'ecosystem_orgs', FILE_UPLOAD = 'file_upload', NOTIFICATION = 'notification', }