Skip to content

Commit

Permalink
Merge pull request #350 from chaynHQ/develop
Browse files Browse the repository at this point in the history
Merge Develop onto Main
  • Loading branch information
eleanorreem authored Nov 7, 2023
2 parents f8d9be3 + c64ee27 commit b479f4e
Show file tree
Hide file tree
Showing 15 changed files with 341 additions and 84 deletions.
5 changes: 2 additions & 3 deletions src/partner-access/dtos/zapier-body.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,9 @@ export class ZapierSimplybookBodyDto {
client_email: string;

@IsString()
@IsDefined()
@IsNotEmpty()
@IsOptional()
@ApiProperty({ type: String })
client_id: string; // This is userId - not to be confused with the simplybook.client_id
client_id?: string; // This is userId - not to be confused with the simplybook.client_id

@IsString()
@IsOptional()
Expand Down
10 changes: 10 additions & 0 deletions src/partner-admin/dtos/update-partner-admin.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsBoolean, IsDefined, IsNotEmpty } from 'class-validator';

export class UpdatePartnerAdminDto {
@IsNotEmpty()
@IsBoolean()
@IsDefined()
@ApiProperty({ type: Boolean })
active: boolean;
}
15 changes: 14 additions & 1 deletion src/partner-admin/partner-admin.controller.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { Body, Controller, Post, UseGuards } from '@nestjs/common';
import { Body, Controller, Param, Patch, Post, UseGuards } from '@nestjs/common';
import { ApiBearerAuth, ApiBody, ApiOperation, ApiTags } from '@nestjs/swagger';
import { PartnerAdminEntity } from '../entities/partner-admin.entity';
import { ControllerDecorator } from '../utils/controller.decorator';
import { CreatePartnerAdminUserDto } from './dtos/create-partner-admin-user.dto';
import { CreatePartnerAdminDto } from './dtos/create-partner-admin.dto';
import { UpdatePartnerAdminDto } from './dtos/update-partner-admin.dto';
import { PartnerAdminService } from './partner-admin.service';
import { SuperAdminAuthGuard } from './super-admin-auth.guard';

Expand Down Expand Up @@ -41,4 +42,16 @@ export class PartnerAdminController {
): Promise<PartnerAdminEntity | unknown> {
return this.partnerAdminService.createPartnerAdminUser(createPartnerAdminUserDto);
}

@ApiBearerAuth('access-token')
@ApiOperation({ description: 'Update partner admin by id' })
@UseGuards(SuperAdminAuthGuard)
@Patch(':id')
@ApiBody({ type: UpdatePartnerAdminDto })
async updatePartnerAdminById(
@Param('id') partnerAdminId: string,
@Body() updatePartnerAdminDto: UpdatePartnerAdminDto,
): Promise<PartnerAdminEntity | unknown> {
return this.partnerAdminService.updatePartnerAdminById(partnerAdminId, updatePartnerAdminDto);
}
}
24 changes: 24 additions & 0 deletions src/partner-admin/partner-admin.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { UserRepository } from '../user/user.repository';
import { generateRandomString } from '../utils/utils';
import { CreatePartnerAdminUserDto } from './dtos/create-partner-admin-user.dto';
import { CreatePartnerAdminDto } from './dtos/create-partner-admin.dto';
import { UpdatePartnerAdminDto } from './dtos/update-partner-admin.dto';
import { PartnerAdminRepository } from './partner-admin.repository';

@Injectable()
Expand Down Expand Up @@ -69,4 +70,27 @@ export class PartnerAdminService {
throw error;
}
}

