Skip to content

Commit 3bc3514

Browse files
committed
merge into final changes branch
2 parents 12f86a0 + 500044a commit 3bc3514

26 files changed

Lines changed: 1142 additions & 384 deletions

src/backend/src/controllers/part-review.controllers.ts

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ export default class PartReviewController {
99
const { wbsNum, indexNum } = req.params;
1010

1111
const wbsNumber: WbsNumber = validateWBS(wbsNum);
12-
const part = await PartReviewService.getPart(req.organization, wbsNumber, indexNum);
12+
const part = await PartReviewService.getPart(req.organization, req.currentUser, wbsNumber, indexNum);
1313
res.status(200).json(part);
1414
} catch (error: unknown) {
1515
next(error);
@@ -83,7 +83,7 @@ export default class PartReviewController {
8383

8484
static async updatePart(req: Request, res: Response, next: NextFunction) {
8585
try {
86-
const { index, commonName, description, reviewStatus, tagIds, assigneeIds } = req.body;
86+
const { index, commonName, description, reviewStatus, tagIds, assigneeIds, reviewerIds } = req.body;
8787
const { partId } = req.params;
8888
const part = await PartReviewService.updatePart(
8989
req.organization.organizationId,
@@ -94,7 +94,8 @@ export default class PartReviewController {
9494
description,
9595
reviewStatus,
9696
tagIds,
97-
assigneeIds
97+
assigneeIds,
98+
reviewerIds
9899
);
99100
res.status(200).json(part);
100101
} catch (error: unknown) {
@@ -147,6 +148,16 @@ export default class PartReviewController {
147148
}
148149
}
149150

151+
static async deleteReview(req: Request, res: Response, next: NextFunction) {
152+
try {
153+
const { reviewId } = req.params;
154+
await PartReviewService.deleteReview(reviewId, req.currentUser, req.organization.organizationId);
155+
res.status(200).json({ message: 'Successfully deleted review' });
156+
} catch (error: unknown) {
157+
next(error);
158+
}
159+
}
160+
150161
static async createSubmission(req: Request, res: Response, next: NextFunction) {
151162
try {
152163
const { partId, name, fileIds, notes } = req.body;

src/backend/src/prisma/seed-data/parts.seed.ts

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ const basicPart = (projectId: string, userCreatedId: string, assigneeIds: string
1111
index: 1,
1212
commonName: 'Basic Part',
1313
description: 'Basic part with all fields populated',
14-
previewImageId: 'https://NER.com/basicpart.jpg',
1514
status: 'IN_PROGRESS',
1615
createdAt: new Date('2025-01-01T10:00:00Z'),
1716
project: {
@@ -32,7 +31,6 @@ const partWithoutDescription = (projectId: string, userCreatedId: string, assign
3231
data: {
3332
index: 2,
3433
commonName: 'Part without description',
35-
previewImageId: 'https://NER.com/partwithoutdes.jpg',
3634
status: 'IN_PROGRESS',
3735
createdAt: new Date('2025-01-01T10:00:00Z'),
3836
project: {
@@ -74,7 +72,6 @@ const partWithEmptyHistory = (projectId: string, userCreatedId: string, assignee
7472
index: 4,
7573
commonName: 'Part with empty history',
7674
description: 'Basic part but with empty history',
77-
previewImageId: 'https://NER.com/partemptyhistory.jpg',
7875
status: 'IN_PROGRESS',
7976
createdAt: new Date('2025-01-01T10:00:00Z'),
8077
project: {
@@ -95,7 +92,6 @@ const partWithLongName = (projectId: string, userCreatedId: string, assigneeIds:
9592
index: 5,
9693
commonName: 'ThisPartHasANameThatIsWayTooLongAndMightCauseProblemsWithVisibilityOnTheWebsiteMaybeIDK',
9794
description: 'part with super long name',
98-
previewImageId: 'https://NER.com/partwithlongname.jpg',
9995
status: 'IN_PROGRESS',
10096
createdAt: new Date('2025-01-01T10:00:00Z'),
10197
project: {
@@ -116,7 +112,6 @@ const partIndexNegative = (projectId: string, userCreatedId: string, assigneeIds
116112
index: -1,
117113
commonName: 'Part with negative index',
118114
description: 'This parts index is negative',
119-
previewImageId: 'https://NER.com/negativeindexpart.jpg',
120115
status: 'IN_PROGRESS',
121116
createdAt: new Date('2025-01-01T10:00:00Z'),
122117
project: {
@@ -137,7 +132,6 @@ const partIndexZero = (projectId: string, userCreatedId: string, assigneeIds: st
137132
index: 0,
138133
commonName: 'Part with index 0',
139134
description: 'This parts index is 0',
140-
previewImageId: 'https://NER.com/zeroindexpart.jpg',
141135
status: 'IN_PROGRESS',
142136
createdAt: new Date('2025-01-01T10:00:00Z'),
143137
project: {
@@ -158,7 +152,6 @@ const partIndexLarge = (projectId: string, userCreatedId: string, assigneeIds: s
158152
index: 99999999,
159153
commonName: 'Part with very large index',
160154
description: 'This part index is very large',
161-
previewImageId: 'https://NER.com/largeindexpart.jpg',
162155
status: 'IN_PROGRESS',
163156
createdAt: new Date('2025-01-01T10:00:00Z'),
164157
project: {
@@ -179,7 +172,6 @@ const partReadyForReview = (projectId: string, userCreatedId: string, assigneeId
179172
index: 9,
180173
commonName: 'Part with READY_FOR_REVIEW status',
181174
description: 'This part is ready for review',
182-
previewImageId: 'https://NER.com/testimage.jpg',
183175
status: 'READY_FOR_REVIEW',
184176
createdAt: new Date('2025-01-01T10:00:00Z'),
185177
project: {
@@ -200,7 +192,6 @@ const partInReview = (projectId: string, userCreatedId: string, assigneeIds: str
200192
index: 10,
201193
commonName: 'Part with IN_REVIEW status',
202194
description: 'This part is in review',
203-
previewImageId: 'https://NER.com/testimage.jpg',
204195
status: 'IN_REVIEW',
205196
createdAt: new Date('2025-01-01T10:00:00Z'),
206197
project: {
@@ -221,7 +212,6 @@ const partReviewed = (projectId: string, userCreatedId: string, assigneeIds: str
221212
index: 11,
222213
commonName: 'Part with REVIEWED status',
223214
description: 'This part is reviewed.',
224-
previewImageId: 'https://NER.com/testimage.jpg',
225215
status: 'REVIEWED',
226216
createdAt: new Date('2025-01-01T10:00:00Z'),
227217
project: {
@@ -242,7 +232,6 @@ const partApproved = (projectId: string, userCreatedId: string, assigneeIds: str
242232
index: 12,
243233
commonName: 'Part with APPROVED status',
244234
description: 'This part is approved.',
245-
previewImageId: 'https://NER.com/testimage.jpg',
246235
status: 'APPROVED',
247236
createdAt: new Date('2025-01-01T10:00:00Z'),
248237
project: {
@@ -263,7 +252,6 @@ const partCurrentDate = (projectId: string, userCreatedId: string, assigneeIds:
263252
index: 13,
264253
commonName: 'Part with current date',
265254
description: 'This part has the current date.',
266-
previewImageId: 'https://NER.com/testimage.jpg',
267255
status: 'APPROVED',
268256
createdAt: new Date(),
269257
project: {
@@ -284,7 +272,6 @@ const partPastDate = (projectId: string, userCreatedId: string, assigneeIds: str
284272
index: 14,
285273
commonName: 'Part with past date',
286274
description: 'This part is old.',
287-
previewImageId: 'https://NER.com/testimage.jpg',
288275
status: 'APPROVED',
289276
createdAt: new Date('2000-01-01T00:00:00Z'),
290277
project: {
@@ -305,7 +292,6 @@ const partUnixEpochDate = (projectId: string, userCreatedId: string, assigneeIds
305292
index: 15,
306293
commonName: 'Part with date of Unix Epoch',
307294
description: 'This part is was made at the unix epoch.',
308-
previewImageId: 'https://NER.com/testimage.jpg',
309295
status: 'APPROVED',
310296
createdAt: new Date('1970-01-01T00:00:00Z'),
311297
project: {
@@ -326,7 +312,6 @@ const partFutureDate = (projectId: string, userCreatedId: string, assigneeIds: s
326312
index: 16,
327313
commonName: 'Part with date of future',
328314
description: 'This part is was made in the future.',
329-
previewImageId: 'https://NER.com/testimage.jpg',
330315
status: 'APPROVED',
331316
createdAt: new Date('2100-12-31T23:59:59Z'),
332317
project: {
@@ -347,7 +332,6 @@ const partLeapYearDate = (projectId: string, userCreatedId: string, assigneeIds:
347332
index: 17,
348333
commonName: 'Part with date with a leap year',
349334
description: 'This part is was during a leap year.',
350-
previewImageId: 'https://NER.com/testimage.jpg',
351335
status: 'APPROVED',
352336
createdAt: new Date('2024-02-29T12:00:00Z'),
353337
project: {

src/backend/src/routes/parts.routes.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ partsRouter.post(
5151
PartReviewController.updateReview
5252
);
5353

54+
partsRouter.post('/review/:reviewId/delete', PartReviewController.deleteReview);
55+
5456
partsRouter.post(
5557
'/submission/create',
5658
nonEmptyString(body('partId')),
@@ -61,6 +63,14 @@ partsRouter.post(
6163
PartReviewController.createSubmission
6264
);
6365

66+
partsRouter.post(
67+
'/submission/:submissionId/update',
68+
nonEmptyString(body('name')),
69+
body('notes').optional().isString(),
70+
validateInputs,
71+
PartReviewController.updateSubmission
72+
);
73+
6474
partsRouter.get('/tags', PartReviewController.getAllPartTags);
6575
partsRouter.get('/faqs', PartReviewController.getAllPartReviewFAQS);
6676

src/backend/src/services/part-review.services.ts

Lines changed: 112 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -47,11 +47,13 @@ import { sendPartAssignmentPopUp, sendPartReviewRequestPopUp } from '../utils/po
4747
export default class PartReviewService {
4848
/**
4949
* Uses the given partId to get the specific part and all of its constituent data
50+
* @param organization the organization to get the part for
51+
* @param user the user requesting the part
5052
* @param wbsNumber the wbsNum of the project this part is under
5153
* @param indexNum the index number of the part on this project
5254
* @returns a single Part
5355
*/
54-
static async getPart(organization: Organization, wbsNumber: WbsNumber, indexNum: string) {
56+
static async getPart(organization: Organization, user: User, wbsNumber: WbsNumber, indexNum: string) {
5557
const project: Project = await ProjectsService.getSingleProject(wbsNumber, organization);
5658
const index = Number(indexNum);
5759
const part = await prisma.part.findUnique({
@@ -67,6 +69,12 @@ export default class PartReviewService {
6769

6870
if (!part) throw new NotFoundException('Part', `projectId: ${project.id} and index number: ${indexNum}`);
6971

72+
part.submissions.forEach((submission) => {
73+
submission.reviews = submission.reviews.filter((review) => {
74+
return review.completedAt || user.userId === review.userCreatedId;
75+
});
76+
});
77+
7078
return partTransformer(part);
7179
}
7280
/**
@@ -232,7 +240,8 @@ export default class PartReviewService {
232240
description: string,
233241
reviewStatus: Review_Status,
234242
tagIds: string[],
235-
assigneeIds: string[]
243+
assigneeIds: string[],
244+
reviewerIds: string[]
236245
) {
237246
const part = await prisma.part.findUnique({
238247
where: { partId },
@@ -248,24 +257,63 @@ export default class PartReviewService {
248257

249258
if (!hasPermission) throw new AccessDeniedException('Only leadership and the part creator can update part data');
250259

251-
const updatedPart = await prisma.part.update({
252-
where: { partId },
253-
data: {
254-
index,
255-
commonName,
256-
description,
257-
status: reviewStatus,
258-
tags: {
259-
set: tagIds.map((partTagId) => ({ partTagId }))
260+
const editedPart = await prisma.$transaction(async (tx) => {
261+
const editedPart = await tx.part.update({
262+
where: { partId },
263+
data: {
264+
index,
265+
commonName,
266+
description,
267+
status: reviewStatus,
268+
tags: {
269+
set: tagIds.map((partTagId) => ({ partTagId }))
270+
},
271+
assignees: {
272+
set: assigneeIds.map((userId) => ({ userId }))
273+
}
260274
},
261-
assignees: {
262-
set: assigneeIds.map((userId) => ({ userId }))
263-
}
264-
},
265-
...getPartQueryArgs(organizationId)
275+
...getPartQueryArgs(organizationId)
276+
});
277+
278+
const reviewersToAdd = reviewerIds.filter(
279+
(id) => !editedPart.reviewRequests.some((reviewReq) => reviewReq.reviewerRequested.userId === id)
280+
);
281+
282+
await Promise.all(
283+
reviewersToAdd.map(async (id) => {
284+
return tx.partReviewRequest.create({
285+
data: {
286+
part: {
287+
connect: {
288+
partId: editedPart.partId
289+
}
290+
},
291+
requester: { connect: { userId: updater.userId } },
292+
reviewerRequested: { connect: { userId: id } }
293+
}
294+
});
295+
})
296+
);
297+
298+
const reviewRequestsToRemove = editedPart.reviewRequests.filter(
299+
(reviewReq) => !reviewerIds.includes(reviewReq.reviewerRequested.userId)
300+
);
301+
302+
await Promise.all(
303+
reviewRequestsToRemove.map(async (reviewReq) => {
304+
return tx.partReviewRequest.update({
305+
where: { partReviewRequestId: reviewReq.partReviewRequestId },
306+
data: {
307+
dateDeleted: new Date()
308+
}
309+
});
310+
})
311+
);
312+
313+
return editedPart;
266314
});
267315

268-
return partTransformer(updatedPart);
316+
return partTransformer(editedPart);
269317
}
270318

271319
static async deletePart(partId: string, deleter: User, organizationId: string) {
@@ -415,6 +463,37 @@ export default class PartReviewService {
415463
return partReviewTransformer(updatedReview);
416464
}
417465

466+
/**
467+
* Deletes a review
468+
* @param reviewId the review being deleted
469+
* @param deleter the user deleting (must be creator)
470+
* @param organizationId the organization
471+
*/
472+
static async deleteReview(reviewId: string, deleter: User, organizationId: string) {
473+
const review = await prisma.partReview.findUnique({
474+
where: { partReviewId: reviewId }
475+
});
476+
477+
if (!review) throw new NotFoundException('Part Review', reviewId);
478+
if (review.dateDeleted) throw new DeletedException('Part Review', reviewId);
479+
if (review.completedAt) throw new HttpException(409, 'Cannot delete a completed review');
480+
481+
if (deleter.userId !== review.userCreatedId) throw new AccessDeniedException('only review creators can delete reviews');
482+
483+
await prisma.partReview.update({
484+
where: { partReviewId: reviewId },
485+
data: {
486+
dateDeleted: new Date(),
487+
userDeleted: {
488+
connect: {
489+
userId: deleter.userId
490+
}
491+
}
492+
},
493+
...getPartReviewQueryArgs(organizationId)
494+
});
495+
}
496+
418497
/**
419498
* Creates a submission for a given part
420499
* @param partId the part that the submission will be added to
@@ -456,6 +535,22 @@ export default class PartReviewService {
456535
...getPartSubmissionQueryArgs(organizationId)
457536
});
458537

538+
await prisma.part.update({
539+
where: { partId },
540+
data: { status: Review_Status.READY_FOR_REVIEW }
541+
});
542+
543+
if (!part.previewImageId && fileIds.length > 0) {
544+
await prisma.part.update({
545+
where: {
546+
partId: part.partId
547+
},
548+
data: {
549+
previewImageId: fileIds[0]
550+
}
551+
});
552+
}
553+
459554
return partSubmissionTransformer(submission);
460555
}
461556

src/backend/src/utils/datetime.utils.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,6 @@
1010
*/
1111
export const transformDate = (date: Date) => {
1212
const month = date.getMonth() + 1 < 10 ? `0${date.getMonth() + 1}` : (date.getMonth() + 1).toString();
13-
const day = date.getDate() + 1 < 10 ? `0${date.getDate() + 1}` : (date.getDate() + 1).toString();
13+
const day = date.getDate() < 10 ? `0${date.getDate()}` : date.getDate().toString();
1414
return `${date.getFullYear().toString()}-${month}-${day}`;
1515
};

0 commit comments

Comments
 (0)