diff --git a/apps/server/src/modules/board/controller/api-test/submission-item-create.api.spec.ts b/apps/server/src/modules/board/controller/api-test/submission-item-create.api.spec.ts index 3e41ee48be0..49cd3af657c 100644 --- a/apps/server/src/modules/board/controller/api-test/submission-item-create.api.spec.ts +++ b/apps/server/src/modules/board/controller/api-test/submission-item-create.api.spec.ts @@ -114,9 +114,7 @@ describe('submission create (api)', () => { expect(result.id).toBeDefined(); expect(result.timestamps.createdAt).toBeDefined(); expect(result.timestamps.lastUpdatedAt).toBeDefined(); - expect(result.userData.userId).toBe(studentUser.id); - expect(result.userData.firstName).toBe('John'); - expect(result.userData.lastName).toBe('Mr Doe'); + expect(result.userId).toBe(studentUser.id); }); it('should actually create the submission item', async () => { diff --git a/apps/server/src/modules/board/controller/api-test/submission-item-lookup.api.spec.ts b/apps/server/src/modules/board/controller/api-test/submission-item-lookup.api.spec.ts index e32e04914f3..4686f953d29 100644 --- a/apps/server/src/modules/board/controller/api-test/submission-item-lookup.api.spec.ts +++ b/apps/server/src/modules/board/controller/api-test/submission-item-lookup.api.spec.ts @@ -15,7 +15,7 @@ import { userFactory, } from '@shared/testing'; import { ServerTestModule } from '@src/modules/server'; -import { SubmissionItemResponse } from '../dto'; +import { SubmissionsResponse } from '../dto'; const baseRouteName = '/board-submissions'; describe('submission item lookup (api)', () => { @@ -38,7 +38,7 @@ describe('submission item lookup (api)', () => { await app.close(); }); - describe('with teacher of two submission containers filled with submission items of 2 students', () => { + describe('when user is teacher and we have 2 submission containers filled with submission items from 2 students', () => { const setup = async () => { await cleanupCollections(em); @@ -110,6 +110,8 @@ describe('submission item lookup (api)', () => { item12, item21, item22, + studentUser1, + studentUser2, }; }; it('should return status 200', async () => { @@ -123,24 +125,38 @@ describe('submission item lookup (api)', () => { const { loggedInClient, submissionContainerNode1, item11, item12 } = await setup(); const response = await loggedInClient.get(`${submissionContainerNode1.id}`); - const body = response.body as SubmissionItemResponse[]; - expect(body.length).toBe(2); - expect(body.map((item) => item.id)).toContain(item11.id); - expect(body.map((item) => item.id)).toContain(item12.id); + const body = response.body as SubmissionsResponse; + const { submissionItemsResponse } = body; + expect(submissionItemsResponse.length).toBe(2); + expect(submissionItemsResponse.map((item) => item.id)).toContain(item11.id); + expect(submissionItemsResponse.map((item) => item.id)).toContain(item12.id); }); it('should return all items from container 2 as teacher', async () => { const { loggedInClient, submissionContainerNode2, item21, item22 } = await setup(); const response = await loggedInClient.get(`${submissionContainerNode2.id}`); - const body = response.body as SubmissionItemResponse[]; - expect(body.length).toBe(2); - expect(body.map((item) => item.id)).toContain(item21.id); - expect(body.map((item) => item.id)).toContain(item22.id); + const body = response.body as SubmissionsResponse; + const { submissionItemsResponse } = body; + expect(submissionItemsResponse.length).toBe(2); + expect(submissionItemsResponse.map((item) => item.id)).toContain(item21.id); + expect(submissionItemsResponse.map((item) => item.id)).toContain(item22.id); + }); + + it('should return list of students', async () => { + const { loggedInClient, submissionContainerNode1, studentUser1, studentUser2 } = await setup(); + + const response = await loggedInClient.get(`${submissionContainerNode1.id}`); + const body = response.body as SubmissionsResponse; + const { users } = body; + expect(users.length).toBe(2); + const userIds = users.map((user) => user.userId); + expect(userIds).toContain(studentUser1.id); + expect(userIds).toContain(studentUser2.id); }); }); - describe('with student of a submission container filled with 2 items', () => { + describe('when user is student and we have a submission container element filled with 2 submission items', () => { const setup = async () => { await cleanupCollections(em); @@ -194,13 +210,14 @@ describe('submission item lookup (api)', () => { const { loggedInClient, submissionContainerNode, item1 } = await setup(); const response = await loggedInClient.get(`${submissionContainerNode.id}`); - const body = response.body as SubmissionItemResponse[]; - expect(body.length).toBe(1); - expect(body[0].id).toBe(item1.id); + const body = response.body as SubmissionsResponse; + const { submissionItemsResponse } = body; + expect(submissionItemsResponse.length).toBe(1); + expect(submissionItemsResponse[0].id).toBe(item1.id); }); }); - describe('with invalid user', () => { + describe('when user is invalid', () => { const setup = async () => { await cleanupCollections(em); diff --git a/apps/server/src/modules/board/controller/board-submission.controller.ts b/apps/server/src/modules/board/controller/board-submission.controller.ts index 8f74b05df3f..56c6ae76ce2 100644 --- a/apps/server/src/modules/board/controller/board-submission.controller.ts +++ b/apps/server/src/modules/board/controller/board-submission.controller.ts @@ -3,15 +3,11 @@ import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'; import { ApiValidationError } from '@shared/common'; import { ICurrentUser } from '@src/modules/authentication'; import { Authenticate, CurrentUser } from '@src/modules/authentication/decorator/auth.decorator'; +import { SubmissionsResponse } from '@src/modules/board/controller/dto/submission-item/submissions.response'; import { CardUc } from '../uc'; import { ElementUc } from '../uc/element.uc'; import { SubmissionItemUc } from '../uc/submission-item.uc'; -import { - SubmissionContainerUrlParams, - SubmissionItemResponse, - SubmissionItemUrlParams, - UpdateSubmissionItemBodyParams, -} from './dto'; +import { SubmissionContainerUrlParams, SubmissionItemUrlParams, UpdateSubmissionItemBodyParams } from './dto'; import { SubmissionItemResponseMapper } from './mapper'; @ApiTags('Board Submission') @@ -25,17 +21,22 @@ export class BoardSubmissionController { ) {} @ApiOperation({ summary: 'Get a list of submission items by their parent container.' }) - @ApiResponse({ status: 200, type: [SubmissionItemResponse] }) + @ApiResponse({ status: 200, type: SubmissionsResponse }) @ApiResponse({ status: 400, type: ApiValidationError }) @ApiResponse({ status: 403, type: ForbiddenException }) @Get(':submissionContainerId') async getSubmissionItems( @CurrentUser() currentUser: ICurrentUser, @Param() urlParams: SubmissionContainerUrlParams - ): Promise { - const items = await this.submissionItemUc.findSubmissionItems(currentUser.userId, urlParams.submissionContainerId); + ): Promise { + const { submissionItems, users } = await this.submissionItemUc.findSubmissionItems( + currentUser.userId, + urlParams.submissionContainerId + ); const mapper = SubmissionItemResponseMapper.getInstance(); - return items.map((item) => mapper.mapToResponse(item)); + const response = mapper.mapToResponse(submissionItems, users); + + return response; } @ApiOperation({ summary: 'Update a single submission item.' }) diff --git a/apps/server/src/modules/board/controller/dto/submission-item/index.ts b/apps/server/src/modules/board/controller/dto/submission-item/index.ts index 200cdc81ea0..294c722dbef 100644 --- a/apps/server/src/modules/board/controller/dto/submission-item/index.ts +++ b/apps/server/src/modules/board/controller/dto/submission-item/index.ts @@ -2,4 +2,5 @@ export * from './submission-container.url.params'; export * from './create-submission-item.body.params'; export * from './submission-item.response'; export * from './submission-item.url.params'; +export * from './submissions.response'; export * from './update-submission-item.body.params'; diff --git a/apps/server/src/modules/board/controller/dto/submission-item/submission-item.response.ts b/apps/server/src/modules/board/controller/dto/submission-item/submission-item.response.ts index 02c3936d843..5b2fd522476 100644 --- a/apps/server/src/modules/board/controller/dto/submission-item/submission-item.response.ts +++ b/apps/server/src/modules/board/controller/dto/submission-item/submission-item.response.ts @@ -1,13 +1,12 @@ import { ApiProperty } from '@nestjs/swagger'; import { TimestampsResponse } from '../timestamps.response'; -import { UserDataResponse } from '../user-data.response'; export class SubmissionItemResponse { - constructor({ id, timestamps, completed, userData }: SubmissionItemResponse) { + constructor({ id, timestamps, completed, userId }: SubmissionItemResponse) { this.id = id; this.timestamps = timestamps; this.completed = completed; - this.userData = userData; + this.userId = userId; } @ApiProperty({ pattern: '[a-f0-9]{24}' }) @@ -19,6 +18,6 @@ export class SubmissionItemResponse { @ApiProperty() completed: boolean; - @ApiProperty() - userData: UserDataResponse; + @ApiProperty({ pattern: '[a-f0-9]{24}' }) + userId: string; } diff --git a/apps/server/src/modules/board/controller/dto/submission-item/submissions.response.ts b/apps/server/src/modules/board/controller/dto/submission-item/submissions.response.ts new file mode 100644 index 00000000000..1092421b0c3 --- /dev/null +++ b/apps/server/src/modules/board/controller/dto/submission-item/submissions.response.ts @@ -0,0 +1,19 @@ +import { SubmissionItemResponse, UserDataResponse } from '@src/modules/board/controller/dto'; +import { ApiProperty } from '@nestjs/swagger'; + +export class SubmissionsResponse { + constructor(submissionItemsResponse: SubmissionItemResponse[], users: UserDataResponse[]) { + this.submissionItemsResponse = submissionItemsResponse; + this.users = users; + } + + @ApiProperty({ + type: [SubmissionItemResponse], + }) + submissionItemsResponse: SubmissionItemResponse[]; + + @ApiProperty({ + type: [UserDataResponse], + }) + users: UserDataResponse[]; +} diff --git a/apps/server/src/modules/board/controller/element.controller.ts b/apps/server/src/modules/board/controller/element.controller.ts index 4048042f9cc..7954f9a8374 100644 --- a/apps/server/src/modules/board/controller/element.controller.ts +++ b/apps/server/src/modules/board/controller/element.controller.ts @@ -105,7 +105,7 @@ export class ElementController { bodyParams.completed ); const mapper = SubmissionItemResponseMapper.getInstance(); - const response = mapper.mapToResponse(submissionItem); + const response = mapper.mapSubmissionsToResponse(submissionItem); return response; } diff --git a/apps/server/src/modules/board/controller/mapper/submission-item-response.mapper.ts b/apps/server/src/modules/board/controller/mapper/submission-item-response.mapper.ts index c2c613da8c6..53efb37a482 100644 --- a/apps/server/src/modules/board/controller/mapper/submission-item-response.mapper.ts +++ b/apps/server/src/modules/board/controller/mapper/submission-item-response.mapper.ts @@ -1,5 +1,5 @@ -import { SubmissionItem } from '@shared/domain'; -import { SubmissionItemResponse, TimestampsResponse, UserDataResponse } from '../dto'; +import { SubmissionItem, UserBoardRoles } from '@shared/domain'; +import { SubmissionItemResponse, SubmissionsResponse, TimestampsResponse, UserDataResponse } from '../dto'; export class SubmissionItemResponseMapper { private static instance: SubmissionItemResponseMapper; @@ -12,7 +12,18 @@ export class SubmissionItemResponseMapper { return SubmissionItemResponseMapper.instance; } - public mapToResponse(submissionItem: SubmissionItem): SubmissionItemResponse { + public mapToResponse(submissionItems: SubmissionItem[], users: UserBoardRoles[]): SubmissionsResponse { + const submissionItemsResponse: SubmissionItemResponse[] = submissionItems.map((item) => + this.mapSubmissionsToResponse(item) + ); + const usersResponse: UserDataResponse[] = users.map((user) => this.mapUsersToResponse(user)); + + const response = new SubmissionsResponse(submissionItemsResponse, usersResponse); + + return response; + } + + public mapSubmissionsToResponse(submissionItem: SubmissionItem): SubmissionItemResponse { const result = new SubmissionItemResponse({ completed: submissionItem.completed, id: submissionItem.id, @@ -20,14 +31,18 @@ export class SubmissionItemResponseMapper { lastUpdatedAt: submissionItem.updatedAt, createdAt: submissionItem.createdAt, }), - userData: new UserDataResponse({ - // TODO: put valid user info here which comes from the submission owner - firstName: 'John', - lastName: 'Mr Doe', - userId: submissionItem.userId, - }), + userId: submissionItem.userId, }); return result; } + + private mapUsersToResponse(user: UserBoardRoles) { + const result = new UserDataResponse({ + userId: user.userId, + firstName: user.firstName || '', + lastName: user.lastName || '', + }); + return result; + } } diff --git a/apps/server/src/modules/board/service/board-do-authorizable.service.spec.ts b/apps/server/src/modules/board/service/board-do-authorizable.service.spec.ts index 90fb5ebf243..752d075b06b 100644 --- a/apps/server/src/modules/board/service/board-do-authorizable.service.spec.ts +++ b/apps/server/src/modules/board/service/board-do-authorizable.service.spec.ts @@ -102,6 +102,9 @@ describe(BoardDoAuthorizableService.name, () => { teacherId: teacher.id, substitutionTeacherId: substitutionTeacher.id, studentIds: students.map((s) => s.id), + teacher, + substitutionTeacher, + students, }; }; @@ -131,6 +134,33 @@ describe(BoardDoAuthorizableService.name, () => { expect(userPermissions[studentIds[2]]).toEqual([BoardRoles.READER]); expect(userRoleEnums[studentIds[2]]).toEqual(UserRoleEnum.STUDENT); }); + + it('should return the users with their names', async () => { + const { board, teacher, substitutionTeacher, students } = setup(); + + const boardDoAuthorizable = await service.getBoardAuthorizable(board); + const firstNames = boardDoAuthorizable.users.reduce((map, user) => { + map[user.userId] = user.firstName; + return map; + }, {}); + + const lastNames = boardDoAuthorizable.users.reduce((map, user) => { + map[user.userId] = user.lastName; + return map; + }, {}); + + expect(boardDoAuthorizable.users).toHaveLength(5); + expect(firstNames[teacher.id]).toEqual(teacher.firstName); + expect(lastNames[teacher.id]).toEqual(teacher.lastName); + expect(firstNames[substitutionTeacher.id]).toEqual(substitutionTeacher.firstName); + expect(lastNames[substitutionTeacher.id]).toEqual(substitutionTeacher.lastName); + expect(firstNames[students[0].id]).toEqual(students[0].firstName); + expect(lastNames[students[0].id]).toEqual(students[0].lastName); + expect(firstNames[students[1].id]).toEqual(students[1].firstName); + expect(lastNames[students[1].id]).toEqual(students[1].lastName); + expect(firstNames[students[2].id]).toEqual(students[2].firstName); + expect(lastNames[students[2].id]).toEqual(students[2].lastName); + }); }); describe('when trying to create a boardDoAuthorizable on a column without a columnboard as root', () => { diff --git a/apps/server/src/modules/board/service/board-do-authorizable.service.ts b/apps/server/src/modules/board/service/board-do-authorizable.service.ts index bc6c5c219e3..7b8b653f9cf 100644 --- a/apps/server/src/modules/board/service/board-do-authorizable.service.ts +++ b/apps/server/src/modules/board/service/board-do-authorizable.service.ts @@ -49,14 +49,32 @@ export class BoardDoAuthorizableService implements AuthorizationLoaderService { private mapCourseUsersToUsergroup(course: Course): UserBoardRoles[] { const users = [ - ...course.getTeacherIds().map((userId) => { - return { userId, roles: [BoardRoles.EDITOR], userRoleEnum: UserRoleEnum.TEACHER }; + ...course.getTeachersList().map((user) => { + return { + userId: user.id, + firstName: user.firstName, + lastName: user.lastName, + roles: [BoardRoles.EDITOR], + userRoleEnum: UserRoleEnum.TEACHER, + }; }), - ...course.getSubstitutionTeacherIds().map((userId) => { - return { userId, roles: [BoardRoles.EDITOR], userRoleEnum: UserRoleEnum.SUBSTITUTION_TEACHER }; + ...course.getSubstitutionTeachersList().map((user) => { + return { + userId: user.id, + firstName: user.firstName, + lastName: user.lastName, + roles: [BoardRoles.EDITOR], + userRoleEnum: UserRoleEnum.SUBSTITUTION_TEACHER, + }; }), - ...course.getStudentIds().map((userId) => { - return { userId, roles: [BoardRoles.READER], userRoleEnum: UserRoleEnum.STUDENT }; + ...course.getStudentsList().map((user) => { + return { + userId: user.id, + firstName: user.firstName, + lastName: user.lastName, + roles: [BoardRoles.READER], + userRoleEnum: UserRoleEnum.STUDENT, + }; }), ]; return users; 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 8e47bd23013..7177201a5af 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 @@ -1,4 +1,3 @@ -import { ObjectId } from 'bson'; import { createMock, DeepMocked } from '@golevelup/ts-jest'; import { Test, TestingModule } from '@nestjs/testing'; import { BoardDoAuthorizable, BoardRoles, UserRoleEnum } from '@shared/domain'; @@ -54,9 +53,6 @@ describe(SubmissionItemUc.name, () => { authorizationService = module.get(AuthorizationService); authorizationService.checkPermission.mockImplementation(() => {}); boardDoAuthorizableService = module.get(BoardDoAuthorizableService); - boardDoAuthorizableService.getBoardAuthorizable.mockResolvedValue( - new BoardDoAuthorizable({ users: [], id: new ObjectId().toHexString() }) - ); elementService = module.get(ContentElementService); submissionItemService = module.get(SubmissionItemService); await setupEntities(); @@ -102,9 +98,14 @@ describe(SubmissionItemUc.name, () => { it('student1 should only get their own submission item', async () => { const { user1, submissionContainerEl, submissionItem1 } = setup(); - const items = await uc.findSubmissionItems(user1.id, submissionContainerEl.id); - expect(items.length).toBe(1); - expect(items[0]).toStrictEqual(submissionItem1); + const { submissionItems } = await uc.findSubmissionItems(user1.id, submissionContainerEl.id); + expect(submissionItems.length).toBe(1); + expect(submissionItems[0]).toStrictEqual(submissionItem1); + }); + it('student should not get a list of users', async () => { + const { user1, submissionContainerEl } = setup(); + const { users } = await uc.findSubmissionItems(user1.id, submissionContainerEl.id); + expect(users.length).toBe(0); }); }); describe('when user is a teacher', () => { @@ -140,10 +141,15 @@ describe(SubmissionItemUc.name, () => { it('teacher should get all submission items', async () => { const { teacher, submissionContainerEl, submissionItem1, submissionItem2 } = setup(); - const items = await uc.findSubmissionItems(teacher.id, submissionContainerEl.id); - expect(items.length).toBe(2); - expect(items.map((item) => item.id)).toContain(submissionItem1.id); - expect(items.map((item) => item.id)).toContain(submissionItem2.id); + const { submissionItems } = await uc.findSubmissionItems(teacher.id, submissionContainerEl.id); + expect(submissionItems.length).toBe(2); + expect(submissionItems.map((item) => item.id)).toContain(submissionItem1.id); + expect(submissionItems.map((item) => item.id)).toContain(submissionItem2.id); + }); + it('teacher should get list of students', async () => { + const { teacher, submissionContainerEl } = setup(); + const { users } = await uc.findSubmissionItems(teacher.id, submissionContainerEl.id); + expect(users.length).toBe(2); }); }); describe('when user has not an authorized role', () => { 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 ec15b9322e3..6a17373818c 100644 --- a/apps/server/src/modules/board/uc/submission-item.uc.ts +++ b/apps/server/src/modules/board/uc/submission-item.uc.ts @@ -1,5 +1,12 @@ import { ForbiddenException, forwardRef, HttpException, HttpStatus, Inject, Injectable } from '@nestjs/common'; -import { AnyBoardDo, EntityId, SubmissionContainerElement, SubmissionItem, UserRoleEnum } from '@shared/domain'; +import { + AnyBoardDo, + EntityId, + SubmissionContainerElement, + SubmissionItem, + UserBoardRoles, + 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'; @@ -18,7 +25,10 @@ export class SubmissionItemUc { this.logger.setContext(SubmissionItemUc.name); } - async findSubmissionItems(userId: EntityId, submissionContainerId: EntityId): Promise { + async findSubmissionItems( + userId: EntityId, + submissionContainerId: EntityId + ): Promise<{ submissionItems: SubmissionItem[]; users: UserBoardRoles[] }> { const submissionContainer = await this.getSubmissionContainer(submissionContainerId); await this.checkPermission(userId, submissionContainer, Action.read); @@ -31,12 +41,16 @@ export class SubmissionItemUc { ); } + const boardAuthorizable = await this.boardDoAuthorizableService.getBoardAuthorizable(submissionContainer); + let users = boardAuthorizable.users.filter((user) => user.userRoleEnum === UserRoleEnum.STUDENT); + const isAuthorizedStudent = await this.isAuthorizedStudent(userId, submissionContainer); if (isAuthorizedStudent) { submissionItems = submissionItems.filter((item) => item.userId === userId); + users = []; } - return submissionItems; + return { submissionItems, users }; } async updateSubmissionItem( diff --git a/apps/server/src/shared/domain/domainobject/board/types/board-do-authorizable.ts b/apps/server/src/shared/domain/domainobject/board/types/board-do-authorizable.ts index d40a41d5dfb..86fb303471f 100644 --- a/apps/server/src/shared/domain/domainobject/board/types/board-do-authorizable.ts +++ b/apps/server/src/shared/domain/domainobject/board/types/board-do-authorizable.ts @@ -15,6 +15,8 @@ export enum UserRoleEnum { } export interface UserBoardRoles { + firstName?: string; + lastName?: string; roles: BoardRoles[]; userId: EntityId; userRoleEnum: UserRoleEnum; diff --git a/apps/server/src/shared/domain/entity/course.entity.spec.ts b/apps/server/src/shared/domain/entity/course.entity.spec.ts index ced3a129e2c..4a1ff02bdd6 100644 --- a/apps/server/src/shared/domain/entity/course.entity.spec.ts +++ b/apps/server/src/shared/domain/entity/course.entity.spec.ts @@ -323,4 +323,85 @@ describe('CourseEntity', () => { }); }); }); + + describe('getStudentsList is called', () => { + const setup = () => { + const students = userFactory.buildListWithId(2); + const course = courseFactory.build({ students }); + return { course, students }; + }; + it('should return the students of the course', () => { + const { course, students } = setup(); + const [student1, student2] = students; + + const result = course.getStudentsList(); + + expect(result.length).toEqual(2); + expect(result[0].id).toEqual(student1.id); + expect(result[0].firstName).toEqual(student1.firstName); + expect(result[0].lastName).toEqual(student1.lastName); + expect(result[1].id).toEqual(student2.id); + }); + it('should return an empty array if no students are in the course', () => { + const course = courseFactory.build({ students: [] }); + + const result = course.getStudentsList(); + + expect(result.length).toEqual(0); + }); + }); + + describe('getTeachersList is called', () => { + const setup = () => { + const teachers = userFactory.buildListWithId(2); + const course = courseFactory.build({ teachers }); + return { course, teachers }; + }; + it('should return the students of the course', () => { + const { course, teachers } = setup(); + const [teacher1, teacher2] = teachers; + + const result = course.getTeachersList(); + + expect(result.length).toEqual(2); + expect(result[0].id).toEqual(teacher1.id); + expect(result[0].firstName).toEqual(teacher1.firstName); + expect(result[0].lastName).toEqual(teacher1.lastName); + expect(result[1].id).toEqual(teacher2.id); + }); + it('should return an empty array if no teachers are in the course', () => { + const course = courseFactory.build({ teachers: [] }); + + const result = course.getTeachersList(); + + expect(result.length).toEqual(0); + }); + }); + + describe('getSubstitutionTeacherList is called', () => { + const setup = () => { + const substitutionTeachers = userFactory.buildListWithId(2); + const course = courseFactory.build({ substitutionTeachers }); + return { course, substitutionTeachers }; + }; + it('should return the substitutionTeachers of the course', () => { + const { course, substitutionTeachers } = setup(); + const [substitutionTeacher1, substitutionTeacher2] = substitutionTeachers; + + const result = course.getSubstitutionTeachersList(); + + expect(result.length).toEqual(2); + expect(result[0].id).toEqual(substitutionTeacher1.id); + expect(result[0].firstName).toEqual(substitutionTeacher1.firstName); + expect(result[0].lastName).toEqual(substitutionTeacher1.lastName); + expect(result[1].id).toEqual(substitutionTeacher2.id); + }); + it('should return an empty array if no substitutionTeachers are in the course', () => { + const course = courseFactory.build({ substitutionTeachers: [] }); + + const result = course.getSubstitutionTeachersList(); + + expect(result.length).toEqual(0); + }); + }); }); diff --git a/apps/server/src/shared/domain/entity/course.entity.ts b/apps/server/src/shared/domain/entity/course.entity.ts index 7bacf7443fc..1aea75aa3c0 100644 --- a/apps/server/src/shared/domain/entity/course.entity.ts +++ b/apps/server/src/shared/domain/entity/course.entity.ts @@ -36,6 +36,14 @@ const enum CourseFeatures { VIDEOCONFERENCE = 'videoconference', } +export class UsersList { + id!: string; + + firstName!: string; + + lastName!: string; +} + @Entity({ tableName: 'courses' }) export class Course extends BaseEntityWithTimestamps @@ -103,21 +111,21 @@ export class Course } public getStudentIds(): EntityId[] { - const studentIds = this.extractIds(this.students); + const studentIds = Course.extractIds(this.students); return studentIds; } public getTeacherIds(): EntityId[] { - const teacherIds = this.extractIds(this.teachers); + const teacherIds = Course.extractIds(this.teachers); return teacherIds; } public getSubstitutionTeacherIds(): EntityId[] { - const substitutionTeacherIds = this.extractIds(this.substitutionTeachers); + const substitutionTeacherIds = Course.extractIds(this.substitutionTeachers); return substitutionTeacherIds; } - private extractIds(users: Collection): EntityId[] { + private static extractIds(users: Collection): EntityId[] { if (!users) { throw new InternalServerErrorException( `Students, teachers or stubstitution is undefined. The course needs to be populated` @@ -130,6 +138,44 @@ export class Course return ids; } + public getStudentsList(): UsersList[] { + const users = this.students.getItems(); + if (users.length) { + const usersList = Course.extractUserList(users); + return usersList; + } + return []; + } + + public getTeachersList(): UsersList[] { + const users = this.teachers.getItems(); + if (users.length) { + const usersList = Course.extractUserList(users); + return usersList; + } + return []; + } + + public getSubstitutionTeachersList(): UsersList[] { + const users = this.substitutionTeachers.getItems(); + if (users.length) { + const usersList = Course.extractUserList(users); + return usersList; + } + return []; + } + + private static extractUserList(users: User[]): UsersList[] { + const usersList: UsersList[] = users.map((user) => { + return { + id: user.id, + firstName: user.firstName, + lastName: user.lastName, + }; + }); + return usersList; + } + public isUserSubstitutionTeacher(user: User): boolean { const isSubstitutionTeacher = this.substitutionTeachers.contains(user); diff --git a/apps/server/src/shared/repo/course/course.repo.integration.spec.ts b/apps/server/src/shared/repo/course/course.repo.integration.spec.ts index eff1cadc3f6..42df9c6ba24 100644 --- a/apps/server/src/shared/repo/course/course.repo.integration.spec.ts +++ b/apps/server/src/shared/repo/course/course.repo.integration.spec.ts @@ -396,6 +396,27 @@ describe('course repo', () => { expect(foundCourse.courseGroups.isInitialized()).toEqual(true); expect(foundCourse.courseGroups[0].id).toEqual(courseGroup.id); }); + + it('should populate course teachers, substitute teachers and students', async () => { + const teacher = userFactory.buildWithId(); + const substitutionTeacher = userFactory.buildWithId(); + const student = userFactory.buildWithId(); + + const course = courseFactory.buildWithId({ + teachers: [teacher], + substitutionTeachers: [substitutionTeacher], + students: [student], + }); + await em.persistAndFlush([course, teacher, substitutionTeacher, student]); + em.clear(); + + const foundCourse = await repo.findById(course.id); + expect(foundCourse.courseGroups.isInitialized()).toEqual(true); + + expect(foundCourse.teachers[0].id).toEqual(teacher.id); + expect(foundCourse.substitutionTeachers[0].id).toEqual(substitutionTeacher.id); + expect(foundCourse.students[0].id).toEqual(student.id); + }); }); describe('unset optional property', () => { diff --git a/apps/server/src/shared/repo/course/course.repo.ts b/apps/server/src/shared/repo/course/course.repo.ts index 3f973dcc7d3..b673dbfded8 100644 --- a/apps/server/src/shared/repo/course/course.repo.ts +++ b/apps/server/src/shared/repo/course/course.repo.ts @@ -63,7 +63,7 @@ export class CourseRepo extends BaseRepo { async findById(id: EntityId, populate = true): Promise { const course = await super.findById(id); if (populate) { - await this._em.populate(course, ['courseGroups', 'teachers']); + await this._em.populate(course, ['courseGroups', 'teachers', 'substitutionTeachers', 'students']); } return course; }