diff --git a/apps/server/src/modules/board/service/submission-item.service.spec.ts b/apps/server/src/modules/board/service/submission-item.service.spec.ts index 0dd2c9efbf9..fef2bf00ae6 100644 --- a/apps/server/src/modules/board/service/submission-item.service.spec.ts +++ b/apps/server/src/modules/board/service/submission-item.service.spec.ts @@ -1,9 +1,9 @@ import { createMock, DeepMocked } from '@golevelup/ts-jest'; -import { NotFoundException } from '@nestjs/common'; +import { NotFoundException, UnprocessableEntityException } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; import { SubmissionItem } from '@shared/domain'; import { ValidationError } from '@shared/common'; -import { setupEntities, userFactory } from '@shared/testing'; +import { richTextElementFactory, setupEntities, userFactory } from '@shared/testing'; import { cardFactory, submissionContainerElementFactory, @@ -108,7 +108,7 @@ describe(SubmissionItemService.name, () => { return { submissionContainer, submissionItem }; }; - it('should fetch the SubmissionContainer parent', async () => { + it('should fetch the parent', async () => { const { submissionItem } = setup(); await service.update(submissionItem, true); @@ -116,6 +116,14 @@ describe(SubmissionItemService.name, () => { expect(boardDoRepo.findParentOfId).toHaveBeenCalledWith(submissionItem.id); }); + it('should throw if parent is not SubmissionContainerElement', async () => { + const submissionItem = submissionItemFactory.build(); + const richTextElement = richTextElementFactory.build(); + boardDoRepo.findParentOfId.mockResolvedValueOnce(richTextElement); + + await expect(service.update(submissionItem, true)).rejects.toThrow(UnprocessableEntityException); + }); + it('should call bord repo to save submission item', async () => { const { submissionItem, submissionContainer } = setup(); diff --git a/apps/server/src/modules/board/service/submission-item.service.ts b/apps/server/src/modules/board/service/submission-item.service.ts index 990504357a7..0fc67ba694f 100644 --- a/apps/server/src/modules/board/service/submission-item.service.ts +++ b/apps/server/src/modules/board/service/submission-item.service.ts @@ -1,7 +1,7 @@ import { ObjectId } from 'bson'; -import { Injectable, NotFoundException } from '@nestjs/common'; +import { Injectable, NotFoundException, UnprocessableEntityException } from '@nestjs/common'; -import { EntityId, SubmissionContainerElement, SubmissionItem } from '@shared/domain'; +import { EntityId, isSubmissionContainerElement, SubmissionContainerElement, SubmissionItem } from '@shared/domain'; import { ValidationError } from '@shared/common'; import { BoardDoRepo } from '../repo'; @@ -42,13 +42,17 @@ export class SubmissionItemService { } async update(submissionItem: SubmissionItem, completed: boolean): Promise { - const parent = (await this.boardDoRepo.findParentOfId(submissionItem.id)) as SubmissionContainerElement; + const submissionContainterElement = await this.boardDoRepo.findParentOfId(submissionItem.id); + if (!isSubmissionContainerElement(submissionContainterElement)) { + throw new UnprocessableEntityException(); + } + const now = new Date(); - if (parent.dueDate && parent.dueDate < now) { + if (submissionContainterElement.dueDate && submissionContainterElement.dueDate < now) { throw new ValidationError('not allowed to save anymore'); } submissionItem.completed = completed; - await this.boardDoRepo.save(submissionItem, parent); + await this.boardDoRepo.save(submissionItem, submissionContainterElement); } } diff --git a/apps/server/src/modules/board/uc/element.uc.ts b/apps/server/src/modules/board/uc/element.uc.ts index 7289fdb6ce2..0dafd9eb98f 100644 --- a/apps/server/src/modules/board/uc/element.uc.ts +++ b/apps/server/src/modules/board/uc/element.uc.ts @@ -1,5 +1,12 @@ import { forwardRef, HttpException, HttpStatus, Inject, Injectable } from '@nestjs/common'; -import { AnyBoardDo, EntityId, SubmissionContainerElement, SubmissionItem, UserRoleEnum } from '@shared/domain'; +import { + AnyBoardDo, + EntityId, + isSubmissionContainerElement, + isSubmissionItem, + SubmissionItem, + UserRoleEnum, +} from '@shared/domain'; import { Logger } from '@src/core/logger'; import { AuthorizationService } from '@src/modules/authorization'; import { Action } from '@src/modules/authorization/types/action.enum'; @@ -33,23 +40,25 @@ export class ElementUc { contentElementId: EntityId, completed: boolean ): Promise { - const submissionContainer = (await this.elementService.findById(contentElementId)) as SubmissionContainerElement; + const submissionContainerElement = await this.elementService.findById(contentElementId); - if (!(submissionContainer instanceof SubmissionContainerElement)) + if (!isSubmissionContainerElement(submissionContainerElement)) { throw new HttpException( 'Cannot create submission-item for non submission-container-element', HttpStatus.UNPROCESSABLE_ENTITY ); + } - if (!submissionContainer.children.every((child) => child instanceof SubmissionItem)) + if (!submissionContainerElement.children.every((child) => isSubmissionItem(child))) { throw new HttpException( 'Children of submission-container-element must be of type submission-item', HttpStatus.UNPROCESSABLE_ENTITY ); + } - const userSubmissionExists = submissionContainer.children.find( - (item) => (item as SubmissionItem).userId === userId - ); + const userSubmissionExists = submissionContainerElement.children + .filter(isSubmissionItem) + .find((item) => item.userId === userId); if (userSubmissionExists) { throw new HttpException( 'User is not allowed to have multiple submission-items per submission-container-element', @@ -57,9 +66,9 @@ export class ElementUc { ); } - await this.checkPermission(userId, submissionContainer, Action.read, UserRoleEnum.STUDENT); + await this.checkPermission(userId, submissionContainerElement, Action.read, UserRoleEnum.STUDENT); - const submissionItem = await this.submissionItemService.create(userId, submissionContainer, { completed }); + const submissionItem = await this.submissionItemService.create(userId, submissionContainerElement, { completed }); return submissionItem; } diff --git a/apps/server/src/modules/board/uc/submission-item.uc.spec.ts b/apps/server/src/modules/board/uc/submission-item.uc.spec.ts index 7177201a5af..33bc8468fc9 100644 --- a/apps/server/src/modules/board/uc/submission-item.uc.spec.ts +++ b/apps/server/src/modules/board/uc/submission-item.uc.spec.ts @@ -194,29 +194,7 @@ describe(SubmissionItemUc.name, () => { it('should throw HttpException', async () => { const { teacher, fileEl } = setup(); - await expect(uc.findSubmissionItems(teacher.id, fileEl.id)).rejects.toThrow( - 'Id does not belong to a submission container' - ); - }); - }); - describe('when called with invalid submission container children', () => { - const setup = () => { - const teacher = userFactory.buildWithId(); - const fileEl = fileElementFactory.build(); - const submissionContainer = submissionContainerElementFactory.build({ - children: [fileEl], - }); - elementService.findById.mockResolvedValue(submissionContainer); - - return { teacher, submissionContainer }; - }; - - it('should throw HttpException', async () => { - const { teacher, submissionContainer } = setup(); - - await expect(uc.findSubmissionItems(teacher.id, submissionContainer.id)).rejects.toThrow( - 'Children of submission-container-element must be of type submission-item' - ); + await expect(uc.findSubmissionItems(teacher.id, fileEl.id)).rejects.toThrow('Id is not submission container'); }); }); }); diff --git a/apps/server/src/modules/board/uc/submission-item.uc.ts b/apps/server/src/modules/board/uc/submission-item.uc.ts index 6a17373818c..e59afa4b49b 100644 --- a/apps/server/src/modules/board/uc/submission-item.uc.ts +++ b/apps/server/src/modules/board/uc/submission-item.uc.ts @@ -2,7 +2,8 @@ import { ForbiddenException, forwardRef, HttpException, HttpStatus, Inject, Inje import { AnyBoardDo, EntityId, - SubmissionContainerElement, + isSubmissionContainerElement, + isSubmissionItem, SubmissionItem, UserBoardRoles, UserRoleEnum, @@ -29,22 +30,20 @@ export class SubmissionItemUc { userId: EntityId, submissionContainerId: EntityId ): Promise<{ submissionItems: SubmissionItem[]; users: UserBoardRoles[] }> { - const submissionContainer = await this.getSubmissionContainer(submissionContainerId); - await this.checkPermission(userId, submissionContainer, Action.read); + const submissionContainerElement = await this.elementService.findById(submissionContainerId); - let submissionItems = submissionContainer.children as SubmissionItem[]; - - if (!submissionItems.every((child) => child instanceof SubmissionItem)) { - throw new HttpException( - 'Children of submission-container-element must be of type submission-item', - HttpStatus.UNPROCESSABLE_ENTITY - ); + if (!isSubmissionContainerElement(submissionContainerElement)) { + throw new HttpException('Id is not submission container', HttpStatus.UNPROCESSABLE_ENTITY); } - const boardAuthorizable = await this.boardDoAuthorizableService.getBoardAuthorizable(submissionContainer); + await this.checkPermission(userId, submissionContainerElement, Action.read); + + let submissionItems = submissionContainerElement.children.filter(isSubmissionItem); + + const boardAuthorizable = await this.boardDoAuthorizableService.getBoardAuthorizable(submissionContainerElement); let users = boardAuthorizable.users.filter((user) => user.userRoleEnum === UserRoleEnum.STUDENT); - const isAuthorizedStudent = await this.isAuthorizedStudent(userId, submissionContainer); + const isAuthorizedStudent = await this.isAuthorizedStudent(userId, submissionContainerElement); if (isAuthorizedStudent) { submissionItems = submissionItems.filter((item) => item.userId === userId); users = []; @@ -86,18 +85,6 @@ export class SubmissionItemUc { return false; } - private async getSubmissionContainer(submissionContainerId: EntityId): Promise { - const submissionContainer = (await this.elementService.findById( - submissionContainerId - )) as SubmissionContainerElement; - - if (!(submissionContainer instanceof SubmissionContainerElement)) { - throw new HttpException('Id does not belong to a submission container', HttpStatus.UNPROCESSABLE_ENTITY); - } - - return submissionContainer; - } - private async checkPermission( userId: EntityId, boardDo: AnyBoardDo, diff --git a/apps/server/src/shared/domain/domainobject/board/submission-item.do.ts b/apps/server/src/shared/domain/domainobject/board/submission-item.do.ts index c21c2d60af4..cb072f37455 100644 --- a/apps/server/src/shared/domain/domainobject/board/submission-item.do.ts +++ b/apps/server/src/shared/domain/domainobject/board/submission-item.do.ts @@ -38,3 +38,7 @@ export interface SubmissionItemProps extends BoardCompositeProps { completed: boolean; userId: EntityId; } + +export function isSubmissionItem(reference: unknown): reference is SubmissionItem { + return reference instanceof SubmissionItem; +}