Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions src/backend/src/controllers/part-review.controllers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,55 @@ export default class PartReviewController {
}
}

static async createSubmission(req: Request, res: Response, next: NextFunction) {
try {
const { partId, name, notes } = req.body;
const submission = await PartReviewService.createSubmission(
partId,
req.currentUser,
req.organization.organizationId,
name,
notes
);
res.status(200).json(submission);
} catch (error: unknown) {
next(error);
}
}

static async updateSubmission(req: Request, res: Response, next: NextFunction) {
try {
const { submissionId } = req.params;
const { name, notes } = req.body;
const updatedSubmission = await PartReviewService.updateSubmission(
submissionId,
req.currentUser,
req.organization.organizationId,
name,
notes
);
res.status(200).json(updatedSubmission);
} catch (error: unknown) {
next(error);
}
}

static async uploadSubmissionFiles(req: Request, res: Response, next: NextFunction) {
try {
const { submissionId } = req.params;
const files = req.files as Express.Multer.File[];
const updatedSubmission = await PartReviewService.uploadSubmissionFiles(
submissionId,
req.currentUser,
req.organization.organizationId,
files
);
res.status(200).json(updatedSubmission);
} catch (error: unknown) {
next(error);
}
}

static async getAllPartTags(req: Request, res: Response, next: NextFunction) {
try {
const tags = await PartReviewService.getAllPartTags(req.organization.organizationId);
Expand Down
50 changes: 37 additions & 13 deletions src/backend/src/routes/parts.routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@ const upload = multer({ limits: { fileSize: 30000000 }, storage: memoryStorage()

const partsRouter = express.Router();

partsRouter.get('/:wbsNum', PartReviewController.getAllPartsForProject);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment here as on the last PR


partsRouter.post(
'/create',
nonEmptyString(body('wbsNum')),
Expand All @@ -24,21 +22,29 @@ partsRouter.post(
PartReviewController.createPart
);

partsRouter.post('/:partId/upload-preview', upload.single('image'), PartReviewController.uploadPreview);
partsRouter.post(
'/submission/create',
nonEmptyString(body('partId')),
nonEmptyString(body('name')),
body('notes').optional().isString(),
validateInputs,
PartReviewController.createSubmission
);

partsRouter.post(
'/:partId/update',
intMinZero(body('index')),
nonEmptyString(body('commonName')),
body('description').optional().isString(),
body('reviewStatus').custom((value) => Object.values(Review_Status).includes(value)),
body('tagIds').isArray(),
body('assigneeIds').isArray(),
'/submission/:submissionId/update',
nonEmptyString(body('name')),
body('notes').optional().isString(),
validateInputs,
PartReviewController.updatePart
PartReviewController.updateSubmission
);

partsRouter.post('/:partId/delete', PartReviewController.deletePart);
partsRouter.post(
'/submission/:submissionId/upload-files',
upload.array('files', 10),
validateInputs,
PartReviewController.uploadSubmissionFiles
);

partsRouter.get('/tags', PartReviewController.getAllPartTags);
partsRouter.get('/faqs', PartReviewController.getAllPartReviewFAQS);
Expand Down Expand Up @@ -120,13 +126,31 @@ partsRouter.post(
partsRouter.post('/common-mistake/:commonMistakeId/delete', PartReviewController.deleteCommonMistake);
partsRouter.post('/popup/:popupId/delete', PartReviewController.deletePartReviewPopup);

partsRouter.post('/reviewRequest/:reviewRequestId/delete', PartReviewController.deletePartReviewRequest);

partsRouter.post(
'/:partId/reviewRequest/create',
nonEmptyString(body('reviewerId')),
validateInputs,
PartReviewController.createPartReviewRequest
);

partsRouter.post('/reviewRequest/:reviewRequestId/delete', PartReviewController.deletePartReviewRequest);
partsRouter.post('/:partId/upload-preview', upload.single('image'), PartReviewController.uploadPreview);

partsRouter.post(
'/:partId/update',
intMinZero(body('index')),
nonEmptyString(body('commonName')),
body('description').optional().isString(),
body('reviewStatus').custom((value) => Object.values(Review_Status).includes(value)),
body('tagIds').isArray(),
body('assigneeIds').isArray(),
validateInputs,
PartReviewController.updatePart
);

partsRouter.post('/:partId/delete', PartReviewController.deletePart);

partsRouter.get('/:wbsNum', PartReviewController.getAllPartsForProject);

export default partsRouter;
121 changes: 118 additions & 3 deletions src/backend/src/services/part-review.services.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,21 +19,24 @@ import {
DeletedException,
HttpException,
NotFoundException,
AccessDeniedGuestException
AccessDeniedGuestException,
InvalidOrganizationException
} from '../utils/errors.utils';
import prisma from '../prisma/prisma';
import { getFaqQueryArgs } from '../prisma-query-args/faq.query-args';
import {
getPartQueryArgs,
getPartReviewQueryArgs,
getPartReviewRequestQueryArgs
getPartReviewRequestQueryArgs,
getPartSubmissionQueryArgs
} from '../prisma-query-args/part-review.query-args';
import { faqTransformer } from '../transformers/faq.transformer';
import {
partReviewRequestTransformer,
partsReviewCommonMistakeTransformer,
partTransformer,
partPreviewTransformer
partPreviewTransformer,
partSubmissionTransformer
} from '../transformers/part-review.transformer';
import { isUserPartOfTeams } from '../utils/teams.utils';
import { uploadFile } from '../utils/google-integration.utils';
Expand Down Expand Up @@ -238,6 +241,118 @@ export default class PartReviewService {
return partTransformer(deletedPart);
}

/**
* Creates a submission for a given part
* @param partId the part that the submission will be added to
* @param creator the creator
* @param organizationId the organization
* @param name the name of the submission
* @param notes optional notes
* @returns the created submission
*/
static async createSubmission(partId: string, creator: User, organizationId: string, name: string, notes?: string) {
const part = await prisma.part.findUnique({
where: { partId },
include: { project: { include: { wbsElement: true } } }
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we use queryargs for this

});
if (!part) throw new NotFoundException('Part', partId);
if (part.dateDeleted) throw new DeletedException('Part', partId);
if (part.project.wbsElement.organizationId !== organizationId) throw new InvalidOrganizationException('Part');

const submission = await prisma.partSubmission.create({
data: {
name,
notes,
part: {
connect: { partId }
},
userCreated: {
connect: { userId: creator.userId }
}
},
...getPartSubmissionQueryArgs(organizationId)
});

return partSubmissionTransformer(submission);
}

/**
* updates a given submission
* @param submissionId the submission being updated
* @param updater the user updating (must be the creator)
* @param organizationId the organization
* @param name the new name of the submission
* @param notes the new notes (optional)
* @returns the updated submission
*/
static async updateSubmission(submissionId: string, updater: User, organizationId: string, name: string, notes?: string) {
const submission = await prisma.partSubmission.findUnique({
where: { partSubmissionId: submissionId },
include: { part: { include: { project: { include: { wbsElement: true } } } } }
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here

});
if (!submission) throw new NotFoundException('Part Submission', submissionId);
if (submission.dateDeleted) throw new DeletedException('Part Submission', submissionId);
if (submission.part.project.wbsElement.organizationId !== organizationId)
throw new InvalidOrganizationException('Part Submission');

if (updater.userId !== submission.userCreatedId)
throw new AccessDeniedException('only submission creators can update submissions');

const updatedSubmission = await prisma.partSubmission.update({
where: { partSubmissionId: submissionId },
data: {
name,
notes: notes ?? submission.notes
},
...getPartSubmissionQueryArgs(organizationId)
});

return partSubmissionTransformer(updatedSubmission);
}

/**
* Uploads an array of files to a given submission
* @param submissionId the submission
* @param uploader the user uploading (must be creator)
* @param organizationId the organization
* @param files an array of files to upload
* @returns the updated submission
*/
static async uploadSubmissionFiles(
submissionId: string,
uploader: User,
organizationId: string,
files: Express.Multer.File[]
) {
const submission = await prisma.partSubmission.findUnique({
where: { partSubmissionId: submissionId },
include: { part: { include: { project: { include: { wbsElement: true } } } } }
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here

});
if (!submission) throw new NotFoundException('Part Submission', submissionId);
if (submission.dateDeleted) throw new DeletedException('Part Submission', submissionId);
if (submission.part.project.wbsElement.organizationId !== organizationId)
throw new InvalidOrganizationException('Part Submission');

if (uploader.userId !== submission.userCreatedId)
throw new AccessDeniedException('only submission creators can update submissions');

const fileIds = await Promise.all(
files.map(async (file) => {
return (await uploadFile(file)).id;
})
);

const updatedSubmission = await prisma.partSubmission.update({
where: { partSubmissionId: submissionId },
data: {
fileIds
},
...getPartSubmissionQueryArgs(organizationId)
});

return partSubmissionTransformer(updatedSubmission);
}

/**
* Uses the given organizationID to and returns an array of part tags
* @param organizationId the organization to get the parts for
Expand Down
1 change: 1 addition & 0 deletions src/backend/src/utils/errors.utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ export type ExceptionObjectNames =
| 'Graph Collection'
| 'Part Review'
| 'Part'
| 'Part Submission'
| 'Part Tag'
| 'common mistake'
| 'Review request'
Expand Down
Loading