Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#2223: Design Review mark user confirmation endpoint #2225

Merged
merged 16 commits into from
Mar 19, 2024
14 changes: 14 additions & 0 deletions src/backend/src/controllers/design-reviews.controllers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,4 +116,18 @@ export default class DesignReviewsController {
next(error);
}
}

// Mark the current user as confirmed for the given design review
static async markUserConfirmed(req: Request, res: Response, next: NextFunction) {
try {
const { availability } = req.body;
martin0he marked this conversation as resolved.
Show resolved Hide resolved
const { designReviewId } = req.params;
const user = await getCurrentUser(res);

await DesignReviewsService.markUserConfirmed(designReviewId, availability, user);
return res.status(200).json({ message: 'Design Review member confirmed successfully' });
} catch (error: unknown) {
next(error);
}
}
}
7 changes: 7 additions & 0 deletions src/backend/src/routes/design-reviews.routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,4 +52,11 @@ designReviewsRouter.post(
DesignReviewsController.editDesignReviews
);

designReviewsRouter.post(
'/:designReviewId/confirm-schedule',
body('meetingTimes').isArray(),
validateInputs,
DesignReviewsController.markUserConfirmed
);

export default designReviewsRouter;
58 changes: 58 additions & 0 deletions src/backend/src/services/design-reviews.services.ts
Original file line number Diff line number Diff line change
Expand Up @@ -316,4 +316,62 @@ export default class DesignReviewsService {
});
return designReviewTransformer(updateDesignReview);
}

/**
* Edits a design review but confirming a given user's availability
* @param submitter the member that is being confirmed
* @param designReviewId the id of the design review
* @param availability the given member's availabilities
* @returns the modified design review with its updated confirmedMembers
*/
static async markUserConfirmed(designReviewId: string, availability: number[], submitter: User): Promise<DesignReview> {
const designReview = await prisma.design_Review.findUnique({
where: { designReviewId },
...designReviewQueryArgs
});

if (!designReview) throw new NotFoundException('Design Review', designReviewId);

if (designReview.dateDeleted) throw new DeletedException('Design Review', designReviewId);

// validation
const requiredMembers = designReview.requiredMembers.map((user) => user.userId);
const optionalMembers = designReview.optionalMembers.map((user) => user.userId);

const isMemberPresent = (member: User): boolean => {
return requiredMembers.includes(member.userId) || optionalMembers.includes(member.userId);
};

if (!isMemberPresent(submitter))
throw new HttpException(400, 'Current user is not in the list of this design reviews members');

// update user schedule settings (in progress)

// Update user schedule settings
if (availability) {
const validAvailability = validateMeetingTimes(availability);
console.log(validAvailability);
if (!validAvailability) throw new HttpException(400, 'This availability is invalid');

// Update user schedule settings with the new availability here...
// Example: await updateUserScheduleSettings(submitter, availability);
}

console.log(availability);

// update design review confirmed members (works)
const newConfirmedMembers: User[] = [...designReview.confirmedMembers, submitter];
const updatedConfirmedMembers: { userId: number }[] = getPrismaQueryUserIds(newConfirmedMembers);
const markedDesignReview = await prisma.design_Review.update({
where: { designReviewId },
...designReviewQueryArgs,
data: {
confirmedMembers: {
set: updatedConfirmedMembers
}
}
});

return designReviewTransformer(markedDesignReview);
}
}
48 changes: 47 additions & 1 deletion src/backend/tests/design-reviews.test.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import {
designReview1,
designReview3,
designReview5,
prismaDesignReview1,
prismaDesignReview2,
prismaDesignReview3,
sharedDesignReview1,
teamType1
} from './test-data/design-reviews.test-data';
import { aquaman, batman, theVisitor, wonderwoman } from './test-data/users.test-data';
import { aquaman, batman, superman, theVisitor, wonderwoman } from './test-data/users.test-data';
import prisma from '../src/prisma/prisma';
import {
AccessDeniedAdminOnlyException,
Expand Down Expand Up @@ -501,4 +502,49 @@ describe('Design Reviews', () => {
).rejects.toThrow(new NotFoundException('WBS Element', 15));
});
});

