Skip to content

Commit

Permalink
add findAll and findOne for admin only usage (#661)
Browse files Browse the repository at this point in the history
* add findAll and findOne for admin only usage

* add delete file endpoint, findOne now accessible by organizer and fix fileUpload

* re-write isAdminFlag

* fix isAdmin import

* fix person type in campaign application

---------

Co-authored-by: Aleksandar Petkov <[email protected]>
  • Loading branch information
Martbul and sashko9807 authored Aug 28, 2024
1 parent 49ae3ea commit d2704c4
Show file tree
Hide file tree
Showing 5 changed files with 208 additions and 32 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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: '[email protected]',
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',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -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')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
mockCampaigns,
mockCreatedCampaignApplication,
mockNewCampaignApplication,
mockSingleCampaignApplication,
mockUpdateCampaignApplication,
} from './__mocks__/campaign-application-mocks'
import { S3Service } from '../s3/s3.service'
Expand Down Expand Up @@ -38,6 +39,7 @@ describe('CampaignApplicationService', () => {

const mockS3Service = {
uploadObject: jest.fn(),
deleteObject: jest.fn(),
}

beforeEach(async () => {
Expand Down Expand Up @@ -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 = {
Expand Down
97 changes: 78 additions & 19 deletions apps/api/src/campaign-application/campaign-application.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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
}
}
}

0 comments on commit d2704c4

Please sign in to comment.