diff --git a/CHANGELOG.md b/CHANGELOG.md index 944850d8..51c4ccc9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -41,11 +41,12 @@ Another example [here](https://co-pilot.dev/changelog) - Update changelog ([#104](https://github.com/chingu-x/chingu-dashboard-be/pull/104)) - Update test.yml to run e2e tests on pull requests to the main branch [#105](https://github.com/chingu-x/chingu-dashboard-be/pull/105) - Update email templates to use domain in environment variables [#110](https://github.com/chingu-x/chingu-dashboard-be/pull/110) --Update /forms /forms/id response to include subQuestions [#115](https://github.com/chingu-x/chingu-dashboard-be/pull/115) +- Update /forms /forms/id response to include subQuestions [#115](https://github.com/chingu-x/chingu-dashboard-be/pull/115) - Add role and permission guard to some existing routes (features, forms, ideations, teams) [#112](https://github.com/chingu-x/chingu-dashboard-be/pull/112) - Refactor voyages endpoint paths to follow API naming conversion [#123](https://github.com/chingu-x/chingu-dashboard-be/pull/123) - Refactor resources PATCH and DELETE URI [#127](https://github.com/chingu-x/chingu-dashboard-be/pull/127) - Modified response for GET voyages/teams/{teamId}/resources, adding user id value [#129](https://github.com/chingu-x/chingu-dashboard-be/pull/129) +- Modified response for POST /api/v1/voyages/teams/{teamId}/techs/{teamTechId} & DELETE /api/v1/voyages/teams/{teamId}/techs/{teamTechId}, refactor id as teamTechStackItemVoteId value [#138](https://github.com/chingu-x/chingu-dashboard-be/pull/138) - updated meeting model schema to include optional description field [#135](https://github.com/chingu-x/chingu-dashboard-be/pull/135) - Remove teamMeetings from response for getSprintDatesByTeamId [#139](https://github.com/chingu-x/chingu-dashboard-be/pull/139) - diff --git a/src/techs/techs.controller.ts b/src/techs/techs.controller.ts index ade830db..2d281260 100644 --- a/src/techs/techs.controller.ts +++ b/src/techs/techs.controller.ts @@ -15,7 +15,11 @@ import { TechsService } from "./techs.service"; import { ApiOperation, ApiParam, ApiResponse, ApiTags } from "@nestjs/swagger"; import { CreateTeamTechDto } from "./dto/create-tech.dto"; import { UpdateTechSelectionsDto } from "./dto/update-tech-selections.dto"; -import { TeamTechResponse, TechItemResponse } from "./techs.response"; +import { + TeamTechResponse, + TechItemResponse, + TechItemDeleteResponse, +} from "./techs.response"; import { BadRequestErrorResponse, ConflictErrorResponse, @@ -119,14 +123,14 @@ export class TechsController { description: "voyage team Id", type: "Integer", required: true, - example: 1, + example: 2, }) @ApiParam({ name: "teamTechId", description: "techId of a tech the team has select (TeamTechStackItem)", type: "Integer", required: true, - example: 11, + example: 6, }) @Post("/:teamTechId") addExistingTechVote( @@ -143,8 +147,8 @@ export class TechsController { @ApiResponse({ status: HttpStatus.OK, description: - "Successfully removed a vote for an existing tech stack item by a user", - type: TechItemResponse, + "Successfully removed a vote for an existing tech stack item by a user or removes the tech stack item if no votes left", + type: TechItemDeleteResponse, }) @ApiResponse({ status: HttpStatus.NOT_FOUND, @@ -166,14 +170,14 @@ export class TechsController { description: "voyage team Id", type: "Integer", required: true, - example: 1, + example: 2, }) @ApiParam({ name: "teamTechId", description: "techId of a tech the team has select (TeamTechStackItem)", type: "Integer", required: true, - example: 11, + example: 6, }) @Delete("/:teamTechId") removeVote( diff --git a/src/techs/techs.response.ts b/src/techs/techs.response.ts index 169816bd..dd3abdea 100644 --- a/src/techs/techs.response.ts +++ b/src/techs/techs.response.ts @@ -54,12 +54,12 @@ export class TeamTechResponse { export class TechItemResponse { @ApiProperty({ example: 10 }) - id: number; + teamTechStackItemVotedId: number; @ApiProperty({ example: 11 }) teamTechId: number; - @ApiProperty({ example: "Frontend" }) + @ApiProperty({ example: 8 }) teamMemberId: number; @ApiProperty({ example: "2023-12-01T13:55:00.611Z" }) @@ -68,3 +68,11 @@ export class TechItemResponse { @ApiProperty({ example: "2023-12-01T13:55:00.611Z" }) updatedAt: Date; } + +export class TechItemDeleteResponse { + @ApiProperty({ example: "The vote and tech stack item were deleted" }) + message: string; + + @ApiProperty({ example: 200 }) + statusCode: number; +} diff --git a/src/techs/techs.service.ts b/src/techs/techs.service.ts index c6e3ce51..4ecb152d 100644 --- a/src/techs/techs.service.ts +++ b/src/techs/techs.service.ts @@ -139,12 +139,20 @@ export class TechsService { }, }); - return this.prisma.teamTechStackItemVote.create({ - data: { - teamTechId: newTeamTechItem.id, - teamMemberId: voyageMemberId, - }, - }); + const TeamTechItemFirstVote = + await this.prisma.teamTechStackItemVote.create({ + data: { + teamTechId: newTeamTechItem.id, + teamMemberId: voyageMemberId, + }, + }); + return { + teamTechStackItemVoteId: TeamTechItemFirstVote.id, + teamTechId: newTeamTechItem.id, + teamMemberId: TeamTechItemFirstVote.teamMemberId, + createdAt: TeamTechItemFirstVote.createdAt, + updatedAt: TeamTechItemFirstVote.updatedAt, + }; } catch (e) { if (e.code === "P2002") { throw new ConflictException( @@ -156,16 +164,33 @@ export class TechsService { } async addExistingTechVote(req, teamId, teamTechId) { + // check if team tech item exists + const teamTechItem = await this.prisma.teamTechStackItem.findUnique({ + where: { + id: teamTechId, + }, + }); + if (!teamTechItem) + throw new BadRequestException("Team Tech Item not found"); const voyageMemberId = await this.findVoyageMemberId(req, teamId); if (!voyageMemberId) throw new BadRequestException("Invalid User"); try { - return await this.prisma.teamTechStackItemVote.create({ - data: { - teamTechId, - teamMemberId: voyageMemberId, - }, - }); + const teamMemberTechVote = + await this.prisma.teamTechStackItemVote.create({ + data: { + teamTechId, + teamMemberId: voyageMemberId, + }, + }); + // If successful, it returns an object containing the details of the vote + return { + teamTechStackItemVoteId: teamMemberTechVote.id, + teamTechId, + teamMemberId: teamMemberTechVote.teamMemberId, + createdAt: teamMemberTechVote.createdAt, + updatedAt: teamMemberTechVote.updatedAt, + }; } catch (e) { if (e.code === "P2002") { throw new ConflictException( @@ -181,7 +206,7 @@ export class TechsService { if (!voyageMemberId) throw new BadRequestException("Invalid User"); try { - const deletedVote = await this.prisma.teamTechStackItemVote.delete({ + await this.prisma.teamTechStackItemVote.delete({ where: { userTeamStackVote: { teamTechId, @@ -201,15 +226,24 @@ export class TechsService { }, }, ); - + // Check if the teamTechStackItemVotes array is empty if (teamTechItem.teamTechStackItemVotes.length === 0) { - return this.prisma.teamTechStackItem.delete({ + // If it's empty, delete the tech item from the database using Prisma ORM + await this.prisma.teamTechStackItem.delete({ where: { id: teamTechId, }, }); + + return { + message: "The vote and tech stack item were deleted", + statusCode: 200, + }; } else { - return deletedVote; + return { + message: "This vote was deleted", + statusCode: 200, + }; } } catch (e) { if (e.code === "P2025") { diff --git a/test/techs.e2e-spec.ts b/test/techs.e2e-spec.ts index b22c7b14..cbc88cf2 100644 --- a/test/techs.e2e-spec.ts +++ b/test/techs.e2e-spec.ts @@ -126,7 +126,7 @@ describe("Techs Controller (e2e)", () => { .expect((res) => { expect(res.body).toEqual( expect.objectContaining({ - id: expect.any(Number), + teamTechStackItemVoteId: expect.any(Number), teamTechId: expect.any(Number), teamMemberId: expect.any(Number), createdAt: expect.any(String), @@ -228,7 +228,7 @@ describe("Techs Controller (e2e)", () => { .expect((res) => { expect(res.body).toEqual( expect.objectContaining({ - id: expect.any(Number), + teamTechStackItemVoteId: expect.any(Number), teamTechId: expect.any(Number), teamMemberId: expect.any(Number), createdAt: expect.any(String), @@ -321,11 +321,8 @@ describe("Techs Controller (e2e)", () => { .expect((res) => { expect(res.body).toEqual( expect.objectContaining({ - id: expect.any(Number), - teamTechId: expect.any(Number), - teamMemberId: expect.any(Number), - createdAt: expect.any(String), - updatedAt: expect.any(String), + message: "This vote was deleted", + statusCode: 200, }), ); }); @@ -341,6 +338,34 @@ describe("Techs Controller (e2e)", () => { return expect(techStackVote[0]).toEqual(undefined); }); + it("should return 200 if tech last vote was deleted and team tech stack item is deleted", async () => { + const teamId: number = 2; + const techId: number = 9; + return request(app.getHttpServer()) + .delete(`/voyages/teams/${teamId}/techs/${techId}`) + .set("Authorization", `Bearer ${userAccessToken}`) + .expect(200) + .expect("Content-Type", /json/) + .expect((res) => { + expect(res.body).toEqual( + expect.objectContaining({ + message: + "The vote and tech stack item were deleted", + statusCode: 200, + }), + ); + }); + }); + + it("- verify that tech stack Item was deleted from database", async () => { + const techStackItem = await prisma.teamTechStackItem.findFirst({ + where: { + name: newTechName, + }, + }); + return expect(techStackItem).toBeNull(); + }); + it("should return 401 unauthorized if not logged in", async () => { const teamId: number = 2; const techId: number = 3;