describe('Mark user confirmation tests', () => {
test('Marking succeeds', async () => {
vi.spyOn(prisma.design_Review, 'findUnique').mockResolvedValue(prismaDesignReview1);
const result = await DesignReviewsService.markUserConfirmed(prismaDesignReview1.designReviewId, [1, 2], wonderwoman);

expect(prisma.design_Review.findUnique).toHaveBeenCalledTimes(0);
expect(result).toEqual(designReview5);
});

test('Design Review was not found', async () => {
vi.spyOn(prisma.design_Review, 'findUnique').mockResolvedValue(null);
await expect(() =>
DesignReviewsService.markUserConfirmed(prismaDesignReview1.designReviewId, [0, 1, 2], batman)
).rejects.toThrow(new NotFoundException('Design Review', prismaDesignReview1.designReviewId));
});

test('Design Review was deleted', async () => {
vi.spyOn(prisma.design_Review, 'findUnique').mockResolvedValue({ ...prismaDesignReview1, dateDeleted: new Date() });
await expect(() =>
DesignReviewsService.markUserConfirmed(prismaDesignReview1.designReviewId, [0, 1, 2], batman)
).rejects.toThrow(new DeletedException('Design Review', prismaDesignReview1.designReviewId));
});

test('User was not in required/optional members of design review', async () => {
vi.spyOn(prisma.design_Review, 'findUnique').mockResolvedValue(prismaDesignReview1);
await expect(() =>
DesignReviewsService.markUserConfirmed(prismaDesignReview1.designReviewId, [0, 1, 2], superman)
).rejects.toThrow(new HttpException(400, 'Current user is not in the list of this design reviews members'));
});

test('Availabilities were invalid - out of bounds', async () => {
vi.spyOn(prisma.design_Review, 'findUnique').mockResolvedValue(prismaDesignReview1);
await expect(() =>
DesignReviewsService.markUserConfirmed(prismaDesignReview1.designReviewId, [0, 85], batman)
).rejects.toThrow(new HttpException(400, 'meeting time must be between 0-83'));
});

test('Availabilities were invalid - non-consecutive', async () => {
vi.spyOn(prisma.design_Review, 'findUnique').mockResolvedValue(prismaDesignReview1);
await expect(() =>
DesignReviewsService.markUserConfirmed(prismaDesignReview1.designReviewId, [1, 3], batman)
).rejects.toThrow(new HttpException(400, 'meeting times must be consecutive'));
});
});
});
46 changes: 46 additions & 0 deletions src/backend/tests/test-data/design-reviews.test-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,33 @@ export const prismaDesignReview3: Prisma.Design_ReviewGetPayload<typeof designRe
teamType: teamType1
};

export const prismaDesignReview5: Prisma.Design_ReviewGetPayload<typeof designReviewQueryArgs> = {
designReviewId: '1',
dateScheduled: new Date('2024-03-25'),
meetingTimes: [0, 1, 2, 3],
dateCreated: new Date('2024-03-10'),
userCreatedId: batman.userId,
userCreated: batman,
status: PrismaDesignReviewStatus.CONFIRMED,
teamTypeId: '1',
teamType: teamType1,
location: null,
isOnline: true,
isInPerson: false,
zoomLink: null,
dateDeleted: null,
userDeletedId: null,
docTemplateLink: null,
wbsElementId: 1,
requiredMembers: [batman],
optionalMembers: [wonderwoman],
confirmedMembers: [batman],
deniedMembers: [],
attendees: [batman],
userDeleted: null,
wbsElement: prismaWbsElement1
};

export const designReview3: DesignReview = {
designReviewId: '2',
dateScheduled: new Date('2024-03-25'),
Expand Down Expand Up @@ -161,3 +188,22 @@ export const sharedDesignReview1: SharedDesignReview = {
wbsName: 'car',
wbsNum: { carNumber: 1, projectNumber: 2, workPackageNumber: 0 }
};

export const designReview5: DesignReview = {
designReviewId: '1',
dateScheduled: new Date('2024-03-25'),
meetingTimes: [0, 1, 2, 3],
dateCreated: new Date('2024-03-10'),
userCreated: batman,
status: DesignReviewStatus.CONFIRMED,
teamType: teamType1,
isOnline: true,
isInPerson: false,
requiredMembers: [batman],
optionalMembers: [wonderwoman],
confirmedMembers: [batman],
deniedMembers: [],
attendees: [batman],
wbsName: 'car',
wbsNum: { carNumber: 1, projectNumber: 2, workPackageNumber: 0 }
};
Loading