From 36d8a3fecb772b51bb5b027c31030f68606f7077 Mon Sep 17 00:00:00 2001 From: sujitaw Date: Tue, 10 Feb 2026 15:05:05 +0530 Subject: [PATCH 1/3] wip Signed-off-by: sujitaw --- .../dtos/send-ecosystem-invitation.ts | 7 ++++ .../src/ecosystem/ecosystem.controller.ts | 3 +- .../src/ecosystem/ecosystem.service.ts | 5 +-- .../repositories/ecosystem.repository.ts | 32 ++++++++++++------- apps/ecosystem/src/ecosystem.controller.ts | 3 +- apps/ecosystem/src/ecosystem.service.ts | 4 ++- .../migration.sql | 11 +++++++ libs/prisma-service/prisma/schema.prisma | 2 +- 8 files changed, 50 insertions(+), 17 deletions(-) create mode 100644 libs/prisma-service/prisma/migrations/20260210093248_loosen_ecosystem_invitation_constraint/migration.sql diff --git a/apps/api-gateway/src/ecosystem/dtos/send-ecosystem-invitation.ts b/apps/api-gateway/src/ecosystem/dtos/send-ecosystem-invitation.ts index c800fc8f7..7de79e045 100755 --- a/apps/api-gateway/src/ecosystem/dtos/send-ecosystem-invitation.ts +++ b/apps/api-gateway/src/ecosystem/dtos/send-ecosystem-invitation.ts @@ -40,4 +40,11 @@ export class UpdateEcosystemInvitationDto { @IsString({ message: 'ecosystemId should be a string' }) @Transform(({ value }) => value?.trim()) ecosystemId: string; + + @ApiProperty({ example: '6e672a9c-64f0-4d98-b312-f578f633810b' }) + @IsUUID() + @IsNotEmpty({ message: 'OrgId is required for updating status' }) + @IsString({ message: 'OrgId should be a string' }) + @Transform(({ value }) => value?.trim()) + orgId: string; } diff --git a/apps/api-gateway/src/ecosystem/ecosystem.controller.ts b/apps/api-gateway/src/ecosystem/ecosystem.controller.ts index a6f39569a..8e06011b8 100755 --- a/apps/api-gateway/src/ecosystem/ecosystem.controller.ts +++ b/apps/api-gateway/src/ecosystem/ecosystem.controller.ts @@ -147,7 +147,8 @@ export class EcosystemController { const result = await this.ecosystemService.updateEcosystemInvitationStatus( status, reqUser.id, - updateInvitation.ecosystemId + updateInvitation.ecosystemId, + updateInvitation.orgId ); if (result) { diff --git a/apps/api-gateway/src/ecosystem/ecosystem.service.ts b/apps/api-gateway/src/ecosystem/ecosystem.service.ts index b0e679d43..3d3c3fbd3 100755 --- a/apps/api-gateway/src/ecosystem/ecosystem.service.ts +++ b/apps/api-gateway/src/ecosystem/ecosystem.service.ts @@ -62,11 +62,12 @@ export class EcosystemService { }); } - async updateEcosystemInvitationStatus(status: Invitation, reqUser: string, ecosystemId: string): Promise { + async updateEcosystemInvitationStatus(status: Invitation, reqUser: string, ecosystemId: string, orgId: string): Promise { return this.natsClient.sendNatsMessage(this.serviceProxy, 'update-ecosystem-invitation-status', { status, reqUser, - ecosystemId + ecosystemId, + orgId }); } diff --git a/apps/ecosystem/repositories/ecosystem.repository.ts b/apps/ecosystem/repositories/ecosystem.repository.ts index 8baad4a29..9e9534a9e 100755 --- a/apps/ecosystem/repositories/ecosystem.repository.ts +++ b/apps/ecosystem/repositories/ecosystem.repository.ts @@ -368,21 +368,31 @@ export class EcosystemRepository { async updateEcosystemInvitationStatusByEmail( email: string, + orgId: string, ecosystemId: string, status: Invitation ): Promise { try { - return this.prisma.ecosystem_invitations.update({ - where: { - email_ecosystemId: { - email, - ecosystemId - } - }, - data: { - status - } - }); + return this.prisma.$transaction(async (tx) => { + const record = await tx.ecosystem_invitations.findFirst({ + where: { + email, + ecosystemId, + invitedOrg: orgId + } + }); + + if (!record) { + throw new Error('Invitation not found for this specific organization'); + } + + return tx.ecosystem_invitations.update({ + where: { + id: record.id + }, + data: { status } + }); + }); } catch (error) { this.logger.error(`Error in updateEcosystemInvitationStatusByEmail: ${error.message}`); throw error; diff --git a/apps/ecosystem/src/ecosystem.controller.ts b/apps/ecosystem/src/ecosystem.controller.ts index 4f55642bf..a3924a9f5 100755 --- a/apps/ecosystem/src/ecosystem.controller.ts +++ b/apps/ecosystem/src/ecosystem.controller.ts @@ -106,8 +106,9 @@ export class EcosystemController { status: Invitation; reqUser: string; ecosystemId: string; + orgId: string; }): Promise { - return this.ecosystemService.updateEcosystemInvitationStatus(payload.status, payload.reqUser, payload.ecosystemId); + return this.ecosystemService.updateEcosystemInvitationStatus(payload.status, payload.reqUser, payload.ecosystemId, payload.orgId); } /** diff --git a/apps/ecosystem/src/ecosystem.service.ts b/apps/ecosystem/src/ecosystem.service.ts index e891cb1c5..e275c2752 100755 --- a/apps/ecosystem/src/ecosystem.service.ts +++ b/apps/ecosystem/src/ecosystem.service.ts @@ -234,6 +234,7 @@ export class EcosystemService { if (checkUser && Invitation.REJECTED === checkUser.status && ecosystemId === checkUser.ecosystemId) { const reopenedInvitation = await this.ecosystemRepository.updateEcosystemInvitationStatusByEmail( user.email, + orgId, ecosystemId, Invitation.PENDING ); @@ -311,7 +312,7 @@ export class EcosystemService { } } - async updateEcosystemInvitationStatus(status: Invitation, reqUser: string, ecosystemId: string): Promise { + async updateEcosystemInvitationStatus(status: Invitation, reqUser: string, ecosystemId: string, orgId: string): Promise { try { const user = await this.ecosystemRepository.getUserById(reqUser); @@ -321,6 +322,7 @@ export class EcosystemService { const result = await this.ecosystemRepository.updateEcosystemInvitationStatusByEmail( user.email, + orgId, ecosystemId, status ); diff --git a/libs/prisma-service/prisma/migrations/20260210093248_loosen_ecosystem_invitation_constraint/migration.sql b/libs/prisma-service/prisma/migrations/20260210093248_loosen_ecosystem_invitation_constraint/migration.sql new file mode 100644 index 000000000..63185c36b --- /dev/null +++ b/libs/prisma-service/prisma/migrations/20260210093248_loosen_ecosystem_invitation_constraint/migration.sql @@ -0,0 +1,11 @@ +/* + Warnings: + + - A unique constraint covering the columns `[email,ecosystemId,invitedOrg]` on the table `ecosystem_invitations` will be added. If there are existing duplicate values, this will fail. + +*/ +-- DropIndex +DROP INDEX "ecosystem_invitations_email_ecosystemId_key"; + +-- CreateIndex +CREATE UNIQUE INDEX "ecosystem_invitations_email_ecosystemId_invitedOrg_key" ON "ecosystem_invitations"("email", "ecosystemId", "invitedOrg"); diff --git a/libs/prisma-service/prisma/schema.prisma b/libs/prisma-service/prisma/schema.prisma index ed3fa21a1..072f87a02 100755 --- a/libs/prisma-service/prisma/schema.prisma +++ b/libs/prisma-service/prisma/schema.prisma @@ -805,7 +805,7 @@ model ecosystem_invitations { organisation organisation? @relation(fields: [invitedOrg], references: [id]) ecosystem ecosystem? @relation(fields: [ecosystemId], references: [id]) user user? @relation(fields: [userId], references: [id]) - @@unique([email, ecosystemId]) + @@unique([email, ecosystemId, invitedOrg]) } From 69c153c56ac51e96488476c966f3d9bc18a59d2f Mon Sep 17 00:00:00 2001 From: sujitaw Date: Wed, 11 Feb 2026 17:04:17 +0530 Subject: [PATCH 2/3] fix/update to allow multiple orgs of lead as member in ecosystem Signed-off-by: sujitaw --- .../src/ecosystem/ecosystem.controller.ts | 4 +- .../src/ecosystem/ecosystem.service.ts | 13 +++-- .../repositories/ecosystem.repository.ts | 47 ++++++++++--------- apps/ecosystem/src/ecosystem.service.ts | 22 +++++++-- 4 files changed, 55 insertions(+), 31 deletions(-) diff --git a/apps/api-gateway/src/ecosystem/ecosystem.controller.ts b/apps/api-gateway/src/ecosystem/ecosystem.controller.ts index 8e06011b8..b6b5511b1 100755 --- a/apps/api-gateway/src/ecosystem/ecosystem.controller.ts +++ b/apps/api-gateway/src/ecosystem/ecosystem.controller.ts @@ -104,7 +104,7 @@ export class EcosystemController { }; return res.status(HttpStatus.CREATED).json(finalResponse); } catch (error) { - if (error instanceof ConflictException || HttpStatus.CONFLICT === error.status) { + if (error instanceof ConflictException || HttpStatus.CONFLICT === error.statusCode) { return res.status(HttpStatus.CONFLICT).json({ status: HttpStatus.CONFLICT, message: error.message @@ -116,7 +116,7 @@ export class EcosystemController { message: ResponseMessages.errorMessages.serverError }; - return res.status(HttpStatus.INTERNAL_SERVER_ERROR).json(finalResponse); + return res.status(HttpStatus.OK).json(finalResponse); } } diff --git a/apps/api-gateway/src/ecosystem/ecosystem.service.ts b/apps/api-gateway/src/ecosystem/ecosystem.service.ts index 3d3c3fbd3..25a150bb9 100755 --- a/apps/api-gateway/src/ecosystem/ecosystem.service.ts +++ b/apps/api-gateway/src/ecosystem/ecosystem.service.ts @@ -62,7 +62,12 @@ export class EcosystemService { }); } - async updateEcosystemInvitationStatus(status: Invitation, reqUser: string, ecosystemId: string, orgId: string): Promise { + async updateEcosystemInvitationStatus( + status: Invitation, + reqUser: string, + ecosystemId: string, + orgId: string + ): Promise { return this.natsClient.sendNatsMessage(this.serviceProxy, 'update-ecosystem-invitation-status', { status, reqUser, @@ -71,8 +76,8 @@ export class EcosystemService { }); } - async deleteEcosystemOrgs(ecosystemId: string, userIds: string[]): Promise<{ count: number }> { - return this.natsClient.sendNatsMessage(this.serviceProxy, 'delete-ecosystem-orgs', { userIds, ecosystemId }); + async deleteEcosystemOrgs(ecosystemId: string, orgIds: string[]): Promise<{ count: number }> { + return this.natsClient.sendNatsMessage(this.serviceProxy, 'delete-ecosystem-orgs', { orgIds, ecosystemId }); } async updateEcosystemOrgStatus( @@ -87,10 +92,10 @@ export class EcosystemService { }); } - // eslint-disable-next-line camelcase async getAllEcosystemOrgsByEcosystemId( ecosystemId: string, pageDetail: IPageDetail + // eslint-disable-next-line camelcase ): Promise> { return this.natsClient.sendNatsMessage(this.serviceProxy, 'get-ecosystem-orgs', { ecosystemId, pageDetail }); } diff --git a/apps/ecosystem/repositories/ecosystem.repository.ts b/apps/ecosystem/repositories/ecosystem.repository.ts index 9e9534a9e..241d5d006 100755 --- a/apps/ecosystem/repositories/ecosystem.repository.ts +++ b/apps/ecosystem/repositories/ecosystem.repository.ts @@ -350,13 +350,18 @@ export class EcosystemRepository { } } - async getEcosystemInvitationsByEmail(email: string, ecosystemId: string): Promise { + async getEcosystemInvitationsByEmail( + email: string, + ecosystemId: string, + invitedOrg: string + ): Promise { try { return this.prisma.ecosystem_invitations.findUnique({ where: { - email_ecosystemId: { + email_ecosystemId_invitedOrg: { email, - ecosystemId + ecosystemId, + invitedOrg } } }); @@ -374,25 +379,25 @@ export class EcosystemRepository { ): Promise { try { return this.prisma.$transaction(async (tx) => { - const record = await tx.ecosystem_invitations.findFirst({ - where: { - email, - ecosystemId, - invitedOrg: orgId - } - }); - - if (!record) { - throw new Error('Invitation not found for this specific organization'); - } - - return tx.ecosystem_invitations.update({ - where: { + const record = await tx.ecosystem_invitations.findFirst({ + where: { + email, + ecosystemId, + invitedOrg: orgId + } + }); + + if (!record) { + throw new Error('Invitation not found for this specific organization'); + } + + return tx.ecosystem_invitations.update({ + where: { id: record.id - }, - data: { status } - }); - }); + }, + data: { status } + }); + }); } catch (error) { this.logger.error(`Error in updateEcosystemInvitationStatusByEmail: ${error.message}`); throw error; diff --git a/apps/ecosystem/src/ecosystem.service.ts b/apps/ecosystem/src/ecosystem.service.ts index e275c2752..b306a58da 100755 --- a/apps/ecosystem/src/ecosystem.service.ts +++ b/apps/ecosystem/src/ecosystem.service.ts @@ -229,7 +229,7 @@ export class EcosystemService { } const organization = await this.organizationRepository.getOrgProfile(orgId); const user = await this.ecosystemRepository.getUserById(organization.createdBy); - const checkUser = await this.ecosystemRepository.getEcosystemInvitationsByEmail(user.email, ecosystemId); + const checkUser = await this.ecosystemRepository.getEcosystemInvitationsByEmail(user.email, ecosystemId, orgId); if (checkUser && Invitation.REJECTED === checkUser.status && ecosystemId === checkUser.ecosystemId) { const reopenedInvitation = await this.ecosystemRepository.updateEcosystemInvitationStatusByEmail( @@ -243,13 +243,17 @@ export class EcosystemService { } } - if (checkUser?.ecosystemId === ecosystemId && checkUser.status === Invitation.PENDING) { + if ( + checkUser?.ecosystemId === ecosystemId && + checkUser.invitedOrg === orgId && + checkUser.status === Invitation.PENDING + ) { throw new RpcException({ statusCode: HttpStatus.CONFLICT, message: `Invitation already exists for org with email ${user.email}` }); } - await this.ecosystemRepository.createEcosystemInvitation({ + const invitation = await this.ecosystemRepository.createEcosystemInvitation({ email: user.email, invitedUserId: user.id, userId: reqUser, @@ -258,6 +262,11 @@ export class EcosystemService { ecosystemId, orgId }); + + if (invitation && reqUser === user.id) { + await this.updateEcosystemInvitationStatus(Invitation.ACCEPTED, reqUser, ecosystemId, orgId); + return true; + } const ecosystem = await this.ecosystemRepository.getEcosystemById(ecosystemId); const emailData = new EmailDto(); const inviteMemberTemplate = new InviteMemberToEcosystem(); @@ -312,7 +321,12 @@ export class EcosystemService { } } - async updateEcosystemInvitationStatus(status: Invitation, reqUser: string, ecosystemId: string, orgId: string): Promise { + async updateEcosystemInvitationStatus( + status: Invitation, + reqUser: string, + ecosystemId: string, + orgId: string + ): Promise { try { const user = await this.ecosystemRepository.getUserById(reqUser); From 607c86b1a95abe0c26bfb10bd185144de76510fc Mon Sep 17 00:00:00 2001 From: sujitaw Date: Wed, 11 Feb 2026 17:26:41 +0530 Subject: [PATCH 3/3] fix/code rabbit issues Signed-off-by: sujitaw --- apps/api-gateway/src/ecosystem/ecosystem.controller.ts | 2 +- apps/ecosystem/repositories/ecosystem.repository.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/api-gateway/src/ecosystem/ecosystem.controller.ts b/apps/api-gateway/src/ecosystem/ecosystem.controller.ts index b6b5511b1..6bddbb69f 100755 --- a/apps/api-gateway/src/ecosystem/ecosystem.controller.ts +++ b/apps/api-gateway/src/ecosystem/ecosystem.controller.ts @@ -312,7 +312,7 @@ export class EcosystemController { async updateEcosystemOrgStatus( @Body() updateUser: UpdateEcosystemOrgStatusDto, @Res() res: Response, - @Query('status') status: EcosystemOrgStatus = EcosystemOrgStatus.INACTIVE + @Query('status', new ParseEnumPipe(EcosystemOrgStatus)) status: EcosystemOrgStatus ): Promise { const result = await this.ecosystemService.updateEcosystemOrgStatus( updateUser.ecosystemId, diff --git a/apps/ecosystem/repositories/ecosystem.repository.ts b/apps/ecosystem/repositories/ecosystem.repository.ts index 241d5d006..f287e4872 100755 --- a/apps/ecosystem/repositories/ecosystem.repository.ts +++ b/apps/ecosystem/repositories/ecosystem.repository.ts @@ -1348,7 +1348,7 @@ export class EcosystemRepository { return { totalPages, data }; } - async getEcosystemByRole(userId: string, role: string): Promise<{ ecosystemRole: { name: string } }> { + async getEcosystemByRole(userId: string, role: string): Promise<{ ecosystemRole: { name: string } } | null> { try { return this.prisma.ecosystem_orgs.findFirst({ where: {