Skip to content

Commit 2ca80c4

Browse files
authored
Merge pull request #1306 from ztmbuilds/fix/invite-transaction
fix: accept invite functionality should use transactions for atomicity
2 parents bdf6865 + dad620c commit 2ca80c4

File tree

3 files changed

+38
-16
lines changed

3 files changed

+38
-16
lines changed

src/modules/invite/invite.service.ts

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { HttpStatus, Injectable, InternalServerErrorException, NotFoundException
22
import { InviteDto } from './dto/invite.dto';
33
import { Invite } from './entities/invite.entity';
44
import { Organisation } from '@modules/organisations/entities/organisations.entity';
5-
import { Repository } from 'typeorm';
5+
import { Repository, EntityManager } from 'typeorm';
66
import { InjectRepository } from '@nestjs/typeorm';
77
import { v4 as uuidv4 } from 'uuid';
88
import { AcceptInviteDto } from './dto/accept-invite.dto';
@@ -24,7 +24,8 @@ export class InviteService {
2424
private readonly mailerService: MailerService,
2525
private readonly emailService: EmailService,
2626
private readonly configService: ConfigService,
27-
private readonly OrganisationService: OrganisationsService
27+
private readonly OrganisationService: OrganisationsService,
28+
private readonly entityManager: EntityManager
2829
) {}
2930

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

130-
const response = await this.OrganisationService.addOrganisationMember(invite.organisation.id, {
131-
user_id: user.id,
131+
return await this.entityManager.transaction(async transactionalEntityManager => {
132+
const response = await this.OrganisationService.addOrganisationMember(
133+
invite.organisation.id,
134+
{
135+
user_id: user.id,
136+
},
137+
transactionalEntityManager
138+
);
139+
140+
if (response.status === 'success') {
141+
invite.isAccepted = true;
142+
await transactionalEntityManager.save(invite);
143+
return response;
144+
} else {
145+
throw new CustomHttpException(SYS_MSG.MEMBER_NOT_ADDED, HttpStatus.INTERNAL_SERVER_ERROR);
146+
}
132147
});
133-
134-
if (response.status === 'success') {
135-
invite.isAccepted = true;
136-
await this.inviteRepository.save(invite);
137-
return response;
138-
} else {
139-
throw new CustomHttpException(SYS_MSG.MEMBER_NOT_ADDED, HttpStatus.INTERNAL_SERVER_ERROR);
140-
}
141148
}
142149

143150
async sendInvitations(createInvitationDto: CreateInvitationDto): Promise<any> {

src/modules/invite/tests/invite.service.spec.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { HttpStatus, BadRequestException, NotFoundException, InternalServerErrorException } from '@nestjs/common';
22
import { Test, TestingModule } from '@nestjs/testing';
33
import { getRepositoryToken } from '@nestjs/typeorm';
4-
import { Repository } from 'typeorm';
4+
import { Repository, EntityManager } from 'typeorm';
55
import { Organisation } from '../../organisations/entities/organisations.entity';
66
import { User } from '../../user/entities/user.entity';
77
import { Invite } from '../entities/invite.entity';
@@ -34,8 +34,14 @@ describe('InviteService', () => {
3434
let organisationService: OrganisationsService;
3535
let configService: ConfigService;
3636
let frontendUrl: string;
37+
let entityManager: jest.Mocked<EntityManager>;
3738

3839
beforeEach(async () => {
40+
entityManager = {
41+
transaction: jest.fn().mockImplementation(async cb => cb(entityManager)),
42+
save: jest.fn(),
43+
} as unknown as jest.Mocked<EntityManager>;
44+
3945
const module: TestingModule = await Test.createTestingModule({
4046
imports: [ConfigModule.forRoot()],
4147
providers: [
@@ -137,6 +143,10 @@ describe('InviteService', () => {
137143
sendMail: jest.fn(),
138144
},
139145
},
146+
{
147+
provide: EntityManager,
148+
useValue: entityManager,
149+
},
140150
],
141151
}).compile();
142152

src/modules/organisations/organisations.service.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,7 @@ export class OrganisationsService {
197197
return userOrganisations;
198198
}
199199

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

230-
await this.organisationUserRole.save(defaultRole);
230+
const organisationUserRoleRepository = manager
231+
? manager.getRepository(OrganisationUserRole)
232+
: this.organisationUserRole;
233+
await organisationUserRoleRepository.save(defaultRole);
231234

232235
user.organisations = [...user.organisations, organisation];
233-
await this.userRepository.save(user);
236+
237+
const userRepository = manager ? manager.getRepository(User) : this.userRepository;
238+
await userRepository.save(user);
234239

235240
const responsePayload = {
236241
id: user.id,

0 commit comments

Comments
 (0)