async updatePartnerAdminById(
partnerAdminId: string,
updatePartnerAdminDto: UpdatePartnerAdminDto,
): Promise<PartnerAdminEntity | unknown> {
try {
const partnerAdminResponse = await this.partnerAdminRepository.findOne({
where: { partnerAdminId },
});

if (!partnerAdminResponse) {
throw new HttpException('Partner admin does not exist', HttpStatus.BAD_REQUEST);
}
return this.partnerAdminRepository
.createQueryBuilder('partner_admin')
.update(PartnerAdminEntity)
.set({ active: updatePartnerAdminDto.active })
.where('partnerAdminId = :partnerAdminId', { partnerAdminId })
.returning('*')
} catch (error) {
throw error;
}
}
}
4 changes: 2 additions & 2 deletions src/user/user.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ describe('UserService', () => {
expect(user.user.email).toBe('user@email.com');
expect(user.partnerAdmin).toBeNull();

const { therapySession, partnerAdmin, partnerAdminId, ...partnerAccessData } =
const { therapySession, partnerAdmin, partnerAdminId, userId, ...partnerAccessData } =
mockPartnerAccessEntity;
expect(user.partnerAccesses).toEqual([
{ ...partnerAccessData, therapySessions: therapySession },
Expand Down Expand Up @@ -203,7 +203,7 @@ describe('UserService', () => {
...createUserDto,
partnerId: mockPartnerEntity.id,
});
const { therapySession, partnerAdmin, partnerAdminId, ...partnerAccessData } =
const { therapySession, partnerAdmin, partnerAdminId, userId, ...partnerAccessData } =
mockPartnerAccessEntity;
// Note different format for the DTO
expect(user.partnerAccesses).toEqual([
Expand Down
9 changes: 9 additions & 0 deletions src/user/user.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -385,6 +385,7 @@ export class UserService {
filters: {
email?: string;
partnerAccess?: { userId: string; featureTherapy: boolean; active: boolean };
partnerAdmin?: { partnerAdminId: string };
},
relations: Array<string>,
fields: Array<string>,
Expand All @@ -396,6 +397,14 @@ export class UserService {
query.leftJoinAndSelect('user.partnerAccess', 'partnerAccess');
}

if (relations?.indexOf('partner-admin') >= 0) {
query.leftJoinAndSelect('user.partnerAdmin', 'partnerAdmin');
}

if (filters?.partnerAdmin?.partnerAdminId === 'IS NOT NULL') {
query.andWhere('partnerAdmin.partnerAdminId IS NOT NULL');
}

if (filters.partnerAccess?.userId === 'IS NOT NULL') {
query.andWhere('partnerAccess.userId IS NOT NULL');
}
Expand Down
17 changes: 10 additions & 7 deletions src/utils/serialize.spec.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import { mockSimplybookBodyBase } from 'test/utils/mockData';
import { formatTherapySessionObject } from './serialize';
import { mockPartnerAccessEntity, mockSimplybookBodyBase } from 'test/utils/mockData';
import { serializeZapierSimplyBookDtoToTherapySessionEntity } from './serialize';

describe('Serialize', () => {
describe('formatTherapySessionObject', () => {
describe('serializeZapierSimplyBookDtoToTherapySessionEntity', () => {
it('should format object correctly when valid object is supplied', () => {
const randomString = formatTherapySessionObject(mockSimplybookBodyBase, 'partnerAccessId');
const randomString = serializeZapierSimplyBookDtoToTherapySessionEntity(
mockSimplybookBodyBase,
mockPartnerAccessEntity,
);
expect(randomString).toEqual({
action: 'UPDATED_BOOKING',
bookingCode: 'abc',
Expand All @@ -13,13 +16,13 @@ describe('Serialize', () => {
clientTimezone: 'Europe/London',
completedAt: null,
endDateTime: new Date('2022-09-12T08:30:00+0000'),
partnerAccessId: 'partnerAccessId',
partnerAccessId: 'pa1',
rescheduledFrom: null,
serviceName: 'bloom therapy',
serviceProviderEmail: 'therapist@test.com',
serviceProviderName: 'therapist@test.com',
serviceProviderName: 'Therapist name',
startDateTime: new Date('2022-09-12T07:30:00+0000'),
userId: mockSimplybookBodyBase.client_id,
userId: null,
});
});
});
Expand Down
10 changes: 5 additions & 5 deletions src/utils/serialize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,25 +147,25 @@ export const formatPartnerObject = (partnerObject: PartnerEntity): IPartner => {
};
};

export const formatTherapySessionObject = (
export const serializeZapierSimplyBookDtoToTherapySessionEntity = (
therapySession: ZapierSimplybookBodyDto,
partnerAccessId: string,
partnerAccess: PartnerAccessEntity,
): Partial<TherapySessionEntity> => {
return {
action: therapySession.action,
bookingCode: therapySession.booking_code,
clientEmail: therapySession.client_email,
clientTimezone: therapySession.client_timezone,
serviceName: therapySession.service_name,
serviceProviderName: therapySession.service_provider_email,
serviceProviderName: therapySession.service_provider_name,
serviceProviderEmail: therapySession.service_provider_email,
startDateTime: new Date(therapySession.start_date_time),
endDateTime: new Date(therapySession.end_date_time),
cancelledAt: null,
rescheduledFrom: null,
completedAt: null,
partnerAccessId,
userId: therapySession.client_id,
partnerAccessId: partnerAccess.id,
userId: partnerAccess.userId,
};
};

Expand Down
4 changes: 2 additions & 2 deletions src/webhooks/webhooks.controller.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { createMock } from '@golevelup/ts-jest';
import { HttpException, HttpStatus } from '@nestjs/common';
import { Test, TestingModule } from '@nestjs/testing';
import { mockSimplybookBodyBase } from 'test/utils/mockData';
import { mockSimplybookBodyBase, mockTherapySessionEntity } from 'test/utils/mockData';
import { mockWebhooksServiceMethods } from 'test/utils/mockedServices';
import { WebhooksController } from './webhooks.controller';
import { WebhooksService } from './webhooks.service';
Expand All @@ -23,7 +23,7 @@ describe('AppController', () => {
it('updatePartnerAccessTherapy should return successful if service returns successful', async () => {
await expect(
webhooksController.updatePartnerAccessTherapy(mockSimplybookBodyBase),
).resolves.toBe('Successful');
).resolves.toBe(mockTherapySessionEntity);
});
it('updatePartnerAccessTherapy should error if service returns errors', async () => {
jest
Expand Down
5 changes: 3 additions & 2 deletions src/webhooks/webhooks.controller.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Body, Controller, Logger, Post, UseGuards } from '@nestjs/common';
import { ApiBody, ApiTags } from '@nestjs/swagger';
import { EventLogEntity } from 'src/entities/event-log.entity';
import { TherapySessionEntity } from 'src/entities/therapy-session.entity';
import { ControllerDecorator } from 'src/utils/controller.decorator';
import { WebhookCreateEventLogDto } from 'src/webhooks/dto/webhook-create-event-log.dto';
import { ZapierSimplybookBodyDto } from '../partner-access/dtos/zapier-body.dto';
Expand All @@ -20,15 +21,15 @@ export class WebhooksController {
@ApiBody({ type: ZapierSimplybookBodyDto })
async updatePartnerAccessTherapy(
@Body() simplybookBodyDto: ZapierSimplybookBodyDto,
): Promise<string> {
): Promise<TherapySessionEntity> {
const updatedPartnerAccessTherapy = await this.webhooksService.updatePartnerAccessTherapy(
simplybookBodyDto,
);
this.logger.log(
`Updated partner access therapy: ${updatedPartnerAccessTherapy.clientEmail} - ${updatedPartnerAccessTherapy.bookingCode}`,
);

return 'Successful';
return updatedPartnerAccessTherapy;
}

@UseGuards(ZapierAuthGuard)
Expand Down
86 changes: 79 additions & 7 deletions src/webhooks/webhooks.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -425,6 +425,83 @@ describe('WebhooksService', () => {
expect(userFindOneRepoSpy).toBeCalled();
});

it('when creating a new therapy session and the client_id/ userId is not provided, it should get userId from previous entry', async () => {
const findTherapySessionSpy = jest
.spyOn(mockedTherapySessionRepository, 'findOne')
.mockImplementationOnce(async () => {
return { ...mockTherapySessionEntity, clientEmail: mockSimplybookBodyBase.client_email };
});
const findPartnerAccessSpy = jest
.spyOn(mockedPartnerAccessRepository, 'find')
.mockImplementationOnce(async () => {
return [{ ...mockPartnerAccessEntity, userId: 'userId1' }];
});
const newTherapySession = await service.updatePartnerAccessTherapy({
...mockSimplybookBodyBase,
client_id: undefined,
action: SIMPLYBOOK_ACTION_ENUM.NEW_BOOKING,
});

expect(newTherapySession).toEqual({
...mockTherapySessionEntity,
clientEmail: mockSimplybookBodyBase.client_email,
bookingCode: mockSimplybookBodyBase.booking_code,
action: SIMPLYBOOK_ACTION_ENUM.NEW_BOOKING,
startDateTime: new Date(mockSimplybookBodyBase.start_date_time),
endDateTime: new Date(mockSimplybookBodyBase.end_date_time),
});

expect(findTherapySessionSpy).toBeCalledWith({
clientEmail: mockSimplybookBodyBase.client_email,
});

expect(findPartnerAccessSpy).toBeCalledWith({
userId: 'userId1',
active: true,
});
});

it('when creating a new therapy session and the client_id/ userId is not provided and no previousTherapySession exists, it should get userId from the userDatabase', async () => {
const findTherapySessionSpy = jest
.spyOn(mockedTherapySessionRepository, 'findOne')
.mockImplementationOnce(async () => {
return null;
});
const findUserSpy = jest.spyOn(mockedUserRepository, 'findOne');

const findPartnerAccessSpy = jest
.spyOn(mockedPartnerAccessRepository, 'find')
.mockImplementationOnce(async () => {
return [{ ...mockPartnerAccessEntity, userId: 'userId1' }];
});
const newTherapySession = await service.updatePartnerAccessTherapy({
...mockSimplybookBodyBase,
client_id: undefined,
action: SIMPLYBOOK_ACTION_ENUM.NEW_BOOKING,
});

expect(newTherapySession).toEqual({
...mockTherapySessionEntity,
clientEmail: mockSimplybookBodyBase.client_email,
bookingCode: mockSimplybookBodyBase.booking_code,
action: SIMPLYBOOK_ACTION_ENUM.NEW_BOOKING,
startDateTime: new Date(mockSimplybookBodyBase.start_date_time),
endDateTime: new Date(mockSimplybookBodyBase.end_date_time),
});

expect(findTherapySessionSpy).toBeCalledWith({
clientEmail: mockSimplybookBodyBase.client_email,
});
expect(findUserSpy).toBeCalledWith({
email: mockSimplybookBodyBase.client_email,
});

expect(findPartnerAccessSpy).toBeCalledWith({
userId: 'userId1',
active: true,
});
});

it('should set a booking as cancelled when action is cancel', async () => {
await expect(
service.updatePartnerAccessTherapy({
Expand Down Expand Up @@ -526,12 +603,7 @@ describe('WebhooksService', () => {
const therapySessionFindOneSpy = jest
.spyOn(mockedTherapySessionRepository, 'findOne')
.mockImplementationOnce(async (args: any) => {
// if statement to ensure the two partner access Ids are passed
if (args.where?.filter((el) => el.partnerAccessId).length === 2) {
return { ...mockTherapySessionEntity, partnerAccessId: 'partnerAccessId2' };
} else {
throw new Error('Unable to find therapy session');
}
return { ...mockTherapySessionEntity, ...args };
});

await expect(
Expand Down Expand Up @@ -726,7 +798,7 @@ describe('WebhooksService', () => {
date: new Date(2000, 1, 1),
event: 'CHAT_MESSAGE_SENT',
id: 'eventLogId1ß',
userId: '1',
userId: 'userId1',
});
});
it('should throw 404 if email is not related to a user is incorrect', async () => {
Expand Down
Loading

0 comments on commit b479f4e

Please sign in to comment.