Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

N21-939-assign-groups-to-course #4464

Merged
merged 20 commits into from
Oct 16, 2023
Merged
Show file tree
Hide file tree
Changes from all 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
3 changes: 3 additions & 0 deletions apps/server/src/apps/server.app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { TeamService } from '@src/modules/teams/service/team.service';
import { AccountValidationService } from '@src/modules/account/services/account.validation.service';
import { AccountUc } from '@src/modules/account/uc/account.uc';
import { CollaborativeStorageUc } from '@src/modules/collaborative-storage/uc/collaborative-storage.uc';
import { GroupService } from '@src/modules/group';
import { RocketChatService } from '@src/modules/rocketchat';
import { ServerModule } from '@src/modules/server';
import express from 'express';
Expand Down Expand Up @@ -82,6 +83,8 @@ async function bootstrap() {
feathersExpress.services['nest-team-service'] = nestApp.get(TeamService);
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-assignment
feathersExpress.services['nest-feathers-roster-service'] = nestApp.get(FeathersRosterService);
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access
feathersExpress.services['nest-group-service'] = nestApp.get(GroupService);
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-assignment
feathersExpress.services['nest-orm'] = orm;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { EntityManager } from '@mikro-orm/mongodb';
import { HttpStatus, INestApplication } from '@nestjs/common';
import { Test, TestingModule } from '@nestjs/testing';
import { Role, RoleName, SchoolEntity, SortOrder, SystemEntity, User } from '@shared/domain';
import { Role, RoleName, SchoolEntity, SchoolYearEntity, SortOrder, SystemEntity, User } from '@shared/domain';
import {
groupEntityFactory,
roleFactory,
schoolFactory,
schoolYearFactory,
systemFactory,
TestApiClient,
UserAndAccountTestFactory,
Expand All @@ -15,6 +16,7 @@ import { ClassEntity } from '@src/modules/class/entity';
import { classEntityFactory } from '@src/modules/class/entity/testing/factory/class.entity.factory';
import { ServerTestModule } from '@src/modules/server';
import { GroupEntity, GroupEntityTypes } from '../../entity';
import { ClassRootType } from '../../uc/dto/class-root-type';
import { ClassInfoSearchListResponse, ClassSortBy } from '../dto';

const baseRouteName = '/groups';
Expand Down Expand Up @@ -48,11 +50,13 @@ describe('Group (API)', () => {
const teacherRole: Role = roleFactory.buildWithId({ name: RoleName.TEACHER });
const teacherUser: User = userFactory.buildWithId({ school, roles: [teacherRole] });
const system: SystemEntity = systemFactory.buildWithId();
const schoolYear: SchoolYearEntity = schoolYearFactory.buildWithId();
const clazz: ClassEntity = classEntityFactory.buildWithId({
name: 'Group A',
schoolId: school._id,
teacherIds: [teacherUser._id],
source: undefined,
year: schoolYear.id,
});
const group: GroupEntity = groupEntityFactory.buildWithId({
name: 'Group B',
Expand All @@ -70,7 +74,17 @@ describe('Group (API)', () => {
],
});

await em.persistAndFlush([school, adminAccount, adminUser, teacherRole, teacherUser, system, clazz, group]);
await em.persistAndFlush([
school,
adminAccount,
adminUser,
teacherRole,
teacherUser,
system,
clazz,
group,
schoolYear,
]);
em.clear();

const adminClient = await testApiClient.login(adminAccount);
Expand All @@ -82,11 +96,12 @@ describe('Group (API)', () => {
system,
adminUser,
teacherUser,
schoolYear,
};
};

it('should return the classes of his school', async () => {
const { adminClient, group, clazz, system, adminUser, teacherUser } = await setup();
const { adminClient, group, clazz, system, adminUser, teacherUser, schoolYear } = await setup();

const response = await adminClient.get(`/class`).query({
skip: 0,
Expand All @@ -99,13 +114,18 @@ describe('Group (API)', () => {
total: 2,
data: [
{
id: group.id,
type: ClassRootType.GROUP,
name: group.name,
externalSourceName: system.displayName,
teachers: [adminUser.lastName],
},
{
id: clazz.id,
type: ClassRootType.CLASS,
name: clazz.gradeLevel ? `${clazz.gradeLevel}${clazz.name}` : clazz.name,
teachers: [teacherUser.lastName],
schoolYear: schoolYear.name,
},
],
skip: 0,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import { ClassRootType } from '../../../uc/dto/class-root-type';

export class ClassInfoResponse {
@ApiProperty()
id: string;

@ApiProperty({ enum: ClassRootType })
type: ClassRootType;

@ApiProperty()
name: string;

Expand All @@ -10,9 +17,15 @@ export class ClassInfoResponse {
@ApiProperty({ type: [String] })
teachers: string[];

@ApiPropertyOptional()
schoolYear?: string;

constructor(props: ClassInfoResponse) {
this.id = props.id;
this.type = props.type;
this.name = props.name;
this.externalSourceName = props.externalSourceName;
this.teachers = props.teachers;
this.schoolYear = props.schoolYear;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,12 @@ export class GroupResponseMapper {

private static mapToClassInfoToResponse(classInfo: ClassInfoDto): ClassInfoResponse {
const mapped = new ClassInfoResponse({
id: classInfo.id,
type: classInfo.type,
name: classInfo.name,
externalSourceName: classInfo.externalSourceName,
teachers: classInfo.teachers,
schoolYear: classInfo.schoolYear,
});

return mapped;
Expand Down
11 changes: 11 additions & 0 deletions apps/server/src/modules/group/uc/dto/class-info.dto.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,24 @@
import { ClassRootType } from './class-root-type';

export class ClassInfoDto {
id: string;

type: ClassRootType;

name: string;

externalSourceName?: string;

teachers: string[];

schoolYear?: string;

constructor(props: ClassInfoDto) {
this.id = props.id;
this.type = props.type;
this.name = props.name;
this.externalSourceName = props.externalSourceName;
this.teachers = props.teachers;
this.schoolYear = props.schoolYear;
}
}
4 changes: 4 additions & 0 deletions apps/server/src/modules/group/uc/dto/class-root-type.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export enum ClassRootType {
CLASS = 'class',
GROUP = 'group',
}
42 changes: 37 additions & 5 deletions apps/server/src/modules/group/uc/group.uc.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@ import { createMock, DeepMocked } from '@golevelup/ts-jest';
import { ObjectId } from '@mikro-orm/mongodb';
import { ForbiddenException } from '@nestjs/common';
import { Test, TestingModule } from '@nestjs/testing';
import { LegacySchoolDo, Page, Permission, SortOrder, User, UserDO } from '@shared/domain';
import { LegacySchoolDo, Page, Permission, SchoolYearEntity, SortOrder, User, UserDO } from '@shared/domain';
import {
groupFactory,
legacySchoolDoFactory,
roleDtoFactory,
schoolYearFactory,
setupEntities,
UserAndAccountTestFactory,
userDoFactory,
Expand All @@ -16,14 +17,15 @@ import { Action, AuthorizationContext, AuthorizationService } from '@src/modules
import { ClassService } from '@src/modules/class';
import { Class } from '@src/modules/class/domain';
import { classFactory } from '@src/modules/class/domain/testing/factory/class.factory';
import { LegacySchoolService } from '@src/modules/legacy-school';
import { LegacySchoolService, SchoolYearService } from '@src/modules/legacy-school';
import { RoleService } from '@src/modules/role';
import { RoleDto } from '@src/modules/role/service/dto/role.dto';
import { SystemDto, SystemService } from '@src/modules/system';
import { UserService } from '@src/modules/user';
import { Group } from '../domain';
import { GroupService } from '../service';
import { ClassInfoDto } from './dto';
import { ClassRootType } from './dto/class-root-type';
import { GroupUc } from './group.uc';

describe('GroupUc', () => {
Expand All @@ -37,6 +39,7 @@ describe('GroupUc', () => {
let roleService: DeepMocked<RoleService>;
let schoolService: DeepMocked<LegacySchoolService>;
let authorizationService: DeepMocked<AuthorizationService>;
let schoolYearService: DeepMocked<SchoolYearService>;

beforeAll(async () => {
module = await Test.createTestingModule({
Expand Down Expand Up @@ -70,6 +73,10 @@ describe('GroupUc', () => {
provide: AuthorizationService,
useValue: createMock<AuthorizationService>(),
},
{
provide: SchoolYearService,
useValue: createMock<SchoolYearService>(),
},
],
}).compile();

Expand All @@ -81,6 +88,7 @@ describe('GroupUc', () => {
roleService = module.get(RoleService);
schoolService = module.get(LegacySchoolService);
authorizationService = module.get(AuthorizationService);
schoolYearService = module.get(SchoolYearService);

await setupEntities();
});
Expand Down Expand Up @@ -144,7 +152,13 @@ describe('GroupUc', () => {
lastName: studentUser.lastName,
roles: [{ id: studentUser.roles[0].id, name: studentUser.roles[0].name }],
});
const clazz: Class = classFactory.build({ name: 'A', teacherIds: [teacherUser.id], source: 'LDAP' });
const schoolYear: SchoolYearEntity = schoolYearFactory.buildWithId();
const clazz: Class = classFactory.build({
name: 'A',
teacherIds: [teacherUser.id],
source: 'LDAP',
year: schoolYear.id,
});
const system: SystemDto = new SystemDto({
id: new ObjectId().toHexString(),
displayName: 'External System',
Expand Down Expand Up @@ -191,6 +205,7 @@ describe('GroupUc', () => {

throw new Error();
});
schoolYearService.findById.mockResolvedValue(schoolYear);

return {
teacherUser,
Expand All @@ -199,6 +214,7 @@ describe('GroupUc', () => {
group,
groupWithSystem,
system,
schoolYear,
};
};

Expand All @@ -219,23 +235,30 @@ describe('GroupUc', () => {

describe('when no pagination is given', () => {
it('should return all classes sorted by name', async () => {
const { teacherUser, clazz, group, groupWithSystem, system } = setup();
const { teacherUser, clazz, group, groupWithSystem, system, schoolYear } = setup();

const result: Page<ClassInfoDto> = await uc.findAllClassesForSchool(teacherUser.id, teacherUser.school.id);

expect(result).toEqual<Page<ClassInfoDto>>({
data: [
{
id: clazz.id,
name: clazz.gradeLevel ? `${clazz.gradeLevel}${clazz.name}` : clazz.name,
type: ClassRootType.CLASS,
externalSourceName: clazz.source,
teachers: [teacherUser.lastName],
schoolYear: schoolYear.name,
},
{
id: group.id,
name: group.name,
type: ClassRootType.GROUP,
teachers: [teacherUser.lastName],
},
{
id: groupWithSystem.id,
name: groupWithSystem.name,
type: ClassRootType.GROUP,
externalSourceName: system.displayName,
teachers: [teacherUser.lastName],
},
Expand All @@ -247,7 +270,7 @@ describe('GroupUc', () => {

describe('when sorting by external source name in descending order', () => {
it('should return all classes sorted by external source name in descending order', async () => {
const { teacherUser, clazz, group, groupWithSystem, system } = setup();
const { teacherUser, clazz, group, groupWithSystem, system, schoolYear } = setup();

const result: Page<ClassInfoDto> = await uc.findAllClassesForSchool(
teacherUser.id,
Expand All @@ -261,17 +284,24 @@ describe('GroupUc', () => {
expect(result).toEqual<Page<ClassInfoDto>>({
data: [
{
id: clazz.id,
name: clazz.gradeLevel ? `${clazz.gradeLevel}${clazz.name}` : clazz.name,
type: ClassRootType.CLASS,
externalSourceName: clazz.source,
teachers: [teacherUser.lastName],
schoolYear: schoolYear.name,
},
{
id: groupWithSystem.id,
name: groupWithSystem.name,
type: ClassRootType.GROUP,
externalSourceName: system.displayName,
teachers: [teacherUser.lastName],
},
{
id: group.id,
name: group.name,
type: ClassRootType.GROUP,
teachers: [teacherUser.lastName],
},
],
Expand All @@ -296,7 +326,9 @@ describe('GroupUc', () => {
expect(result).toEqual<Page<ClassInfoDto>>({
data: [
{
id: group.id,
name: group.name,
type: ClassRootType.GROUP,
teachers: [teacherUser.lastName],
},
],
Expand Down
14 changes: 10 additions & 4 deletions apps/server/src/modules/group/uc/group.uc.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { Injectable } from '@nestjs/common';
import { EntityId, LegacySchoolDo, Page, Permission, SortOrder, User, UserDO } from '@shared/domain';
import { EntityId, LegacySchoolDo, Page, Permission, SchoolYearEntity, SortOrder, User, UserDO } from '@shared/domain';
import { AuthorizationContextBuilder, AuthorizationService } from '@src/modules/authorization';
import { ClassService } from '@src/modules/class';
import { Class } from '@src/modules/class/domain';
import { LegacySchoolService } from '@src/modules/legacy-school';
import { LegacySchoolService, SchoolYearService } from '@src/modules/legacy-school';
import { RoleService } from '@src/modules/role';
import { RoleDto } from '@src/modules/role/service/dto/role.dto';
import { SystemDto, SystemService } from '@src/modules/system';
Expand All @@ -23,7 +23,8 @@ export class GroupUc {
private readonly userService: UserService,
private readonly roleService: RoleService,
private readonly schoolService: LegacySchoolService,
private readonly authorizationService: AuthorizationService
private readonly authorizationService: AuthorizationService,
private readonly schoolYearService: SchoolYearService
) {}

public async findAllClassesForSchool(
Expand Down Expand Up @@ -72,7 +73,12 @@ export class GroupUc {
clazz.teacherIds.map((teacherId: EntityId) => this.userService.findById(teacherId))
);

const mapped: ClassInfoDto = GroupUcMapper.mapClassToClassInfoDto(clazz, teachers);
let schoolYear: SchoolYearEntity | undefined;
if (clazz.year) {
schoolYear = await this.schoolYearService.findById(clazz.year);
}

const mapped: ClassInfoDto = GroupUcMapper.mapClassToClassInfoDto(clazz, teachers, schoolYear);

return mapped;
})
Expand Down
Loading
Loading