Skip to content

Commit

Permalink
Delete endpoint for custom projects
Browse files Browse the repository at this point in the history
  • Loading branch information
catalin-oancea committed Dec 24, 2024
1 parent 3738456 commit 340f071
Show file tree
Hide file tree
Showing 3 changed files with 126 additions and 0 deletions.
27 changes: 27 additions & 0 deletions api/src/modules/custom-projects/custom-projects.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,4 +144,31 @@ export class CustomProjectsController {
},
);
}

@UseGuards(AuthGuard('jwt'), RolesGuard)
@RequiredRoles(ROLES.PARTNER, ROLES.ADMIN)
@TsRestHandler(customProjectContract.deleteCustomProjects)
async deleteCustomProjects(
@GetUser() user: User,
@Body() body: { ids: string[] },
): Promise<any> {
return tsRestHandler(
customProjectContract.deleteCustomProjects,
async () => {
if (
!(await this.customProjects.canUserDeleteProjects(user.id, body.ids))
) {
return {
status: 401,
body: null,
};
}
await this.customProjects.removeMany(body.ids);
return {
status: 200,
body: null,
};
},
);
}
}
12 changes: 12 additions & 0 deletions api/src/modules/custom-projects/custom-projects.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { FetchSpecification } from 'nestjs-base-service';
import { GetActivityTypesDefaults } from '@shared/dtos/custom-projects/get-activity-types-defaults.dto';
import { z } from 'zod';
import { customProjecsQuerySchema } from '@shared/contracts/custom-projects.contract';
import { In } from 'typeorm';

export type CustomProjectFetchSpecificacion = z.infer<
typeof customProjecsQuerySchema
Expand Down Expand Up @@ -143,4 +144,15 @@ export class CustomProjectsService extends AppBaseService<

return query;
}

async canUserDeleteProjects(
userId: string,
projectIds: string[],
): Promise<boolean> {
const customProjects = await this.repo.findBy({ id: In(projectIds) });

return customProjects.every(
(customProject) => customProject.user?.id === userId,
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { TestManager } from '../../utils/test-manager';
import { customProjectContract } from '@shared/contracts/custom-projects.contract';
import { User } from '@shared/entities/users/user.entity';
import { HttpStatus } from '@nestjs/common';

describe('Delete Custom projects', () => {
let testManager: TestManager;
let jwtToken: string;
let user: User;

beforeAll(async () => {
testManager = await TestManager.createTestManager();
});

beforeEach(async () => {
({ jwtToken, user } = await testManager.setUpTestUser());
await testManager.ingestCountries();
await testManager.ingestExcel(jwtToken);
});

afterEach(async () => {
await testManager.clearDatabase();
});

afterAll(async () => {
await testManager.close();
});

describe('Delete Custom Projects', () => {
test('An anonymous user should be UNAUTHORIZED to delete a custom project', async () => {
// Given
const customProject = await testManager.mocks().createCustomProject({
user: { id: user.id } as User,
});

// When deleting the custom project
const response = await testManager
.request()
.delete(customProjectContract.deleteCustomProjects.path)
.send({ ids: [customProject.id] });

// Then
expect(response.status).toBe(HttpStatus.UNAUTHORIZED);
expect(response.body.errors).toBeDefined();
});

test.only('An authenticated user should not be able to delete projects that do not belong to them', async () => {
// Given a custom project exists
const customProject = await testManager.mocks().createCustomProject();

// When deleting the custom project
const response = await testManager
.request()
.delete(customProjectContract.deleteCustomProjects.path)
.set('Authorization', `Bearer ${jwtToken}`)
.send({ ids: [customProject.id] });

// Then
expect(response.status).toBe(HttpStatus.UNAUTHORIZED);
});

test('An authenticated user should be able to delete projects', async () => {
// Given a custom project exists
const customProject = await testManager.mocks().createCustomProject({
user: { id: user.id } as User,
});

// When deleting the custom project
const response = await testManager
.request()
.delete(customProjectContract.deleteCustomProjects.path)
.set('Authorization', `Bearer ${jwtToken}`)
.send({ ids: [customProject.id] });

expect(response.status).toBe(HttpStatus.OK);

// Then the project should no longer exist
const getProjectResponse = await testManager
.request()
.get(
`${customProjectContract.getCustomProject.path}/${customProject.id}`,
)
.set('Authorization', `Bearer ${jwtToken}`);
expect(getProjectResponse.status).toBe(HttpStatus.NOT_FOUND);
});
});
});

0 comments on commit 340f071

Please sign in to comment.