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 c800fc8f74..7de79e0453 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 5c45d5beb0..547c51dd63 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); } } @@ -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 1cdeb8fa2f..1731ec388a 100755 --- a/apps/api-gateway/src/ecosystem/ecosystem.service.ts +++ b/apps/api-gateway/src/ecosystem/ecosystem.service.ts @@ -63,16 +63,22 @@ 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 }); } - 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,7 +93,6 @@ export class EcosystemService { }); } - // eslint-disable-next-line camelcase async getAllEcosystemOrgsByEcosystemId( ecosystemId: string, pageDetail: IPaginationSortingDto diff --git a/apps/ecosystem/repositories/ecosystem.repository.ts b/apps/ecosystem/repositories/ecosystem.repository.ts index 01cdf0a62d..3c793d0d5a 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 } } }); @@ -368,20 +373,30 @@ export class EcosystemRepository { async updateEcosystemInvitationStatusByEmail( email: string, + orgId: string, ecosystemId: string, status: Invitation ): Promise { try { - return this.prisma.ecosystem_invitations.update({ - where: { - email_ecosystemId: { + return this.prisma.$transaction(async (tx) => { + const record = await tx.ecosystem_invitations.findFirst({ + where: { email, - ecosystemId + ecosystemId, + invitedOrg: orgId } - }, - data: { - status + }); + + 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}`); @@ -1336,7 +1351,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: { diff --git a/apps/ecosystem/src/ecosystem.controller.ts b/apps/ecosystem/src/ecosystem.controller.ts index cce5eb152b..916969a9c6 100755 --- a/apps/ecosystem/src/ecosystem.controller.ts +++ b/apps/ecosystem/src/ecosystem.controller.ts @@ -109,8 +109,14 @@ 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 fc65dcd373..74c2256694 100755 --- a/apps/ecosystem/src/ecosystem.service.ts +++ b/apps/ecosystem/src/ecosystem.service.ts @@ -229,11 +229,12 @@ 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( user.email, + orgId, ecosystemId, Invitation.PENDING ); @@ -242,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, @@ -257,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(); @@ -311,7 +321,12 @@ 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 +336,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 0000000000..63185c36b4 --- /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 ed3fa21a1a..072f87a027 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]) } diff --git a/libs/supabase/src/supabase.service.ts b/libs/supabase/src/supabase.service.ts index 83acf5d6ff..c8721fb6ba 100644 --- a/libs/supabase/src/supabase.service.ts +++ b/libs/supabase/src/supabase.service.ts @@ -3,31 +3,25 @@ import { SupabaseClient, createClient } from '@supabase/supabase-js'; @Injectable({ scope: Scope.REQUEST }) export class SupabaseService { + private readonly logger = new Logger(SupabaseService.name); + private clientInstance: SupabaseClient; + constructor() {} - private readonly logger = new Logger(SupabaseService.name); - private clientInstance: SupabaseClient; - constructor( - ) { } + // eslint-disable-next-line @typescript-eslint/no-explicit-any + getClient(): SupabaseClient { + this.logger.log('getting supabase client... '); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - getClient(): SupabaseClient { - this.logger.log('getting supabase client...'); - - if (this.clientInstance) { - this.logger.log('client exists - returning for current Scope.REQUEST'); - return this.clientInstance; - } + if (this.clientInstance) { + this.logger.log('client exists - returning for current Scope.REQUEST'); + return this.clientInstance; + } - this.logger.log('initialising new supabase client for new Scope.REQUEST'); + this.logger.log('initialising new supabase client for new Scope.REQUEST'); - this.clientInstance = createClient( - - process.env.SUPABASE_URL, - process.env.SUPABASE_KEY - ); + this.clientInstance = createClient(process.env.SUPABASE_URL, process.env.SUPABASE_KEY); - this.logger.log('auth has been set!'); + this.logger.log('auth has been set!'); - return this.clientInstance; - } + return this.clientInstance; + } }