diff --git a/apps/api/src/campaign-application/__mocks__/campaign-application-mocks.ts b/apps/api/src/campaign-application/__mocks__/campaign-application-mocks.ts index 85a419ef..c788a891 100644 --- a/apps/api/src/campaign-application/__mocks__/campaign-application-mocks.ts +++ b/apps/api/src/campaign-application/__mocks__/campaign-application-mocks.ts @@ -18,6 +18,31 @@ export const mockNewCampaignApplication = { category: CampaignTypeCategory.medical, } +export const mockSingleCampaignApplication = { + id: '1', + createdAt: new Date('2022-04-08T06:36:33.661Z'), + updatedAt: new Date('2022-04-08T06:36:33.662Z'), + description: 'Test description1', + organizerId: 'ffdbcc41-85ec-476c-9e59-0662f3b433af', + organizerName: 'Test Organizer1', + organizerEmail: 'organizer1@example.com', + beneficiary: 'test beneficary1', + organizerPhone: '123456789', + organizerBeneficiaryRel: 'Test Relation1', + campaignName: 'Test Campaign1', + goal: 'Test Goal1', + history: 'test history1', + amount: '1000', + campaignGuarantee: 'test campaignGuarantee1', + otherFinanceSources: 'test otherFinanceSources1', + otherNotes: 'test otherNotes1', + state: CampaignApplicationState.review, + category: CampaignTypeCategory.medical, + ticketURL: 'testsodifhso1', + archived: false, + documents: [{ id: 'fileId' }], +} + export const mockCampaigns = [ { id: '1', diff --git a/apps/api/src/campaign-application/campaign-application.controller.spec.ts b/apps/api/src/campaign-application/campaign-application.controller.spec.ts index a0179f0e..9effa150 100644 --- a/apps/api/src/campaign-application/campaign-application.controller.spec.ts +++ b/apps/api/src/campaign-application/campaign-application.controller.spec.ts @@ -110,25 +110,36 @@ describe('CampaignApplicationController', () => { // Act & Assert expect(() => controller.findAll(user)).toThrow(ForbiddenException) }) - it('when findAll called by an admin user it should delegate to the service findAll', () => { + + it('when findOne is called by an organizer, it should delegate to the service findOne', async () => { + // Arrange + jest.spyOn(personService, 'findOneByKeycloakId').mockResolvedValue(mockUser) + jest.mock('../auth/keycloak', () => ({ - isAdmin: jest.fn().mockImplementation((user: KeycloakTokenParsed) => { - return user.resource_access?.account?.roles.includes('account-view-supporters') - }), + isAdmin: jest.fn().mockReturnValue(false), })) - // Act & Assert - expect(() => controller.findAll(mockUserAdmin)).not.toThrow(ForbiddenException) - controller.findAll(mockUserAdmin) - expect(service.findAll).toHaveBeenCalled() + // Act + await controller.findOne('id', mockUser) + + // Assert + expect(personService.findOneByKeycloakId).toHaveBeenCalledWith(mockUser.sub) + expect(service.findOne).toHaveBeenCalledWith('id', false, mockUser) }) - it('when findOne called it should delegate to the service findOne', () => { + it('when findOne is called by an admin user, it should delegate to the service with isAdmin true', async () => { + // Arrange + jest.spyOn(personService, 'findOneByKeycloakId').mockResolvedValue(mockUserAdmin) + jest.mock('../auth/keycloak', () => ({ + isAdmin: jest.fn().mockReturnValue(true), + })) + // Act - controller.findOne('id') + await controller.findOne('id', mockUserAdmin) // Assert - expect(service.findOne).toHaveBeenCalledWith('id') + expect(personService.findOneByKeycloakId).toHaveBeenCalledWith(mockUserAdmin.sub) + expect(service.findOne).toHaveBeenCalledWith('id', true, mockUserAdmin) }) it('when update called by an user it should delegate to the service update', async () => { diff --git a/apps/api/src/campaign-application/campaign-application.controller.ts b/apps/api/src/campaign-application/campaign-application.controller.ts index d11333e8..b3beb2ac 100644 --- a/apps/api/src/campaign-application/campaign-application.controller.ts +++ b/apps/api/src/campaign-application/campaign-application.controller.ts @@ -10,6 +10,7 @@ import { Logger, UploadedFiles, UseInterceptors, + Delete, } from '@nestjs/common' import { CampaignApplicationService } from './campaign-application.service' import { CreateCampaignApplicationDto } from './dto/create-campaign-application.dto' @@ -76,8 +77,29 @@ export class CampaignApplicationController { } @Get('byId/:id') - findOne(@Param('id') id: string) { - return this.campaignApplicationService.findOne(id) + async findOne(@Param('id') id: string, @AuthenticatedUser() user: KeycloakTokenParsed) { + const person = await this.personService.findOneByKeycloakId(user.sub) + if (!person) { + Logger.error('No person found in database') + throw new NotFoundException('No person found in database') + } + + const isAdminFlag = isAdmin(user) + + return this.campaignApplicationService.findOne(id, isAdminFlag, person) + } + + @Delete('fileById/:id') + async deleteFile(@Param('id') id: string, @AuthenticatedUser() user: KeycloakTokenParsed) { + const person = await this.personService.findOneByKeycloakId(user.sub) + if (!person) { + Logger.error('No person found in database') + throw new NotFoundException('No person found in database') + } + + const isAdminFlag = isAdmin(user) + + return this.campaignApplicationService.deleteFile(id, isAdminFlag, person) } @Patch(':id') diff --git a/apps/api/src/campaign-application/campaign-application.service.spec.ts b/apps/api/src/campaign-application/campaign-application.service.spec.ts index 5bbca2f7..eadf7e94 100644 --- a/apps/api/src/campaign-application/campaign-application.service.spec.ts +++ b/apps/api/src/campaign-application/campaign-application.service.spec.ts @@ -10,6 +10,7 @@ import { mockCampaigns, mockCreatedCampaignApplication, mockNewCampaignApplication, + mockSingleCampaignApplication, mockUpdateCampaignApplication, } from './__mocks__/campaign-application-mocks' import { S3Service } from '../s3/s3.service' @@ -38,6 +39,7 @@ describe('CampaignApplicationService', () => { const mockS3Service = { uploadObject: jest.fn(), + deleteObject: jest.fn(), } beforeEach(async () => { @@ -202,6 +204,63 @@ describe('CampaignApplicationService', () => { }) }) + describe('findOne', () => { + it('should return a single campaign-application', async () => { + prismaMock.campaignApplication.findUnique.mockResolvedValue(mockSingleCampaignApplication) + + const result = await service.findOne('id', false, mockPerson) + + expect(result).toEqual(mockSingleCampaignApplication) + expect(prismaMock.campaignApplication.findUnique).toHaveBeenCalledTimes(1) + }) + + it('should throw a NotFoundException if no campaign-application is found', async () => { + prismaMock.campaignApplication.findUnique.mockResolvedValue(null) + + await expect(service.findOne('id', false, mockPerson)).rejects.toThrow( + new NotFoundException('Campaign application doesnt exist'), + ) + expect(prismaMock.campaignApplication.findUnique).toHaveBeenCalledTimes(1) + }) + + it('should handle errors and throw an exception', async () => { + const errorMessage = 'error' + prismaMock.campaignApplication.findUnique.mockRejectedValue(new Error(errorMessage)) + + await expect(service.findOne('id', false, mockPerson)).rejects.toThrow(errorMessage) + expect(prismaMock.campaignApplication.findUnique).toHaveBeenCalledTimes(1) + }) + }) + + describe('deleteFile', () => { + it('should return a message on successful deletion', async () => { + prismaMock.campaignApplication.findFirst.mockResolvedValue(mockSingleCampaignApplication) + + const result = await service.deleteFile('fileId', false, mockPerson) + + expect(result).toEqual('Successfully deleted file') + expect(prismaMock.campaignApplication.findFirst).toHaveBeenCalledTimes(1) + }) + + it('should throw a NotFoundException if no campaign-application is found', async () => { + prismaMock.campaignApplication.findUnique.mockResolvedValue(null) + + await expect(service.deleteFile('fileId', false, mockPerson)).rejects.toThrow( + new NotFoundException('File does not exist'), + ) + expect(prismaMock.campaignApplication.findFirst).toHaveBeenCalledTimes(1) + }) + + it('should handle errors and throw an exception', async () => { + const errorMessage = 'error' + prismaMock.campaignApplication.findFirst.mockRejectedValue(new Error(errorMessage)) + + await expect(service.deleteFile('fileId', false, mockPerson)).rejects.toThrow(errorMessage) + expect(prismaMock.campaignApplication.findFirst).toHaveBeenCalledTimes(1) + expect(prismaMock.campaignApplicationFile.delete).not.toHaveBeenCalled() + }) + }) + describe('updateCampaignApplication', () => { it('should update a campaign application if the user is the organizer', async () => { const mockCampaignApplication = { diff --git a/apps/api/src/campaign-application/campaign-application.service.ts b/apps/api/src/campaign-application/campaign-application.service.ts index 0024aac0..ae0fff51 100644 --- a/apps/api/src/campaign-application/campaign-application.service.ts +++ b/apps/api/src/campaign-application/campaign-application.service.ts @@ -9,7 +9,7 @@ import { CreateCampaignApplicationDto } from './dto/create-campaign-application. import { UpdateCampaignApplicationDto } from './dto/update-campaign-application.dto' import { PrismaService } from '../prisma/prisma.service' import { OrganizerService } from '../organizer/organizer.service' -import { CampaignApplicationFileRole, Person } from '@prisma/client' +import { CampaignApplicationFileRole, Person, Prisma } from '@prisma/client' import { S3Service } from './../s3/s3.service' import { CreateCampaignApplicationFileDto } from './dto/create-campaignApplication-file.dto' @Injectable() @@ -86,12 +86,66 @@ export class CampaignApplicationService { } } - findAll() { - return this.prisma.campaignApplication.findMany() + async findAll() { + try { + const campaignApplications = await this.prisma.campaignApplication.findMany() + return campaignApplications + } catch (error) { + Logger.error('Error in findAll():', error) + throw error + } + } + + async findOne(id: string, isAdminFlag: boolean, person: Prisma.PersonGetPayload<{ include: { organizer: {select:{id:true}}}}>) { + try { + const singleCampaignApplication = await this.prisma.campaignApplication.findUnique({ + where: { id }, + }) + if (!singleCampaignApplication) { + throw new NotFoundException('Campaign application doesnt exist') + } + + if (isAdminFlag === false && singleCampaignApplication.organizerId !== person.organizer?.id) { + throw new ForbiddenException('User is not admin or organizer of the campaignApplication') + } + + return singleCampaignApplication + } catch (error) { + Logger.error('Error in findOne():', error) + throw error + } } - findOne(id: string) { - return `This action returns a #${id} campaignApplication` + async deleteFile(id: string, isAdminFlag: boolean, person: Prisma.PersonGetPayload<{ include: { organizer: {select:{id:true}}}}>) { + try { + const campaignApplication = await this.prisma.campaignApplication.findFirst({ + where: { + documents: { + some: { + id: id, + }, + }, + }, + }) + + if (!campaignApplication) { + throw new NotFoundException('File does not exist') + } + + if (isAdminFlag === false && campaignApplication.organizerId !== person.organizer?.id) { + throw new ForbiddenException('User is not admin or organizer of the campaignApplication') + } + + await this.prisma.campaignApplicationFile.delete({ + where: { id }, + }) + + await this.s3.deleteObject(this.bucketName, id) + } catch (error) { + Logger.error('Error in deleteFile():', error) + throw error + } + return 'Successfully deleted file' } async updateCampaignApplication( @@ -182,20 +236,25 @@ export class CampaignApplicationService { role: CampaignApplicationFileRole.document, } - const createFileInDb = await this.prisma.campaignApplicationFile.create({ - data: fileDto, - }) + try { + const createFileInDb = await this.prisma.campaignApplicationFile.create({ + data: fileDto, + }) - await this.s3.uploadObject( - this.bucketName, - createFileInDb.id, - file.originalname, - file.mimetype, - file.buffer, - 'CampaignApplicationFile', - campaignApplicationId, - personId, - ) - return createFileInDb + await this.s3.uploadObject( + this.bucketName, + createFileInDb.id, + file.originalname, + file.mimetype, + file.buffer, + 'CampaignApplicationFile', + campaignApplicationId, + personId, + ) + return createFileInDb + } catch (error) { + Logger.error('Error in campaignApplicationFilesCreate():', error) + throw error + } } }