Skip to content

Commit 00280db

Browse files
committed
Merge branch 'develop' into #2131-drc-create-modal
2 parents 870b279 + 7f69812 commit 00280db

35 files changed

Lines changed: 1306 additions & 131 deletions

src/backend/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ import changeRequestsRouter from './src/routes/change-requests.routes';
1111
import descriptionBulletsRouter from './src/routes/description-bullets.routes';
1212
import tasksRouter from './src/routes/tasks.routes';
1313
import reimbursementRequestsRouter from './src/routes/reimbursement-requests.routes';
14-
import designReviewRouter from './src/routes/design-review.routes';
1514
import notificationsRouter from './src/routes/notifications.routes';
15+
import designReviewsRouter from './src/routes/design-reviews.routes';
1616

1717
const app = express();
1818
const port = process.env.PORT || 3001;
@@ -54,7 +54,7 @@ app.use('/change-requests', changeRequestsRouter);
5454
app.use('/description-bullets', descriptionBulletsRouter);
5555
app.use('/tasks', tasksRouter);
5656
app.use('/reimbursement-requests', reimbursementRequestsRouter);
57-
app.use('/design-reviews', designReviewRouter);
57+
app.use('/design-reviews', designReviewsRouter);
5858
app.use('/notifications', notificationsRouter);
5959
app.use('/', (_req, res) => {
6060
res.json('Welcome to FinishLine');

src/backend/src/controllers/design-review.controllers.ts renamed to src/backend/src/controllers/design-reviews.controllers.ts

Lines changed: 53 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
import { User } from '@prisma/client';
2-
import { Request, Response, NextFunction } from 'express';
1+
import { NextFunction, Request, Response } from 'express';
2+
import DesignReviewsService from '../services/design-reviews.services';
33
import { getCurrentUser } from '../utils/auth.utils';
4-
import DesignReviewService from '../services/design-review.services';
4+
import { User } from '@prisma/client';
55

6-
export default class DesignReviewController {
6+
export default class DesignReviewsController {
77
static async getAllDesignReviews(_req: Request, res: Response, next: NextFunction) {
88
try {
9-
const designReviews = await DesignReviewService.getAllDesignReviews();
9+
const designReviews = await DesignReviewsService.getAllDesignReviews();
1010
return res.status(200).json(designReviews);
1111
} catch (error: unknown) {
1212
next(error);
@@ -17,7 +17,7 @@ export default class DesignReviewController {
1717
try {
1818
const drId: string = req.params.designReviewId;
1919
const user: User = await getCurrentUser(res);
20-
const deletedDesignReview = await DesignReviewService.deleteDesignReview(user, drId);
20+
const deletedDesignReview = await DesignReviewsService.deleteDesignReview(user, drId);
2121
return res.status(200).json(deletedDesignReview);
2222
} catch (error: unknown) {
2323
next(error);
@@ -41,7 +41,7 @@ export default class DesignReviewController {
4141
meetingTimes
4242
} = req.body;
4343

44-
const createdDesignReview = await DesignReviewService.createDesignReview(
44+
const createdDesignReview = await DesignReviewsService.createDesignReview(
4545
submitter,
4646
dateScheduled,
4747
teamTypeId,
@@ -65,10 +65,55 @@ export default class DesignReviewController {
6565
try {
6666
const drId: string = req.params.designReviewId;
6767
const user: User = await getCurrentUser(res);
68-
const designReview = await DesignReviewService.getSingleDesignReview(user, drId);
68+
const designReview = await DesignReviewsService.getSingleDesignReview(user, drId);
6969
return res.status(200).json(designReview);
7070
} catch (error: unknown) {
7171
next(error);
7272
}
7373
}
74+
75+
// Edit a work package to the given specifications
76+
static async editDesignReviews(req: Request, res: Response, next: NextFunction) {
77+
try {
78+
const {
79+
dateScheduled,
80+
teamType,
81+
requiredMembers,
82+
optionalMembers,
83+
isOnline,
84+
isInPerson,
85+
zoomLink,
86+
location,
87+
docTemplateLink,
88+
status,
89+
attendees,
90+
meetingTimes
91+
} = req.body;
92+
93+
const { designReviewId } = req.params;
94+
95+
// get the user from the submitter
96+
const user = await getCurrentUser(res);
97+
98+
await DesignReviewsService.editDesignReview(
99+
user,
100+
designReviewId,
101+
dateScheduled,
102+
teamType.teamTypeId,
103+
requiredMembers,
104+
optionalMembers,
105+
isOnline,
106+
isInPerson,
107+
zoomLink,
108+
location,
109+
docTemplateLink,
110+
status,
111+
attendees,
112+
meetingTimes
113+
);
114+
return res.status(200).json({ message: 'Design Review updated successfully' });
115+
} catch (error: unknown) {
116+
next(error);
117+
}
118+
}
74119
}

src/backend/src/controllers/users.controllers.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,24 @@ export default class UsersController {
146146
}
147147
}
148148

149+
static async setUserScheduleSettings(req: Request, res: Response, next: NextFunction) {
150+
try {
151+
const { personalGmail, personalZoomLink, availability } = req.body;
152+
const user = await getCurrentUser(res);
153+
154+
const updatedScheduleSettings = await UsersService.setUserScheduleSettings(
155+
user,
156+
personalGmail,
157+
personalZoomLink,
158+
availability
159+
);
160+
161+
return res.status(200).json(updatedScheduleSettings);
162+
} catch (error: unknown) {
163+
next(error);
164+
}
165+
}
166+
149167
static async getUserScheduleSettings(req: Request, res: Response, next: NextFunction) {
150168
try {
151169
const userId: number = parseInt(req.params.userId);

src/backend/src/prisma-query-args/design-review.query-args.ts renamed to src/backend/src/prisma-query-args/design-reviews.query-args.ts

File renamed without changes.

src/backend/src/routes/design-review.routes.ts

Lines changed: 0 additions & 34 deletions
This file was deleted.
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import express from 'express';
2+
import { body } from 'express-validator';
3+
import { intMinZero, nonEmptyString, isDate, isDesignReviewStatus } from '../utils/validation.utils';
4+
import DesignReviewsController from '../controllers/design-reviews.controllers';
5+
import { validateInputs } from '../utils/utils';
6+
const designReviewsRouter = express.Router();
7+
8+
designReviewsRouter.get('/', DesignReviewsController.getAllDesignReviews);
9+
10+
designReviewsRouter.delete('/:designReviewId/delete', DesignReviewsController.deleteDesignReview);
11+
designReviewsRouter.get('/:designReviewId', DesignReviewsController.getSingleDesignReview);
12+
13+
designReviewsRouter.post(
14+
'/create',
15+
isDate(body('dateScheduled')),
16+
nonEmptyString(body('teamTypeId')),
17+
body('requiredMemberIds').isArray(),
18+
intMinZero(body('requiredMemberIds.*')),
19+
body('optionalMemberIds').isArray(),
20+
intMinZero(body('optionalMemberIds.*')),
21+
nonEmptyString(body('location').optional()),
22+
body('isOnline').isBoolean(),
23+
body('isInPerson').isBoolean(),
24+
nonEmptyString(body('zoomLink').optional()),
25+
nonEmptyString(body('docTemplateLink')).optional(),
26+
body('wbsNum'),
27+
body('meetingTimes').isArray(),
28+
intMinZero(body('meetingTimes.*')),
29+
validateInputs,
30+
DesignReviewsController.createDesignReview
31+
);
32+
33+
designReviewsRouter.post(
34+
'/:designReviewId/edit',
35+
isDate(body('dateScheduled')),
36+
nonEmptyString(body('teamTypeId')),
37+
body('requiredMembersIds').isArray(),
38+
intMinZero(body('requiredMembersIds.*')),
39+
body('optionalMembersIds').isArray(),
40+
intMinZero(body('optionalMembersIds.*')),
41+
body('isOnline').isBoolean(),
42+
body('isInPerson').isBoolean(),
43+
nonEmptyString(body('zoomLink')).isURL().optional(),
44+
nonEmptyString(body('location')).optional(),
45+
nonEmptyString(body('docTemplateLink')).isURL().optional(),
46+
isDesignReviewStatus(body('status')),
47+
body('attendees').isArray(),
48+
intMinZero(body('attendees.*')),
49+
body('meetingTimes').isArray(),
50+
intMinZero(body('meetingTimes.*')),
51+
validateInputs,
52+
DesignReviewsController.editDesignReviews
53+
);
54+
55+
export default designReviewsRouter;

src/backend/src/routes/users.routes.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import express from 'express';
33
import { body } from 'express-validator';
44
import UsersController from '../controllers/users.controllers';
55
import { validateInputs } from '../utils/utils';
6-
import { isRole, nonEmptyString } from '../utils/validation.utils';
6+
import { isRole, nonEmptyString, intMinZero } from '../utils/validation.utils';
77

88
const userRouter = express.Router();
99

@@ -32,6 +32,18 @@ userRouter.post(
3232
nonEmptyString(body('phoneNumber')),
3333
UsersController.setUserSecureSettings
3434
);
35+
36+
userRouter.post(
37+
'/schedule-settings/set',
38+
nonEmptyString(body('personalGmail')).isEmail(),
39+
nonEmptyString(body('personalZoomLink')).isURL(),
40+
body('availability').isArray(),
41+
intMinZero(body('availibility.*')),
42+
validateInputs,
43+
UsersController.setUserScheduleSettings
44+
);
45+
3546
userRouter.get('/:userId/secure-settings', UsersController.getUserSecureSettings);
3647
userRouter.get('/:userId/schedule-settings', UsersController.getUserScheduleSettings);
48+
3749
export default userRouter;

src/backend/src/services/design-review.services.ts renamed to src/backend/src/services/design-reviews.services.ts

Lines changed: 117 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,20 @@
1-
import { DesignReview, WbsNumber, isAdmin, isLeadership } from 'shared';
1+
import { Design_Review_Status, User } from '@prisma/client';
2+
import { DesignReview, WbsNumber, isAdmin, isLeadership, isNotLeadership } from 'shared';
23
import prisma from '../prisma/prisma';
34
import {
4-
AccessDeniedAdminOnlyException,
5-
DeletedException,
65
NotFoundException,
6+
AccessDeniedMemberException,
7+
DeletedException,
78
HttpException,
9+
AccessDeniedAdminOnlyException,
810
AccessDeniedException
911
} from '../utils/errors.utils';
10-
import { User, Design_Review_Status } from '@prisma/client';
11-
import designReviewQueryArgs from '../prisma-query-args/design-review.query-args';
12-
import { designReviewTransformer } from '../transformers/design-review.transformer';
12+
import { getUsers, getPrismaQueryUserIds } from '../utils/users.utils';
13+
import { validateMeetingTimes } from '../utils/design-reviews.utils';
14+
import designReviewQueryArgs from '../prisma-query-args/design-reviews.query-args';
15+
import { designReviewTransformer } from '../transformers/design-reviews.transformer';
1316
import { sendSlackDesignReviewNotification } from '../utils/slack.utils';
14-
15-
export default class DesignReviewService {
17+
export default class DesignReviewsService {
1618
/**
1719
* Gets all design reviews in the database
1820
* @returns All of the design reviews
@@ -207,4 +209,111 @@ export default class DesignReviewService {
207209

208210
return designReviewTransformer(designReview);
209211
}
212+
213+
/**
214+
* Edits a Design_Review in the database
215+
* @param user the user editing the design review (must be leadership)
216+
* @param designReviewId the id of the design review to edit
217+
* @param dateScheduled the date of the design review
218+
* @param teamTypeId the team that the design_review is for (software, electrical, etc.)
219+
* @param requiredMembersIds required members Ids for the design review
220+
* @param optionalMembersIds optional members Ids for the design review
221+
* @param isOnline is the design review online (IF TRUE: zoom link should be requried))
222+
* @param isInPerson is the design review in person (IF TRUE: location should be required)
223+
* @param zoomLink the zoom link for the design review meeting
224+
* @param location the location for the design review meeting
225+
* @param docTemplateLink the document template link for the design review
226+
* @param status see Design_Review_Status enum
227+
* @param attendees the attendees for the design review (should they have any relation to the other shit / can't edit this after STATUS: DONE)
228+
* @param meetingTimes meeting time must be between 0-83 (Monday 12am - Sunday 12am, 1hr minute increments)
229+
*/
230+
231+
static async editDesignReview(
232+
user: User,
233+
designReviewId: string,
234+
dateScheduled: Date,
235+
teamTypeId: string,
236+
requiredMembersIds: number[],
237+
optionalMembersIds: number[],
238+
isOnline: boolean,
239+
isInPerson: boolean,
240+
zoomLink: string | null,
241+
location: string | null,
242+
docTemplateLink: string | null,
243+
status: Design_Review_Status,
244+
attendees: number[],
245+
meetingTimes: number[]
246+
): Promise<DesignReview> {
247+
// verify user is allowed to edit work package
248+
if (isNotLeadership(user.role)) throw new AccessDeniedMemberException('edit design reviews');
249+
250+
// make sure the requiredMembersIds are not in the optionalMembers
251+
if (requiredMembersIds.length > 0 && requiredMembersIds.some((rMemberId) => optionalMembersIds.includes(rMemberId))) {
252+
throw new HttpException(400, 'required members cannot be in optional members');
253+
}
254+
255+
// make sure there is a zoom link if the design review is online
256+
if (isOnline && zoomLink === null) {
257+
throw new HttpException(400, 'zoom link is required for online design reviews');
258+
}
259+
// make sure there is a location if the design review is in person
260+
if (isInPerson && location === null) {
261+
throw new HttpException(400, 'location is required for in person design reviews');
262+
}
263+
264+
// throws if meeting times are not: consecutive and between 0-83
265+
meetingTimes = validateMeetingTimes(meetingTimes);
266+
267+
// docTemplateLink is required if the status is scheduled or done
268+
if (status === Design_Review_Status.SCHEDULED || status === Design_Review_Status.DONE) {
269+
if (docTemplateLink == null) {
270+
throw new HttpException(400, 'doc template link is required for scheduled and done design reviews');
271+
}
272+
}
273+
// validate the design review exists and is not deleted
274+
const originaldesignReview = await prisma.design_Review.findUnique({
275+
where: { designReviewId }
276+
});
277+
if (!originaldesignReview) throw new NotFoundException('Design Review', designReviewId);
278+
if (originaldesignReview.dateDeleted) throw new DeletedException('Design Review', designReviewId);
279+
280+
// validate the teamTypeId exists
281+
const teamType = await prisma.teamType.findUnique({
282+
where: { teamTypeId }
283+
});
284+
if (!teamType) throw new NotFoundException('Team Type', teamTypeId);
285+
286+
// throw if a user isn't found, then build prisma queries for connecting userIds
287+
const updatedRequiredMembers = getPrismaQueryUserIds(await getUsers(requiredMembersIds));
288+
const updatedOptionalMembers = getPrismaQueryUserIds(await getUsers(optionalMembersIds));
289+
const updatedAttendees = getPrismaQueryUserIds(await getUsers(attendees));
290+
291+
// actually try to update the design review
292+
const updateDesignReview = await prisma.design_Review.update({
293+
where: { designReviewId },
294+
...designReviewQueryArgs,
295+
data: {
296+
designReviewId,
297+
dateScheduled,
298+
meetingTimes,
299+
status,
300+
teamTypeId,
301+
requiredMembers: {
302+
set: updatedRequiredMembers
303+
},
304+
optionalMembers: {
305+
set: updatedOptionalMembers
306+
},
307+
location,
308+
isOnline,
309+
isInPerson,
310+
zoomLink,
311+
docTemplateLink,
312+
attendees: {
313+
set: updatedAttendees
314+
}
315+
}
316+
});
317+
return designReviewTransformer(updateDesignReview);
318+
}
210319
}

0 commit comments

Comments
 (0)