From 9eafab56272c0bedbd6188c3267dd9ccbaf02c5e Mon Sep 17 00:00:00 2001 From: G4EVA-dev Date: Fri, 28 Feb 2025 03:42:49 +0100 Subject: [PATCH 01/19] feat: install dependencies setup server and start docker compose --- docker-compose.yml | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 docker-compose.yml diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 000000000..d2b327baf --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,36 @@ +services: + postgres: + container_name: postgres-boiler + image: postgres:latest + ports: + - '5433:5432' + environment: + - POSTGRES_USER=${DB_USERNAME} + - POSTGRES_PASSWORD=${DB_PASSWORD} + - POSTGRES_DB=${DB_NAME} + volumes: + - ./data/db:/var/lib/postgresql/data + restart: always + + adminer: + image: adminer + container_name: adminer-boiler + ports: + - '8080:8080' + restart: always + depends_on: + - postgres + + redis: + image: redis:latest + container_name: redis-boiler + ports: + - '6379:6379' + command: ['redis-server', '--appendonly', 'yes'] + volumes: + - redis_data:/data + restart: always + +volumes: + data: + redis_data: From c15ea6da578086c934fe0f069b43b11766425dcb Mon Sep 17 00:00:00 2001 From: G4EVA-dev Date: Fri, 28 Feb 2025 11:14:21 +0100 Subject: [PATCH 02/19] feat: Update the Controller to accept pagination parameters --- src/modules/invite/invite.controller.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/modules/invite/invite.controller.ts b/src/modules/invite/invite.controller.ts index fbbf8e1f0..f096c2afd 100644 --- a/src/modules/invite/invite.controller.ts +++ b/src/modules/invite/invite.controller.ts @@ -10,6 +10,7 @@ import { Body, Res, UseGuards, + Query, // Added this import } from '@nestjs/common'; import { ApiBearerAuth, ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'; import { InviteService } from './invite.service'; @@ -25,12 +26,14 @@ import { SendInvitationsResponseDto } from './dto/send-invitations-response.dto' import { AcceptInviteDto } from './dto/accept-invite.dto'; import { OwnershipGuard } from '@guards/authorization.guard'; import * as SYS_MSG from '@shared/constants/SystemMessages'; +import { PaginationQueryDto } from './dto/pagination-query.dto'; // Added this import @ApiBearerAuth() @ApiTags('Organisation Invites') @Controller('organizations') export class InviteController { constructor(private readonly inviteService: InviteService) {} + // invite.controller.ts - Changes to the findAllInvitations method @ApiOperation({ summary: 'Get All Invitations' }) @ApiResponse({ status: 200, @@ -43,10 +46,11 @@ export class InviteController { type: ErrorResponseDto, }) @Get('invites') - async findAllInvitations() { - const allInvites = await this.inviteService.findAllInvitations(); + async findAllInvitations(@Query() paginationQuery: PaginationQueryDto) { + const allInvites = await this.inviteService.findAllInvitations(paginationQuery.page, paginationQuery.limit); return allInvites; } + @ApiOperation({ summary: 'Get All Pending Invitations' }) @ApiResponse({ status: 200, From 6af0f70e1676fd76b060c4736065b5b60e626c13 Mon Sep 17 00:00:00 2001 From: G4EVA-dev Date: Fri, 28 Feb 2025 11:19:11 +0100 Subject: [PATCH 03/19] feat: Update the Response DTO to include pagination metadata --- .../invite/dto/all-invitations-response.dto.ts | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/modules/invite/dto/all-invitations-response.dto.ts b/src/modules/invite/dto/all-invitations-response.dto.ts index 8631f00d5..971c59d42 100644 --- a/src/modules/invite/dto/all-invitations-response.dto.ts +++ b/src/modules/invite/dto/all-invitations-response.dto.ts @@ -1,13 +1,25 @@ +// dto/all-invitations-response.dto.ts import { InviteDto } from './invite.dto'; import { ApiProperty } from '@nestjs/swagger'; +export class PaginatedInvitationsDto { + @ApiProperty({ type: [InviteDto] }) + invitations: InviteDto[]; + + @ApiProperty({ example: 250, description: 'Total number of invitations' }) + total: number; +} + export class FindAllInvitationsResponseDto { + @ApiProperty({ example: 'success' }) + status: string; + @ApiProperty({ example: 200 }) status_code: number; - @ApiProperty({ example: 'Successfully fetched invites' }) + @ApiProperty({ example: 'Invitations retrieved successfully' }) message: string; - @ApiProperty({ type: [InviteDto] }) - data: InviteDto[]; + @ApiProperty({ type: PaginatedInvitationsDto }) + data: PaginatedInvitationsDto; } From 812b54c47aafbadcd2122f79703dbbadc2f020a1 Mon Sep 17 00:00:00 2001 From: G4EVA-dev Date: Fri, 28 Feb 2025 11:20:16 +0100 Subject: [PATCH 04/19] feat: Update the Controller to accept pagination paramerers --- src/modules/invite/invite.service.ts | 31 +++++++++++++++++++++++----- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/src/modules/invite/invite.service.ts b/src/modules/invite/invite.service.ts index 6df36001d..7fbffab59 100644 --- a/src/modules/invite/invite.service.ts +++ b/src/modules/invite/invite.service.ts @@ -51,11 +51,28 @@ export class InviteService { throw new InternalServerErrorException(`Internal server error: ${error.message}`); } } - async findAllInvitations(): Promise<{ status_code: number; message: string; data: InviteDto[] }> { + // invite.service.ts - Changes to the findAllInvitations method + async findAllInvitations( + page: number = 1, + limit: number = 10 + ): Promise<{ + status: string; + status_code: number; + message: string; + data: { invitations: InviteDto[]; total: number }; + }> { try { - const invites = await this.inviteRepository.find(); + // Calculate skip value for pagination + const skip = (page - 1) * limit; - const allInvites: InviteDto[] = invites.map(invite => { + // Get paginated invites + const [invites, total] = await this.inviteRepository.findAndCount({ + skip, + take: limit, + }); + + // Map to DTOs + const invitesDto: InviteDto[] = invites.map(invite => { return { token: invite.token, id: invite.id, @@ -67,9 +84,13 @@ export class InviteService { }); const responseData = { + status: 'success', status_code: HttpStatus.OK, - message: 'Successfully fetched invites', - data: allInvites, + message: 'Invitations retrieved successfully', + data: { + invitations: invitesDto, + total, + }, }; return responseData; From 3bd53d222467d5a2d8439db5f5c46d0a58eecf7e Mon Sep 17 00:00:00 2001 From: G4EVA-dev Date: Fri, 28 Feb 2025 11:20:52 +0100 Subject: [PATCH 05/19] feat: create a Pagination DTO to handle pagination parameters --- .../invite/dto/pagination-query.dto.ts | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 src/modules/invite/dto/pagination-query.dto.ts diff --git a/src/modules/invite/dto/pagination-query.dto.ts b/src/modules/invite/dto/pagination-query.dto.ts new file mode 100644 index 000000000..cdb7c2416 --- /dev/null +++ b/src/modules/invite/dto/pagination-query.dto.ts @@ -0,0 +1,29 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsOptional, IsInt, Min } from 'class-validator'; +import { Type } from 'class-transformer'; + +export class PaginationQueryDto { + @ApiProperty({ + description: 'Page number (starts at 1)', + required: false, + default: 1, + type: Number, + }) + @IsOptional() + @Type(() => Number) + @IsInt() + @Min(1) + page?: number = 1; + + @ApiProperty({ + description: 'Number of items per page', + required: false, + default: 10, + type: Number, + }) + @IsOptional() + @Type(() => Number) + @IsInt() + @Min(1) + limit?: number = 10; +} From 695aa663a857f2757da21d85435593b2fa05c7c1 Mon Sep 17 00:00:00 2001 From: G4EVA-dev Date: Fri, 28 Feb 2025 12:06:06 +0100 Subject: [PATCH 06/19] feat: write test for invite service --- .../invite/tests/test-invite.service.spec.ts | 166 ++++++++++++++++++ 1 file changed, 166 insertions(+) create mode 100644 src/modules/invite/tests/test-invite.service.spec.ts diff --git a/src/modules/invite/tests/test-invite.service.spec.ts b/src/modules/invite/tests/test-invite.service.spec.ts new file mode 100644 index 000000000..cb1685074 --- /dev/null +++ b/src/modules/invite/tests/test-invite.service.spec.ts @@ -0,0 +1,166 @@ +// invite.service.spec.ts +import { Test, TestingModule } from '@nestjs/testing'; +import { InviteService } from '../invite.service'; +import { getRepositoryToken } from '@nestjs/typeorm'; +import { Invite } from '../entities/invite.entity'; +import { Organisation } from '@modules/organisations/entities/organisations.entity'; +import { User } from '@modules/user/entities/user.entity'; +import { HttpStatus, InternalServerErrorException } from '@nestjs/common'; +import { MailerService } from '@nestjs-modules/mailer'; +import { EmailService } from '@modules/email/email.service'; +import { ConfigService } from '@nestjs/config'; +import { OrganisationsService } from '@modules/organisations/organisations.service'; +import { Repository } from 'typeorm'; + +describe('InviteService', () => { + let service: InviteService; + let inviteRepository: jest.Mocked>; + + const mockInviteRepository = { + find: jest.fn(), + findOne: jest.fn(), + findAndCount: jest.fn(), + create: jest.fn(), + save: jest.fn(), + }; + + const mockOrganisationRepository = { + findOne: jest.fn(), + }; + + const mockUserRepository = { + findOne: jest.fn(), + }; + + const mockMailerService = { + sendMail: jest.fn(), + }; + + const mockEmailService = { + getTemplate: jest.fn(), + }; + + const mockConfigService = { + get: jest.fn(), + }; + + const mockOrganisationsService = { + addOrganisationMember: jest.fn(), + }; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [ + InviteService, + { provide: getRepositoryToken(Invite), useValue: mockInviteRepository }, + { provide: getRepositoryToken(Organisation), useValue: mockOrganisationRepository }, + { provide: getRepositoryToken(User), useValue: mockUserRepository }, + { provide: MailerService, useValue: mockMailerService }, + { provide: EmailService, useValue: mockEmailService }, + { provide: ConfigService, useValue: mockConfigService }, + { provide: OrganisationsService, useValue: mockOrganisationsService }, + ], + }).compile(); + + service = module.get(InviteService); + inviteRepository = module.get(getRepositoryToken(Invite)); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); + + describe('findAllInvitations', () => { + it('should return paginated invitations with default parameters', async () => { + // Mock invites data + const mockInvites = [ + { + id: '1', + token: 'token1', + isAccepted: false, + isGeneric: true, + organisation: { id: 'org1', name: 'Org 1' }, + email: 'test1@example.com', + }, + ]; + + // Setup repository mock + mockInviteRepository.findAndCount.mockResolvedValue([mockInvites, 1]); + + // Call service method + const result = await service.findAllInvitations(); + + // Verify repository was called with correct params + expect(mockInviteRepository.findAndCount).toHaveBeenCalledWith({ + skip: 0, + take: 10, + }); + + // Verify response structure + expect(result).toEqual({ + status: 'success', + status_code: HttpStatus.OK, + message: 'Invitations retrieved successfully', + data: { + invitations: expect.any(Array), + total: 1, + }, + }); + expect(result.data.invitations).toHaveLength(1); + }); + + it('should return paginated invitations with custom parameters', async () => { + // Mock invites data + const mockInvites = [ + { + id: '1', + token: 'token1', + isAccepted: false, + isGeneric: true, + organisation: { id: 'org1', name: 'Org 1' }, + email: 'test1@example.com', + }, + { + id: '2', + token: 'token2', + isAccepted: true, + isGeneric: false, + organisation: { id: 'org2', name: 'Org 2' }, + email: 'test2@example.com', + }, + ]; + + // Setup repository mock + mockInviteRepository.findAndCount.mockResolvedValue([mockInvites, 10]); + + // Call service method with custom page and limit + const result = await service.findAllInvitations(2, 5); + + // Verify repository was called with correct params + expect(mockInviteRepository.findAndCount).toHaveBeenCalledWith({ + skip: 5, + take: 5, + }); + + // Verify response structure + expect(result).toEqual({ + status: 'success', + status_code: HttpStatus.OK, + message: 'Invitations retrieved successfully', + data: { + invitations: expect.any(Array), + total: 10, + }, + }); + expect(result.data.invitations).toHaveLength(2); + }); + + it('should handle errors and throw InternalServerErrorException', async () => { + // Setup repository mock to throw error + mockInviteRepository.findAndCount.mockRejectedValue(new Error('Database error')); + + // Verify error handling + await expect(service.findAllInvitations()).rejects.toThrow(InternalServerErrorException); + }); + }); +}); From 1d458ab98271cae4806ba82bb7e47e584e5d35c1 Mon Sep 17 00:00:00 2001 From: G4EVA-dev Date: Fri, 28 Feb 2025 12:48:02 +0100 Subject: [PATCH 07/19] fix: Update the repository mock in beforeEach block to include the findAndCount method --- src/modules/invite/tests/invite.service.spec.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/modules/invite/tests/invite.service.spec.ts b/src/modules/invite/tests/invite.service.spec.ts index 491321194..f5b685030 100644 --- a/src/modules/invite/tests/invite.service.spec.ts +++ b/src/modules/invite/tests/invite.service.spec.ts @@ -52,6 +52,7 @@ describe('InviteService', () => { save: jest.fn(), findOneBy: jest.fn(), update: jest.fn(), + findAndCount: jest.fn(), // Added this line because of pagination }, }, { From 0dae544eeb620b91a2ca62ecc495bb1344710b78 Mon Sep 17 00:00:00 2001 From: G4EVA-dev Date: Fri, 28 Feb 2025 13:58:28 +0100 Subject: [PATCH 08/19] fix: add createDate and updateDate in entity --- src/modules/invite/entities/invite.entity.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/modules/invite/entities/invite.entity.ts b/src/modules/invite/entities/invite.entity.ts index f925dfd68..d1a6ad505 100644 --- a/src/modules/invite/entities/invite.entity.ts +++ b/src/modules/invite/entities/invite.entity.ts @@ -1,4 +1,4 @@ -import { Entity, Column, ManyToOne } from 'typeorm'; +import { Entity, Column, ManyToOne, CreateDateColumn, UpdateDateColumn } from 'typeorm'; // Add imports for createDate and UpdateDate here import { AbstractBaseEntity } from '../../../entities/base.entity'; import { Organisation } from '../../organisations/entities/organisations.entity'; @@ -18,4 +18,10 @@ export class Invite extends AbstractBaseEntity { @ManyToOne(() => Organisation, organisation => organisation.invites, { nullable: false, onDelete: 'CASCADE' }) organisation: Organisation; + + @CreateDateColumn() + created_at: Date; + + @UpdateDateColumn() + updated_at: Date; } From a9b23d5e950787018ef27e61ffde659e738ccdf7 Mon Sep 17 00:00:00 2001 From: G4EVA-dev Date: Fri, 28 Feb 2025 13:59:19 +0100 Subject: [PATCH 09/19] fix: InternalServerErrorException: Internal server error --- .../invite/tests/invite.service.spec.ts | 23 +++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/src/modules/invite/tests/invite.service.spec.ts b/src/modules/invite/tests/invite.service.spec.ts index f5b685030..33c7e7496 100644 --- a/src/modules/invite/tests/invite.service.spec.ts +++ b/src/modules/invite/tests/invite.service.spec.ts @@ -154,15 +154,28 @@ describe('InviteService', () => { }); it('should fetch all invites', async () => { - jest.spyOn(repository, 'find').mockResolvedValue(mockInvites); + // Create a copy of mockInvites without the timestamp properties for comparison + const expectedInvites = mockInvites.map(invite => { + const { created_at, updated_at, ...rest } = invite; + return rest; + }); + + jest.spyOn(repository, 'findAndCount').mockResolvedValue([mockInvites, mockInvites.length]); const result = await service.findAllInvitations(); expect(result).toEqual({ - status_code: 200, - message: 'Successfully fetched invites', - data: mockInvitesResponse, + status: 'success', + status_code: HttpStatus.OK, + message: 'Invitations retrieved successfully', + data: { + invitations: expectedInvites, + total: mockInvites.length, + }, }); + + // Verify that findAndCount was called + expect(repository.findAndCount).toHaveBeenCalled(); }); it('should throw an internal server error if an exception occurs', async () => { @@ -186,6 +199,8 @@ describe('InviteService', () => { isGeneric: invite.isGeneric, organisation: invite.organisation, email: invite.email, + created_at: new Date(), + updated_at: new Date(), })), }); }); From e24deb2e657d059895fcedf3e265ec40c3f3555d Mon Sep 17 00:00:00 2001 From: G4EVA-dev Date: Fri, 28 Feb 2025 22:57:29 +0100 Subject: [PATCH 10/19] fix: conflict in gitignore --- .gitignore | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/.gitignore b/.gitignore index b5f15abc6..f8292fc2f 100644 --- a/.gitignore +++ b/.gitignore @@ -407,25 +407,10 @@ todo.txt # Docker compose docker-compose.yml data/ -<<<<<<< HEAD -data/ -docker-compose.yml -package-lock.json -.dev.env - - -package-lock.json -docker-compose.yml -data/ -.dev.env - - -======= # Docker compose docker-compose.yml data/ ->>>>>>> d080450 (chore: created a docker-compose.yml file and updated the gitignore) data/ docker-compose.yml From c5691679c962ec4bf18d35b28a670c36e7f276db Mon Sep 17 00:00:00 2001 From: G4EVA-dev Date: Fri, 28 Feb 2025 23:11:12 +0100 Subject: [PATCH 11/19] fix: remove docker-compose.yml --- docker-compose.yml | 36 ------------------------------------ 1 file changed, 36 deletions(-) delete mode 100644 docker-compose.yml diff --git a/docker-compose.yml b/docker-compose.yml deleted file mode 100644 index d2b327baf..000000000 --- a/docker-compose.yml +++ /dev/null @@ -1,36 +0,0 @@ -services: - postgres: - container_name: postgres-boiler - image: postgres:latest - ports: - - '5433:5432' - environment: - - POSTGRES_USER=${DB_USERNAME} - - POSTGRES_PASSWORD=${DB_PASSWORD} - - POSTGRES_DB=${DB_NAME} - volumes: - - ./data/db:/var/lib/postgresql/data - restart: always - - adminer: - image: adminer - container_name: adminer-boiler - ports: - - '8080:8080' - restart: always - depends_on: - - postgres - - redis: - image: redis:latest - container_name: redis-boiler - ports: - - '6379:6379' - command: ['redis-server', '--appendonly', 'yes'] - volumes: - - redis_data:/data - restart: always - -volumes: - data: - redis_data: From 8174d97890371adef44ccde1612ddd71205a1880 Mon Sep 17 00:00:00 2001 From: G4EVA-dev Date: Fri, 28 Feb 2025 23:28:01 +0100 Subject: [PATCH 12/19] fix: remove useless comments --- src/modules/invite/dto/all-invitations-response.dto.ts | 1 - src/modules/invite/entities/invite.entity.ts | 2 +- src/modules/invite/invite.service.ts | 2 +- src/modules/invite/tests/invite.service.spec.ts | 2 +- 4 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/modules/invite/dto/all-invitations-response.dto.ts b/src/modules/invite/dto/all-invitations-response.dto.ts index 971c59d42..ef06d9011 100644 --- a/src/modules/invite/dto/all-invitations-response.dto.ts +++ b/src/modules/invite/dto/all-invitations-response.dto.ts @@ -1,4 +1,3 @@ -// dto/all-invitations-response.dto.ts import { InviteDto } from './invite.dto'; import { ApiProperty } from '@nestjs/swagger'; diff --git a/src/modules/invite/entities/invite.entity.ts b/src/modules/invite/entities/invite.entity.ts index d1a6ad505..c2d075d7d 100644 --- a/src/modules/invite/entities/invite.entity.ts +++ b/src/modules/invite/entities/invite.entity.ts @@ -1,4 +1,4 @@ -import { Entity, Column, ManyToOne, CreateDateColumn, UpdateDateColumn } from 'typeorm'; // Add imports for createDate and UpdateDate here +import { Entity, Column, ManyToOne, CreateDateColumn, UpdateDateColumn } from 'typeorm'; import { AbstractBaseEntity } from '../../../entities/base.entity'; import { Organisation } from '../../organisations/entities/organisations.entity'; diff --git a/src/modules/invite/invite.service.ts b/src/modules/invite/invite.service.ts index 7fbffab59..8ecaceafd 100644 --- a/src/modules/invite/invite.service.ts +++ b/src/modules/invite/invite.service.ts @@ -51,7 +51,7 @@ export class InviteService { throw new InternalServerErrorException(`Internal server error: ${error.message}`); } } - // invite.service.ts - Changes to the findAllInvitations method + async findAllInvitations( page: number = 1, limit: number = 10 diff --git a/src/modules/invite/tests/invite.service.spec.ts b/src/modules/invite/tests/invite.service.spec.ts index 33c7e7496..81c9af7ba 100644 --- a/src/modules/invite/tests/invite.service.spec.ts +++ b/src/modules/invite/tests/invite.service.spec.ts @@ -52,7 +52,7 @@ describe('InviteService', () => { save: jest.fn(), findOneBy: jest.fn(), update: jest.fn(), - findAndCount: jest.fn(), // Added this line because of pagination + findAndCount: jest.fn(), }, }, { From 1d18c0d667043c82debcf04598bb7099267a15ae Mon Sep 17 00:00:00 2001 From: G4EVA-dev Date: Fri, 28 Feb 2025 23:30:20 +0100 Subject: [PATCH 13/19] fix: remove useless comments --- src/modules/invite/invite.controller.ts | 4 ++-- src/modules/invite/tests/invite.service.spec.ts | 2 -- src/modules/invite/tests/test-invite.service.spec.ts | 11 ----------- 3 files changed, 2 insertions(+), 15 deletions(-) diff --git a/src/modules/invite/invite.controller.ts b/src/modules/invite/invite.controller.ts index f096c2afd..24218b305 100644 --- a/src/modules/invite/invite.controller.ts +++ b/src/modules/invite/invite.controller.ts @@ -10,7 +10,7 @@ import { Body, Res, UseGuards, - Query, // Added this import + Query, } from '@nestjs/common'; import { ApiBearerAuth, ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'; import { InviteService } from './invite.service'; @@ -26,7 +26,7 @@ import { SendInvitationsResponseDto } from './dto/send-invitations-response.dto' import { AcceptInviteDto } from './dto/accept-invite.dto'; import { OwnershipGuard } from '@guards/authorization.guard'; import * as SYS_MSG from '@shared/constants/SystemMessages'; -import { PaginationQueryDto } from './dto/pagination-query.dto'; // Added this import +import { PaginationQueryDto } from './dto/pagination-query.dto'; @ApiBearerAuth() @ApiTags('Organisation Invites') @Controller('organizations') diff --git a/src/modules/invite/tests/invite.service.spec.ts b/src/modules/invite/tests/invite.service.spec.ts index 81c9af7ba..84c5f4f53 100644 --- a/src/modules/invite/tests/invite.service.spec.ts +++ b/src/modules/invite/tests/invite.service.spec.ts @@ -154,7 +154,6 @@ describe('InviteService', () => { }); it('should fetch all invites', async () => { - // Create a copy of mockInvites without the timestamp properties for comparison const expectedInvites = mockInvites.map(invite => { const { created_at, updated_at, ...rest } = invite; return rest; @@ -174,7 +173,6 @@ describe('InviteService', () => { }, }); - // Verify that findAndCount was called expect(repository.findAndCount).toHaveBeenCalled(); }); diff --git a/src/modules/invite/tests/test-invite.service.spec.ts b/src/modules/invite/tests/test-invite.service.spec.ts index cb1685074..90f83dc78 100644 --- a/src/modules/invite/tests/test-invite.service.spec.ts +++ b/src/modules/invite/tests/test-invite.service.spec.ts @@ -72,7 +72,6 @@ describe('InviteService', () => { describe('findAllInvitations', () => { it('should return paginated invitations with default parameters', async () => { - // Mock invites data const mockInvites = [ { id: '1', @@ -84,19 +83,15 @@ describe('InviteService', () => { }, ]; - // Setup repository mock mockInviteRepository.findAndCount.mockResolvedValue([mockInvites, 1]); - // Call service method const result = await service.findAllInvitations(); - // Verify repository was called with correct params expect(mockInviteRepository.findAndCount).toHaveBeenCalledWith({ skip: 0, take: 10, }); - // Verify response structure expect(result).toEqual({ status: 'success', status_code: HttpStatus.OK, @@ -110,7 +105,6 @@ describe('InviteService', () => { }); it('should return paginated invitations with custom parameters', async () => { - // Mock invites data const mockInvites = [ { id: '1', @@ -130,19 +124,15 @@ describe('InviteService', () => { }, ]; - // Setup repository mock mockInviteRepository.findAndCount.mockResolvedValue([mockInvites, 10]); - // Call service method with custom page and limit const result = await service.findAllInvitations(2, 5); - // Verify repository was called with correct params expect(mockInviteRepository.findAndCount).toHaveBeenCalledWith({ skip: 5, take: 5, }); - // Verify response structure expect(result).toEqual({ status: 'success', status_code: HttpStatus.OK, @@ -159,7 +149,6 @@ describe('InviteService', () => { // Setup repository mock to throw error mockInviteRepository.findAndCount.mockRejectedValue(new Error('Database error')); - // Verify error handling await expect(service.findAllInvitations()).rejects.toThrow(InternalServerErrorException); }); }); From 0a76874e300b6beeb52bbaed97e8018a7cdb65c5 Mon Sep 17 00:00:00 2001 From: G4EVA-dev Date: Fri, 28 Feb 2025 22:57:29 +0100 Subject: [PATCH 14/19] fix: conflict in gitignore --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 236b4e6c4..39f224c6b 100644 --- a/.gitignore +++ b/.gitignore @@ -415,6 +415,9 @@ todo.txt docker-compose.yml data/ +# Docker compose +docker-compose.yml +data/ data/ docker-compose.yml From e7b1c44bb8d873decbe822230713a10024d16714 Mon Sep 17 00:00:00 2001 From: G4EVA-dev Date: Sun, 2 Mar 2025 01:09:49 +0100 Subject: [PATCH 15/19] fix: resolve merge conflicts --- src/modules/invite/invite.controller.ts | 5 ++--- src/modules/invite/tests/test-invite.service.spec.ts | 2 -- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/modules/invite/invite.controller.ts b/src/modules/invite/invite.controller.ts index 24218b305..69ff13219 100644 --- a/src/modules/invite/invite.controller.ts +++ b/src/modules/invite/invite.controller.ts @@ -10,7 +10,7 @@ import { Body, Res, UseGuards, - Query, + Query, // Added this import } from '@nestjs/common'; import { ApiBearerAuth, ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'; import { InviteService } from './invite.service'; @@ -26,14 +26,13 @@ import { SendInvitationsResponseDto } from './dto/send-invitations-response.dto' import { AcceptInviteDto } from './dto/accept-invite.dto'; import { OwnershipGuard } from '@guards/authorization.guard'; import * as SYS_MSG from '@shared/constants/SystemMessages'; -import { PaginationQueryDto } from './dto/pagination-query.dto'; +import { PaginationQueryDto } from './dto/pagination-query.dto'; // Added this import @ApiBearerAuth() @ApiTags('Organisation Invites') @Controller('organizations') export class InviteController { constructor(private readonly inviteService: InviteService) {} - // invite.controller.ts - Changes to the findAllInvitations method @ApiOperation({ summary: 'Get All Invitations' }) @ApiResponse({ status: 200, diff --git a/src/modules/invite/tests/test-invite.service.spec.ts b/src/modules/invite/tests/test-invite.service.spec.ts index 90f83dc78..a488deb00 100644 --- a/src/modules/invite/tests/test-invite.service.spec.ts +++ b/src/modules/invite/tests/test-invite.service.spec.ts @@ -1,4 +1,3 @@ -// invite.service.spec.ts import { Test, TestingModule } from '@nestjs/testing'; import { InviteService } from '../invite.service'; import { getRepositoryToken } from '@nestjs/typeorm'; @@ -146,7 +145,6 @@ describe('InviteService', () => { }); it('should handle errors and throw InternalServerErrorException', async () => { - // Setup repository mock to throw error mockInviteRepository.findAndCount.mockRejectedValue(new Error('Database error')); await expect(service.findAllInvitations()).rejects.toThrow(InternalServerErrorException); From 077286b4cb150784a2d7ed42ab8deed0e0549678 Mon Sep 17 00:00:00 2001 From: Khaybee Date: Sat, 1 Mar 2025 00:51:55 +0100 Subject: [PATCH 16/19] fix: fixed conflict from merge --- src/modules/auth/auth.service.ts | 50 ++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/src/modules/auth/auth.service.ts b/src/modules/auth/auth.service.ts index 9da65d5f4..0483a0256 100644 --- a/src/modules/auth/auth.service.ts +++ b/src/modules/auth/auth.service.ts @@ -31,9 +31,14 @@ export default class AuthenticationService { private otpService: OtpService, private emailService: EmailService, private organisationService: OrganisationsService, +<<<<<<< HEAD private profileService: ProfileService, private dataSource: DataSource ) {} +======= + private profileService: ProfileService + ) { } +>>>>>>> f63b542 (fix: fixed conflict from merge) async createNewUser(createUserDto: CreateUserDTO) { const result = await this.dataSource.transaction(async (manager: EntityManager) => { @@ -48,12 +53,57 @@ export default class AuthenticationService { const user = await this.userService.createUser(createUserDto, manager); +<<<<<<< HEAD if (!user) { throw new CustomHttpException(SYS_MSG.FAILED_TO_CREATE_USER, HttpStatus.BAD_REQUEST); } const newOrganisationPayload = { name: `${user.first_name}'s Organisation`, description: '', +======= + const user = await this.userService.getUserRecord({ identifier: createUserDto.email, identifierType: 'email' }); + + if (!user) { + throw new CustomHttpException(SYS_MSG.FAILED_TO_CREATE_USER, HttpStatus.BAD_REQUEST); + } + const newOrganisationPayload = { + name: `${user.first_name}'s Organisation`, + description: '', + email: user.email, + industry: '', + type: '', + country: '', + address: '', + state: '', + }; + + const createOrganisationPayload: CreateOrganisationRecordOptions = { + createPayload: newOrganisationPayload, + dbTransaction: { + useTransaction: true, + transactionManager: manager, + }, + }; + + const newOrganisation = await this.organisationService.create(createOrganisationPayload); + + const userOrganisations = await this.organisationService.getAllUserOrganisations(user.id, 1, 10); + const isSuperAdmin = userOrganisations.map(instance => instance.user_role).includes('super-admin'); + + const token = (await this.otpService.createOtp(user.id, manager)).token; + + const access_token = this.jwtService.sign({ + id: user.id, + sub: user.id, + email: user.email, + }); + + const responsePayload = { + user: { + id: user.id, + first_name: user.first_name, + last_name: user.last_name, +>>>>>>> f63b542 (fix: fixed conflict from merge) email: user.email, industry: '', type: '', From ed4e0df3ae60e3889431ebb7bcab5b17cb1dd9c9 Mon Sep 17 00:00:00 2001 From: G4EVA-dev Date: Sun, 2 Mar 2025 14:27:17 +0100 Subject: [PATCH 17/19] fix: resolved merge conflicts in auth.service.ts --- src/modules/auth/auth.service.ts | 50 -------------------------------- 1 file changed, 50 deletions(-) diff --git a/src/modules/auth/auth.service.ts b/src/modules/auth/auth.service.ts index 0483a0256..9da65d5f4 100644 --- a/src/modules/auth/auth.service.ts +++ b/src/modules/auth/auth.service.ts @@ -31,14 +31,9 @@ export default class AuthenticationService { private otpService: OtpService, private emailService: EmailService, private organisationService: OrganisationsService, -<<<<<<< HEAD private profileService: ProfileService, private dataSource: DataSource ) {} -======= - private profileService: ProfileService - ) { } ->>>>>>> f63b542 (fix: fixed conflict from merge) async createNewUser(createUserDto: CreateUserDTO) { const result = await this.dataSource.transaction(async (manager: EntityManager) => { @@ -53,57 +48,12 @@ export default class AuthenticationService { const user = await this.userService.createUser(createUserDto, manager); -<<<<<<< HEAD if (!user) { throw new CustomHttpException(SYS_MSG.FAILED_TO_CREATE_USER, HttpStatus.BAD_REQUEST); } const newOrganisationPayload = { name: `${user.first_name}'s Organisation`, description: '', -======= - const user = await this.userService.getUserRecord({ identifier: createUserDto.email, identifierType: 'email' }); - - if (!user) { - throw new CustomHttpException(SYS_MSG.FAILED_TO_CREATE_USER, HttpStatus.BAD_REQUEST); - } - const newOrganisationPayload = { - name: `${user.first_name}'s Organisation`, - description: '', - email: user.email, - industry: '', - type: '', - country: '', - address: '', - state: '', - }; - - const createOrganisationPayload: CreateOrganisationRecordOptions = { - createPayload: newOrganisationPayload, - dbTransaction: { - useTransaction: true, - transactionManager: manager, - }, - }; - - const newOrganisation = await this.organisationService.create(createOrganisationPayload); - - const userOrganisations = await this.organisationService.getAllUserOrganisations(user.id, 1, 10); - const isSuperAdmin = userOrganisations.map(instance => instance.user_role).includes('super-admin'); - - const token = (await this.otpService.createOtp(user.id, manager)).token; - - const access_token = this.jwtService.sign({ - id: user.id, - sub: user.id, - email: user.email, - }); - - const responsePayload = { - user: { - id: user.id, - first_name: user.first_name, - last_name: user.last_name, ->>>>>>> f63b542 (fix: fixed conflict from merge) email: user.email, industry: '', type: '', From 3b9637f3b5c51cede5de55b3f2d15d4d80de7db6 Mon Sep 17 00:00:00 2001 From: G4EVA-dev Date: Sun, 2 Mar 2025 15:22:41 +0100 Subject: [PATCH 18/19] fix: fix error with EntityManager --- src/modules/invite/tests/test-invite.service.spec.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/modules/invite/tests/test-invite.service.spec.ts b/src/modules/invite/tests/test-invite.service.spec.ts index a488deb00..f65498b18 100644 --- a/src/modules/invite/tests/test-invite.service.spec.ts +++ b/src/modules/invite/tests/test-invite.service.spec.ts @@ -9,7 +9,7 @@ import { MailerService } from '@nestjs-modules/mailer'; import { EmailService } from '@modules/email/email.service'; import { ConfigService } from '@nestjs/config'; import { OrganisationsService } from '@modules/organisations/organisations.service'; -import { Repository } from 'typeorm'; +import { Repository, EntityManager } from 'typeorm'; describe('InviteService', () => { let service: InviteService; @@ -47,6 +47,11 @@ describe('InviteService', () => { addOrganisationMember: jest.fn(), }; + // Add a mock for EntityManager + const mockEntityManager = { + transaction: jest.fn(), + }; + beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [ @@ -58,6 +63,7 @@ describe('InviteService', () => { { provide: EmailService, useValue: mockEmailService }, { provide: ConfigService, useValue: mockConfigService }, { provide: OrganisationsService, useValue: mockOrganisationsService }, + { provide: EntityManager, useValue: mockEntityManager }, // Add this line ], }).compile(); From ae23b2f956fb184c3c4b8af18eab7295f87d3421 Mon Sep 17 00:00:00 2001 From: Glenn Tanze <71010568+G4EVA-dev@users.noreply.github.com> Date: Sun, 2 Mar 2025 16:40:40 +0100 Subject: [PATCH 19/19] Update invite.entity.ts fix: remove createDate and upDate --- src/modules/invite/entities/invite.entity.ts | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/modules/invite/entities/invite.entity.ts b/src/modules/invite/entities/invite.entity.ts index c2d075d7d..f925dfd68 100644 --- a/src/modules/invite/entities/invite.entity.ts +++ b/src/modules/invite/entities/invite.entity.ts @@ -1,4 +1,4 @@ -import { Entity, Column, ManyToOne, CreateDateColumn, UpdateDateColumn } from 'typeorm'; +import { Entity, Column, ManyToOne } from 'typeorm'; import { AbstractBaseEntity } from '../../../entities/base.entity'; import { Organisation } from '../../organisations/entities/organisations.entity'; @@ -18,10 +18,4 @@ export class Invite extends AbstractBaseEntity { @ManyToOne(() => Organisation, organisation => organisation.invites, { nullable: false, onDelete: 'CASCADE' }) organisation: Organisation; - - @CreateDateColumn() - created_at: Date; - - @UpdateDateColumn() - updated_at: Date; }