Skip to content

Commit

Permalink
Merge pull request #1306 from ztmbuilds/fix/invite-transaction
Browse files Browse the repository at this point in the history
fix: accept invite functionality should use transactions for atomicity
  • Loading branch information
Homoakin619 authored Mar 1, 2025
2 parents bdf6865 + dad620c commit 2ca80c4
Show file tree
Hide file tree
Showing 3 changed files with 38 additions and 16 deletions.
31 changes: 19 additions & 12 deletions src/modules/invite/invite.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { HttpStatus, Injectable, InternalServerErrorException, NotFoundException
import { InviteDto } from './dto/invite.dto';
import { Invite } from './entities/invite.entity';
import { Organisation } from '@modules/organisations/entities/organisations.entity';
import { Repository } from 'typeorm';
import { Repository, EntityManager } from 'typeorm';
import { InjectRepository } from '@nestjs/typeorm';
import { v4 as uuidv4 } from 'uuid';
import { AcceptInviteDto } from './dto/accept-invite.dto';
Expand All @@ -24,7 +24,8 @@ export class InviteService {
private readonly mailerService: MailerService,
private readonly emailService: EmailService,
private readonly configService: ConfigService,
private readonly OrganisationService: OrganisationsService
private readonly OrganisationService: OrganisationsService,
private readonly entityManager: EntityManager
) {}

async getPendingInvites(): Promise<{ message: string; data: InviteDto[] }> {
Expand Down Expand Up @@ -127,17 +128,23 @@ export class InviteService {
throw new CustomHttpException(SYS_MSG.USER_NOT_REGISTERED, HttpStatus.NOT_FOUND);
}

const response = await this.OrganisationService.addOrganisationMember(invite.organisation.id, {
user_id: user.id,
return await this.entityManager.transaction(async transactionalEntityManager => {
const response = await this.OrganisationService.addOrganisationMember(
invite.organisation.id,
{
user_id: user.id,
},
transactionalEntityManager
);

if (response.status === 'success') {
invite.isAccepted = true;
await transactionalEntityManager.save(invite);
return response;
} else {
throw new CustomHttpException(SYS_MSG.MEMBER_NOT_ADDED, HttpStatus.INTERNAL_SERVER_ERROR);
}
});

if (response.status === 'success') {
invite.isAccepted = true;
await this.inviteRepository.save(invite);
return response;
} else {
throw new CustomHttpException(SYS_MSG.MEMBER_NOT_ADDED, HttpStatus.INTERNAL_SERVER_ERROR);
}
}

async sendInvitations(createInvitationDto: CreateInvitationDto): Promise<any> {
Expand Down
12 changes: 11 additions & 1 deletion src/modules/invite/tests/invite.service.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { HttpStatus, BadRequestException, NotFoundException, InternalServerErrorException } from '@nestjs/common';
import { Test, TestingModule } from '@nestjs/testing';
import { getRepositoryToken } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Repository, EntityManager } from 'typeorm';
import { Organisation } from '../../organisations/entities/organisations.entity';
import { User } from '../../user/entities/user.entity';
import { Invite } from '../entities/invite.entity';
Expand Down Expand Up @@ -34,8 +34,14 @@ describe('InviteService', () => {
let organisationService: OrganisationsService;
let configService: ConfigService;
let frontendUrl: string;
let entityManager: jest.Mocked<EntityManager>;

beforeEach(async () => {
entityManager = {
transaction: jest.fn().mockImplementation(async cb => cb(entityManager)),
save: jest.fn(),
} as unknown as jest.Mocked<EntityManager>;

const module: TestingModule = await Test.createTestingModule({
imports: [ConfigModule.forRoot()],
providers: [
Expand Down Expand Up @@ -137,6 +143,10 @@ describe('InviteService', () => {
sendMail: jest.fn(),
},
},
{
provide: EntityManager,
useValue: entityManager,
},
],
}).compile();

Expand Down
11 changes: 8 additions & 3 deletions src/modules/organisations/organisations.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ export class OrganisationsService {
return userOrganisations;
}

async addOrganisationMember(org_id: string, addMemberDto: AddMemberDto) {
async addOrganisationMember(org_id: string, addMemberDto: AddMemberDto, manager?: EntityManager) {
const organisation = await this.organisationRepository.findOneBy({ id: org_id });
if (!organisation) {
throw new CustomHttpException(SYS_MSG.ORG_NOT_FOUND, HttpStatus.NOT_FOUND);
Expand Down Expand Up @@ -227,10 +227,15 @@ export class OrganisationsService {
defaultRole.organisationId = organisation.id;
defaultRole.roleId = userRole.id;

await this.organisationUserRole.save(defaultRole);
const organisationUserRoleRepository = manager
? manager.getRepository(OrganisationUserRole)
: this.organisationUserRole;
await organisationUserRoleRepository.save(defaultRole);

user.organisations = [...user.organisations, organisation];
await this.userRepository.save(user);

const userRepository = manager ? manager.getRepository(User) : this.userRepository;
await userRepository.save(user);

const responsePayload = {
id: user.id,
Expand Down

0 comments on commit 2ca80c4

Please sign in to comment.