Merge pull request #184 from SELab-2/fix/183-post-assignment-fix

fix: POST op assignment
This commit is contained in:
Adriaan J. 2025-04-19 14:19:44 +02:00 committed by GitHub
commit c649fa15c4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
30 changed files with 282 additions and 1180 deletions

View file

@ -16,11 +16,11 @@
"test:unit": "vitest --run" "test:unit": "vitest --run"
}, },
"dependencies": { "dependencies": {
"@mikro-orm/core": "6.4.9", "@mikro-orm/core": "6.4.12",
"@mikro-orm/knex": "6.4.9", "@mikro-orm/knex": "6.4.12",
"@mikro-orm/postgresql": "6.4.9", "@mikro-orm/postgresql": "6.4.12",
"@mikro-orm/reflection": "6.4.9", "@mikro-orm/reflection": "6.4.12",
"@mikro-orm/sqlite": "6.4.9", "@mikro-orm/sqlite": "6.4.12",
"axios": "^1.8.2", "axios": "^1.8.2",
"cors": "^2.8.5", "cors": "^2.8.5",
"cross": "^1.0.0", "cross": "^1.0.0",
@ -43,7 +43,7 @@
"winston-loki": "^6.1.3" "winston-loki": "^6.1.3"
}, },
"devDependencies": { "devDependencies": {
"@mikro-orm/cli": "6.4.9", "@mikro-orm/cli": "6.4.12",
"@types/cors": "^2.8.17", "@types/cors": "^2.8.17",
"@types/express": "^5.0.0", "@types/express": "^5.0.0",
"@types/js-yaml": "^4.0.9", "@types/js-yaml": "^4.0.9",

View file

@ -69,8 +69,8 @@ export async function getAllGroupsHandler(req: Request, res: Response): Promise<
export async function createGroupHandler(req: Request, res: Response): Promise<void> { export async function createGroupHandler(req: Request, res: Response): Promise<void> {
const classid = req.params.classid; const classid = req.params.classid;
const assignmentId = Number(req.params.assignmentid); const assignmentId = Number(req.params.assignmentid);
const members = req.body.members;
requireFields({ classid, assignmentId }); requireFields({ classid, assignmentId, members });
if (isNaN(assignmentId)) { if (isNaN(assignmentId)) {
throw new BadRequestException('Assignment id must be a number'); throw new BadRequestException('Assignment id must be a number');

View file

@ -4,7 +4,7 @@ import { Class } from '../../entities/classes/class.entity.js';
export class AssignmentRepository extends DwengoEntityRepository<Assignment> { export class AssignmentRepository extends DwengoEntityRepository<Assignment> {
public async findByClassAndId(within: Class, id: number): Promise<Assignment | null> { public async findByClassAndId(within: Class, id: number): Promise<Assignment | null> {
return this.findOne({ within: within, id: id }); return this.findOne({ within: within, id: id }, { populate: ['groups', 'groups.members'] });
} }
public async findByClassIdAndAssignmentId(withinClass: string, id: number): Promise<Assignment | null> { public async findByClassIdAndAssignmentId(withinClass: string, id: number): Promise<Assignment | null> {
return this.findOne({ within: { classId: withinClass }, id: id }); return this.findOne({ within: { classId: withinClass }, id: id });
@ -23,7 +23,7 @@ export class AssignmentRepository extends DwengoEntityRepository<Assignment> {
}); });
} }
public async findAllAssignmentsInClass(within: Class): Promise<Assignment[]> { public async findAllAssignmentsInClass(within: Class): Promise<Assignment[]> {
return this.findAll({ where: { within: within } }); return this.findAll({ where: { within: within }, populate: ['groups', 'groups.members'] });
} }
public async deleteByClassAndId(within: Class, id: number): Promise<void> { public async deleteByClassAndId(within: Class, id: number): Promise<void> {
return this.deleteWhere({ within: within, id: id }); return this.deleteWhere({ within: within, id: id });

View file

@ -14,7 +14,7 @@ export class Assignment {
}) })
within!: Class; within!: Class;
@PrimaryKey({ type: 'number', autoincrement: true }) @PrimaryKey({ type: 'integer', autoincrement: true })
id?: number; id?: number;
@Property({ type: 'string' }) @Property({ type: 'string' })
@ -35,5 +35,5 @@ export class Assignment {
entity: () => Group, entity: () => Group,
mappedBy: 'assignment', mappedBy: 'assignment',
}) })
groups!: Collection<Group>; groups: Collection<Group> = new Collection<Group>(this);
} }

View file

@ -7,17 +7,23 @@ import { GroupRepository } from '../../data/assignments/group-repository.js';
repository: () => GroupRepository, repository: () => GroupRepository,
}) })
export class Group { export class Group {
/*
WARNING: Don't move the definition of groupNumber! If it does not come before the definition of assignment,
creating groups fails because of a MikroORM bug!
*/
@PrimaryKey({ type: 'integer', autoincrement: true })
groupNumber?: number;
@ManyToOne({ @ManyToOne({
entity: () => Assignment, entity: () => Assignment,
primary: true, primary: true,
}) })
assignment!: Assignment; assignment!: Assignment;
@PrimaryKey({ type: 'integer', autoincrement: true })
groupNumber?: number;
@ManyToMany({ @ManyToMany({
entity: () => Student, entity: () => Student,
owner: true,
inversedBy: 'groups',
}) })
members!: Collection<Student>; members: Collection<Student> = new Collection<Student>(this);
} }

View file

@ -14,9 +14,9 @@ export class Class {
@Property({ type: 'string' }) @Property({ type: 'string' })
displayName!: string; displayName!: string;
@ManyToMany(() => Teacher) @ManyToMany({ entity: () => Teacher, owner: true, inversedBy: 'classes' })
teachers!: Collection<Teacher>; teachers!: Collection<Teacher>;
@ManyToMany(() => Student) @ManyToMany({ entity: () => Student, owner: true, inversedBy: 'classes' })
students!: Collection<Student>; students!: Collection<Student>;
} }

View file

@ -8,9 +8,9 @@ import { StudentRepository } from '../../data/users/student-repository.js';
repository: () => StudentRepository, repository: () => StudentRepository,
}) })
export class Student extends User { export class Student extends User {
@ManyToMany(() => Class) @ManyToMany({ entity: () => Class, mappedBy: 'students' })
classes!: Collection<Class>; classes!: Collection<Class>;
@ManyToMany(() => Group) @ManyToMany({ entity: () => Group, mappedBy: 'members' })
groups!: Collection<Group>; groups: Collection<Group> = new Collection<Group>(this);
} }

View file

@ -5,6 +5,6 @@ import { TeacherRepository } from '../../data/users/teacher-repository.js';
@Entity({ repository: () => TeacherRepository }) @Entity({ repository: () => TeacherRepository })
export class Teacher extends User { export class Teacher extends User {
@ManyToMany(() => Class) @ManyToMany({ entity: () => Class, mappedBy: 'teachers' })
classes!: Collection<Class>; classes!: Collection<Class>;
} }

View file

@ -0,0 +1,12 @@
import { ExceptionWithHttpState } from './exception-with-http-state.js';
/**
* Exception for HTTP 500 Internal Server Error
*/
export class ServerErrorException extends ExceptionWithHttpState {
status = 500;
constructor(message = 'Internal server error, something went wrong') {
super(500, message);
}
}

View file

@ -1,18 +1,14 @@
import { languageMap } from '@dwengo-1/common/util/language'; import { languageMap } from '@dwengo-1/common/util/language';
import { FALLBACK_LANG } from '../config.js';
import { Assignment } from '../entities/assignments/assignment.entity.js'; import { Assignment } from '../entities/assignments/assignment.entity.js';
import { Class } from '../entities/classes/class.entity.js'; import { Class } from '../entities/classes/class.entity.js';
import { getLogger } from '../logging/initalize.js'; import { AssignmentDTO, AssignmentDTOId } from '@dwengo-1/common/interfaces/assignment';
import { AssignmentDTO } from '@dwengo-1/common/interfaces/assignment'; import { mapToGroupDTO } from './group.js';
import { getAssignmentRepository } from '../data/repositories.js';
export function mapToAssignmentDTOId(assignment: Assignment): AssignmentDTO { export function mapToAssignmentDTOId(assignment: Assignment): AssignmentDTOId {
return { return {
id: assignment.id!, id: assignment.id!,
within: assignment.within.classId!, within: assignment.within.classId!,
title: assignment.title,
description: assignment.description,
learningPath: assignment.learningPathHruid,
language: assignment.learningPathLanguage,
}; };
} }
@ -24,19 +20,17 @@ export function mapToAssignmentDTO(assignment: Assignment): AssignmentDTO {
description: assignment.description, description: assignment.description,
learningPath: assignment.learningPathHruid, learningPath: assignment.learningPathHruid,
language: assignment.learningPathLanguage, language: assignment.learningPathLanguage,
// Groups: assignment.groups.map(mapToGroupDTO), groups: assignment.groups.map((group) => mapToGroupDTO(group, assignment.within)),
}; };
} }
export function mapToAssignment(assignmentData: AssignmentDTO, cls: Class): Assignment { export function mapToAssignment(assignmentData: AssignmentDTO, cls: Class): Assignment {
const assignment = new Assignment(); return getAssignmentRepository().create({
assignment.title = assignmentData.title; within: cls,
assignment.description = assignmentData.description; title: assignmentData.title,
assignment.learningPathHruid = assignmentData.learningPath; description: assignmentData.description,
assignment.learningPathLanguage = languageMap[assignmentData.language] || FALLBACK_LANG; learningPathHruid: assignmentData.learningPath,
assignment.within = cls; learningPathLanguage: languageMap[assignmentData.language],
groups: [],
getLogger().debug(assignment); });
return assignment;
} }

View file

@ -1,14 +1,12 @@
import { Group } from '../entities/assignments/group.entity.js'; import { Group } from '../entities/assignments/group.entity.js';
import { mapToAssignment } from './assignment.js'; import { mapToAssignment } from './assignment.js';
import { mapToStudent } from './student.js'; import { mapToStudent } from './student.js';
import { mapToAssignmentDTO } from './assignment.js';
import { mapToStudentDTO } from './student.js'; import { mapToStudentDTO } from './student.js';
import { GroupDTO } from '@dwengo-1/common/interfaces/group'; import { GroupDTO, GroupDTOId } from '@dwengo-1/common/interfaces/group';
import { getGroupRepository } from '../data/repositories.js'; import { getGroupRepository } from '../data/repositories.js';
import { AssignmentDTO } from '@dwengo-1/common/interfaces/assignment'; import { AssignmentDTO } from '@dwengo-1/common/interfaces/assignment';
import { Class } from '../entities/classes/class.entity.js'; import { Class } from '../entities/classes/class.entity.js';
import { StudentDTO } from '@dwengo-1/common/interfaces/student'; import { StudentDTO } from '@dwengo-1/common/interfaces/student';
import { mapToClassDTO } from './class.js';
export function mapToGroup(groupDto: GroupDTO, clazz: Class): Group { export function mapToGroup(groupDto: GroupDTO, clazz: Class): Group {
const assignmentDto = groupDto.assignment as AssignmentDTO; const assignmentDto = groupDto.assignment as AssignmentDTO;
@ -20,18 +18,18 @@ export function mapToGroup(groupDto: GroupDTO, clazz: Class): Group {
}); });
} }
export function mapToGroupDTO(group: Group): GroupDTO { export function mapToGroupDTO(group: Group, cls: Class): GroupDTO {
return { return {
class: mapToClassDTO(group.assignment.within), class: cls.classId!,
assignment: mapToAssignmentDTO(group.assignment), assignment: group.assignment.id!,
groupNumber: group.groupNumber!, groupNumber: group.groupNumber!,
members: group.members.map(mapToStudentDTO), members: group.members.map(mapToStudentDTO),
}; };
} }
export function mapToGroupDTOId(group: Group): GroupDTO { export function mapToGroupDTOId(group: Group, cls: Class): GroupDTOId {
return { return {
class: group.assignment.within.classId!, class: cls.classId!,
assignment: group.assignment.id!, assignment: group.assignment.id!,
groupNumber: group.groupNumber!, groupNumber: group.groupNumber!,
}; };

View file

@ -31,7 +31,7 @@ export function mapToQuestionDTO(question: Question): QuestionDTO {
learningObjectIdentifier, learningObjectIdentifier,
sequenceNumber: question.sequenceNumber!, sequenceNumber: question.sequenceNumber!,
author: mapToStudentDTO(question.author), author: mapToStudentDTO(question.author),
inGroup: mapToGroupDTOId(question.inGroup), inGroup: mapToGroupDTOId(question.inGroup, question.inGroup.assignment?.within),
timestamp: question.timestamp.toISOString(), timestamp: question.timestamp.toISOString(),
content: question.content, content: question.content,
}; };

View file

@ -16,7 +16,7 @@ export function mapToSubmissionDTO(submission: Submission): SubmissionDTO {
submissionNumber: submission.submissionNumber, submissionNumber: submission.submissionNumber,
submitter: mapToStudentDTO(submission.submitter), submitter: mapToStudentDTO(submission.submitter),
time: submission.submissionTime, time: submission.submissionTime,
group: mapToGroupDTOId(submission.onBehalfOf), group: submission.onBehalfOf ? mapToGroupDTOId(submission.onBehalfOf, submission.onBehalfOf.assignment.within) : undefined,
content: submission.content, content: submission.content,
}; };
} }

View file

@ -1,4 +1,4 @@
import { AssignmentDTO } from '@dwengo-1/common/interfaces/assignment'; import { AssignmentDTO, AssignmentDTOId } from '@dwengo-1/common/interfaces/assignment';
import { import {
getAssignmentRepository, getAssignmentRepository,
getClassRepository, getClassRepository,
@ -16,6 +16,8 @@ import { QuestionDTO, QuestionId } from '@dwengo-1/common/interfaces/question';
import { SubmissionDTO, SubmissionDTOId } from '@dwengo-1/common/interfaces/submission'; import { SubmissionDTO, SubmissionDTOId } from '@dwengo-1/common/interfaces/submission';
import { EntityDTO } from '@mikro-orm/core'; import { EntityDTO } from '@mikro-orm/core';
import { putObject } from './service-helper.js'; import { putObject } from './service-helper.js';
import { fetchStudents } from './students.js';
import { ServerErrorException } from '../exceptions/server-error-exception.js';
export async function fetchAssignment(classid: string, assignmentNumber: number): Promise<Assignment> { export async function fetchAssignment(classid: string, assignmentNumber: number): Promise<Assignment> {
const classRepository = getClassRepository(); const classRepository = getClassRepository();
@ -35,7 +37,7 @@ export async function fetchAssignment(classid: string, assignmentNumber: number)
return assignment; return assignment;
} }
export async function getAllAssignments(classid: string, full: boolean): Promise<AssignmentDTO[]> { export async function getAllAssignments(classid: string, full: boolean): Promise<AssignmentDTO[] | AssignmentDTOId[]> {
const cls = await fetchClass(classid); const cls = await fetchClass(classid);
const assignmentRepository = getAssignmentRepository(); const assignmentRepository = getAssignmentRepository();
@ -51,13 +53,39 @@ export async function getAllAssignments(classid: string, full: boolean): Promise
export async function createAssignment(classid: string, assignmentData: AssignmentDTO): Promise<AssignmentDTO> { export async function createAssignment(classid: string, assignmentData: AssignmentDTO): Promise<AssignmentDTO> {
const cls = await fetchClass(classid); const cls = await fetchClass(classid);
const assignment = mapToAssignment(assignmentData, cls);
const assignmentRepository = getAssignmentRepository(); const assignmentRepository = getAssignmentRepository();
const newAssignment = assignmentRepository.create(assignment); const assignment = mapToAssignment(assignmentData, cls);
await assignmentRepository.save(newAssignment, { preventOverwrite: true }); await assignmentRepository.save(assignment);
return mapToAssignmentDTO(newAssignment); if (assignmentData.groups) {
/*
For some reason when trying to add groups, it does not work when using the original assignment variable.
The assignment needs to be refetched in order for it to work.
*/
const assignmentCopy = await assignmentRepository.findByClassAndId(cls, assignment.id!);
if (assignmentCopy === null) {
throw new ServerErrorException('Something has gone horribly wrong. Could not find newly added assignment which is needed to add groups.');
}
const groupRepository = getGroupRepository();
(assignmentData.groups as string[][]).forEach(async (memberUsernames) => {
const members = await fetchStudents(memberUsernames);
const newGroup = groupRepository.create({
assignment: assignmentCopy,
members: members,
});
await groupRepository.save(newGroup);
});
}
/* Need to refetch the assignment here again such that the groups are added. */
const assignmentWithGroups = await fetchAssignment(classid, assignment.id!);
return mapToAssignmentDTO(assignmentWithGroups);
} }
export async function getAssignment(classid: string, id: number): Promise<AssignmentDTO> { export async function getAssignment(classid: string, id: number): Promise<AssignmentDTO> {

View file

@ -1,13 +1,14 @@
import { EntityDTO } from '@mikro-orm/core'; import { EntityDTO } from '@mikro-orm/core';
import { getGroupRepository, getStudentRepository, getSubmissionRepository } from '../data/repositories.js'; import { getGroupRepository, getSubmissionRepository } from '../data/repositories.js';
import { Group } from '../entities/assignments/group.entity.js'; import { Group } from '../entities/assignments/group.entity.js';
import { mapToGroupDTO, mapToShallowGroupDTO } from '../interfaces/group.js'; import { mapToGroupDTO, mapToGroupDTOId } from '../interfaces/group.js';
import { mapToSubmissionDTO, mapToSubmissionDTOId } from '../interfaces/submission.js'; import { mapToSubmissionDTO, mapToSubmissionDTOId } from '../interfaces/submission.js';
import { GroupDTO } from '@dwengo-1/common/interfaces/group'; import { GroupDTO, GroupDTOId } from '@dwengo-1/common/interfaces/group';
import { SubmissionDTO, SubmissionDTOId } from '@dwengo-1/common/interfaces/submission'; import { SubmissionDTO, SubmissionDTOId } from '@dwengo-1/common/interfaces/submission';
import { fetchAssignment } from './assignments.js'; import { fetchAssignment } from './assignments.js';
import { NotFoundException } from '../exceptions/not-found-exception.js'; import { NotFoundException } from '../exceptions/not-found-exception.js';
import { putObject } from './service-helper.js'; import { putObject } from './service-helper.js';
import { fetchStudents } from './students.js';
export async function fetchGroup(classId: string, assignmentNumber: number, groupNumber: number): Promise<Group> { export async function fetchGroup(classId: string, assignmentNumber: number, groupNumber: number): Promise<Group> {
const assignment = await fetchAssignment(classId, assignmentNumber); const assignment = await fetchAssignment(classId, assignmentNumber);
@ -24,7 +25,7 @@ export async function fetchGroup(classId: string, assignmentNumber: number, grou
export async function getGroup(classId: string, assignmentNumber: number, groupNumber: number): Promise<GroupDTO> { export async function getGroup(classId: string, assignmentNumber: number, groupNumber: number): Promise<GroupDTO> {
const group = await fetchGroup(classId, assignmentNumber, groupNumber); const group = await fetchGroup(classId, assignmentNumber, groupNumber);
return mapToGroupDTO(group); return mapToGroupDTO(group, group.assignment.within);
} }
export async function putGroup( export async function putGroup(
@ -37,7 +38,7 @@ export async function putGroup(
await putObject<Group>(group, groupData, getGroupRepository()); await putObject<Group>(group, groupData, getGroupRepository());
return mapToGroupDTO(group); return mapToGroupDTO(group, group.assignment.within);
} }
export async function deleteGroup(classId: string, assignmentNumber: number, groupNumber: number): Promise<GroupDTO> { export async function deleteGroup(classId: string, assignmentNumber: number, groupNumber: number): Promise<GroupDTO> {
@ -47,7 +48,7 @@ export async function deleteGroup(classId: string, assignmentNumber: number, gro
const groupRepository = getGroupRepository(); const groupRepository = getGroupRepository();
await groupRepository.deleteByAssignmentAndGroupNumber(assignment, groupNumber); await groupRepository.deleteByAssignmentAndGroupNumber(assignment, groupNumber);
return mapToGroupDTO(group); return mapToGroupDTO(group, assignment.within);
} }
export async function getExistingGroupFromGroupDTO(groupData: GroupDTO): Promise<Group> { export async function getExistingGroupFromGroupDTO(groupData: GroupDTO): Promise<Group> {
@ -59,12 +60,8 @@ export async function getExistingGroupFromGroupDTO(groupData: GroupDTO): Promise
} }
export async function createGroup(groupData: GroupDTO, classid: string, assignmentNumber: number): Promise<GroupDTO> { export async function createGroup(groupData: GroupDTO, classid: string, assignmentNumber: number): Promise<GroupDTO> {
const studentRepository = getStudentRepository();
const memberUsernames = (groupData.members as string[]) || []; const memberUsernames = (groupData.members as string[]) || [];
const members = (await Promise.all([...memberUsernames].map(async (id) => studentRepository.findByUsername(id)))).filter( const members = await fetchStudents(memberUsernames);
(student) => student !== null
);
const assignment = await fetchAssignment(classid, assignmentNumber); const assignment = await fetchAssignment(classid, assignmentNumber);
@ -73,22 +70,23 @@ export async function createGroup(groupData: GroupDTO, classid: string, assignme
assignment: assignment, assignment: assignment,
members: members, members: members,
}); });
await groupRepository.save(newGroup); await groupRepository.save(newGroup);
return mapToGroupDTO(newGroup); return mapToGroupDTO(newGroup, newGroup.assignment.within);
} }
export async function getAllGroups(classId: string, assignmentNumber: number, full: boolean): Promise<GroupDTO[]> { export async function getAllGroups(classId: string, assignmentNumber: number, full: boolean): Promise<GroupDTO[] | GroupDTOId[]> {
const assignment = await fetchAssignment(classId, assignmentNumber); const assignment = await fetchAssignment(classId, assignmentNumber);
const groupRepository = getGroupRepository(); const groupRepository = getGroupRepository();
const groups = await groupRepository.findAllGroupsForAssignment(assignment); const groups = await groupRepository.findAllGroupsForAssignment(assignment);
if (full) { if (full) {
return groups.map(mapToGroupDTO); return groups.map((group) => mapToGroupDTO(group, assignment.within));
} }
return groups.map(mapToShallowGroupDTO); return groups.map((group) => mapToGroupDTOId(group, assignment.within));
} }
export async function getGroupSubmissions( export async function getGroupSubmissions(

View file

@ -7,7 +7,7 @@ import {
getSubmissionRepository, getSubmissionRepository,
} from '../data/repositories.js'; } from '../data/repositories.js';
import { mapToClassDTO } from '../interfaces/class.js'; import { mapToClassDTO } from '../interfaces/class.js';
import { mapToGroupDTO, mapToShallowGroupDTO } from '../interfaces/group.js'; import { mapToGroupDTO, mapToGroupDTOId } from '../interfaces/group.js';
import { mapToStudent, mapToStudentDTO } from '../interfaces/student.js'; import { mapToStudent, mapToStudentDTO } from '../interfaces/student.js';
import { mapToSubmissionDTO, mapToSubmissionDTOId } from '../interfaces/submission.js'; import { mapToSubmissionDTO, mapToSubmissionDTOId } from '../interfaces/submission.js';
import { getAllAssignments } from './assignments.js'; import { getAllAssignments } from './assignments.js';
@ -18,8 +18,8 @@ import { NotFoundException } from '../exceptions/not-found-exception.js';
import { fetchClass } from './classes.js'; import { fetchClass } from './classes.js';
import { StudentDTO } from '@dwengo-1/common/interfaces/student'; import { StudentDTO } from '@dwengo-1/common/interfaces/student';
import { ClassDTO } from '@dwengo-1/common/interfaces/class'; import { ClassDTO } from '@dwengo-1/common/interfaces/class';
import { AssignmentDTO } from '@dwengo-1/common/interfaces/assignment'; import { AssignmentDTO, AssignmentDTOId } from '@dwengo-1/common/interfaces/assignment';
import { GroupDTO } from '@dwengo-1/common/interfaces/group'; import { GroupDTO, GroupDTOId } from '@dwengo-1/common/interfaces/group';
import { SubmissionDTO, SubmissionDTOId } from '@dwengo-1/common/interfaces/submission'; import { SubmissionDTO, SubmissionDTOId } from '@dwengo-1/common/interfaces/submission';
import { QuestionDTO, QuestionId } from '@dwengo-1/common/interfaces/question'; import { QuestionDTO, QuestionId } from '@dwengo-1/common/interfaces/question';
import { ClassJoinRequestDTO } from '@dwengo-1/common/interfaces/class-join-request'; import { ClassJoinRequestDTO } from '@dwengo-1/common/interfaces/class-join-request';
@ -48,6 +48,11 @@ export async function fetchStudent(username: string): Promise<Student> {
return user; return user;
} }
export async function fetchStudents(usernames: string[]): Promise<Student[]> {
const members = await Promise.all(usernames.map(async (username) => await fetchStudent(username)));
return members;
}
export async function getStudent(username: string): Promise<StudentDTO> { export async function getStudent(username: string): Promise<StudentDTO> {
const user = await fetchStudent(username); const user = await fetchStudent(username);
return mapToStudentDTO(user); return mapToStudentDTO(user);
@ -83,7 +88,7 @@ export async function getStudentClasses(username: string, full: boolean): Promis
return classes.map((cls) => cls.classId!); return classes.map((cls) => cls.classId!);
} }
export async function getStudentAssignments(username: string, full: boolean): Promise<AssignmentDTO[]> { export async function getStudentAssignments(username: string, full: boolean): Promise<AssignmentDTO[] | AssignmentDTOId[]> {
const student = await fetchStudent(username); const student = await fetchStudent(username);
const classRepository = getClassRepository(); const classRepository = getClassRepository();
@ -92,17 +97,17 @@ export async function getStudentAssignments(username: string, full: boolean): Pr
return (await Promise.all(classes.map(async (cls) => await getAllAssignments(cls.classId!, full)))).flat(); return (await Promise.all(classes.map(async (cls) => await getAllAssignments(cls.classId!, full)))).flat();
} }
export async function getStudentGroups(username: string, full: boolean): Promise<GroupDTO[]> { export async function getStudentGroups(username: string, full: boolean): Promise<GroupDTO[] | GroupDTOId[]> {
const student = await fetchStudent(username); const student = await fetchStudent(username);
const groupRepository = getGroupRepository(); const groupRepository = getGroupRepository();
const groups = await groupRepository.findAllGroupsWithStudent(student); const groups = await groupRepository.findAllGroupsWithStudent(student);
if (full) { if (full) {
return groups.map(mapToGroupDTO); return groups.map((group) => mapToGroupDTO(group, group.assignment.within));
} }
return groups.map(mapToShallowGroupDTO); return groups.map((group) => mapToGroupDTOId(group, group.assignment.within));
} }
export async function getStudentSubmissions(username: string, full: boolean): Promise<SubmissionDTO[] | SubmissionDTOId[]> { export async function getStudentSubmissions(username: string, full: boolean): Promise<SubmissionDTO[] | SubmissionDTOId[]> {

View file

@ -33,7 +33,7 @@ export async function getAllSubmissions(loId: LearningObjectIdentifier): Promise
export async function createSubmission(submissionDTO: SubmissionDTO): Promise<SubmissionDTO> { export async function createSubmission(submissionDTO: SubmissionDTO): Promise<SubmissionDTO> {
const submitter = await fetchStudent(submissionDTO.submitter.username); const submitter = await fetchStudent(submissionDTO.submitter.username);
const group = await getExistingGroupFromGroupDTO(submissionDTO.group); const group = await getExistingGroupFromGroupDTO(submissionDTO.group!);
const submissionRepository = getSubmissionRepository(); const submissionRepository = getSubmissionRepository();
const submission = mapToSubmission(submissionDTO, submitter, group); const submission = mapToSubmission(submissionDTO, submitter, group);

View file

@ -16,7 +16,7 @@ describe('AssignmentRepository', () => {
it('should return the requested assignment', async () => { it('should return the requested assignment', async () => {
const class_ = await classRepository.findById('34d484a1-295f-4e9f-bfdc-3e7a23d86a89'); const class_ = await classRepository.findById('34d484a1-295f-4e9f-bfdc-3e7a23d86a89');
const assignment = await assignmentRepository.findByClassAndId(class_!, 2); const assignment = await assignmentRepository.findByClassAndId(class_!, 21001);
expect(assignment).toBeTruthy(); expect(assignment).toBeTruthy();
expect(assignment!.title).toBe('tool'); expect(assignment!.title).toBe('tool');
@ -35,7 +35,7 @@ describe('AssignmentRepository', () => {
const result = await assignmentRepository.findAllByResponsibleTeacher('testleerkracht1'); const result = await assignmentRepository.findAllByResponsibleTeacher('testleerkracht1');
const resultIds = result.map((it) => it.id).sort((a, b) => (a ?? 0) - (b ?? 0)); const resultIds = result.map((it) => it.id).sort((a, b) => (a ?? 0) - (b ?? 0));
expect(resultIds).toEqual([1, 1, 3, 4]); expect(resultIds).toEqual([21000, 21002, 21003, 21004]);
}); });
it('should not find removed assignment', async () => { it('should not find removed assignment', async () => {

View file

@ -19,16 +19,16 @@ describe('GroupRepository', () => {
it('should return the requested group', async () => { it('should return the requested group', async () => {
const class_ = await classRepository.findById('8764b861-90a6-42e5-9732-c0d9eb2f55f9'); const class_ = await classRepository.findById('8764b861-90a6-42e5-9732-c0d9eb2f55f9');
const assignment = await assignmentRepository.findByClassAndId(class_!, 1); const assignment = await assignmentRepository.findByClassAndId(class_!, 21000);
const group = await groupRepository.findByAssignmentAndGroupNumber(assignment!, 1); const group = await groupRepository.findByAssignmentAndGroupNumber(assignment!, 21001);
expect(group).toBeTruthy(); expect(group).toBeTruthy();
}); });
it('should return all groups for assignment', async () => { it('should return all groups for assignment', async () => {
const class_ = await classRepository.findById('8764b861-90a6-42e5-9732-c0d9eb2f55f9'); const class_ = await classRepository.findById('8764b861-90a6-42e5-9732-c0d9eb2f55f9');
const assignment = await assignmentRepository.findByClassAndId(class_!, 1); const assignment = await assignmentRepository.findByClassAndId(class_!, 21000);
const groups = await groupRepository.findAllGroupsForAssignment(assignment!); const groups = await groupRepository.findAllGroupsForAssignment(assignment!);
@ -38,9 +38,9 @@ describe('GroupRepository', () => {
it('should not find removed group', async () => { it('should not find removed group', async () => {
const class_ = await classRepository.findById('34d484a1-295f-4e9f-bfdc-3e7a23d86a89'); const class_ = await classRepository.findById('34d484a1-295f-4e9f-bfdc-3e7a23d86a89');
const assignment = await assignmentRepository.findByClassAndId(class_!, 2); const assignment = await assignmentRepository.findByClassAndId(class_!, 21001);
await groupRepository.deleteByAssignmentAndGroupNumber(assignment!, 1); await groupRepository.deleteByAssignmentAndGroupNumber(assignment!, 21001);
const group = await groupRepository.findByAssignmentAndGroupNumber(assignment!, 1); const group = await groupRepository.findByAssignmentAndGroupNumber(assignment!, 1);

View file

@ -54,8 +54,8 @@ describe('SubmissionRepository', () => {
it('should find the most recent submission for a group', async () => { it('should find the most recent submission for a group', async () => {
const id = new LearningObjectIdentifier('id03', Language.English, 1); const id = new LearningObjectIdentifier('id03', Language.English, 1);
const class_ = await classRepository.findById('8764b861-90a6-42e5-9732-c0d9eb2f55f9'); const class_ = await classRepository.findById('8764b861-90a6-42e5-9732-c0d9eb2f55f9');
const assignment = await assignmentRepository.findByClassAndId(class_!, 1); const assignment = await assignmentRepository.findByClassAndId(class_!, 21000);
const group = await groupRepository.findByAssignmentAndGroupNumber(assignment!, 1); const group = await groupRepository.findByAssignmentAndGroupNumber(assignment!, 21001);
const submission = await submissionRepository.findMostRecentSubmissionForGroup(id, group!); const submission = await submissionRepository.findMostRecentSubmissionForGroup(id, group!);
expect(submission).toBeTruthy(); expect(submission).toBeTruthy();
@ -67,7 +67,7 @@ describe('SubmissionRepository', () => {
let loId: LearningObjectIdentifier; let loId: LearningObjectIdentifier;
it('should find all submissions for a certain learning object and assignment', async () => { it('should find all submissions for a certain learning object and assignment', async () => {
clazz = await classRepository.findById('8764b861-90a6-42e5-9732-c0d9eb2f55f9'); clazz = await classRepository.findById('8764b861-90a6-42e5-9732-c0d9eb2f55f9');
assignment = await assignmentRepository.findByClassAndId(clazz!, 1); assignment = await assignmentRepository.findByClassAndId(clazz!, 21000);
loId = { loId = {
hruid: 'id02', hruid: 'id02',
language: Language.English, language: Language.English,
@ -92,7 +92,7 @@ describe('SubmissionRepository', () => {
}); });
it('should find only the submissions for a certain learning object and assignment made for the given group', async () => { it('should find only the submissions for a certain learning object and assignment made for the given group', async () => {
const group = await groupRepository.findByAssignmentAndGroupNumber(assignment!, 2); const group = await groupRepository.findByAssignmentAndGroupNumber(assignment!, 21002);
const result = await submissionRepository.findAllSubmissionsForLearningObjectAndGroup(loId, group!); const result = await submissionRepository.findAllSubmissionsForLearningObjectAndGroup(loId, group!);
expect(result).toHaveLength(1); expect(result).toHaveLength(1);

View file

@ -38,8 +38,8 @@ describe('QuestionRepository', () => {
const student = await studentRepository.findByUsername('Noordkaap'); const student = await studentRepository.findByUsername('Noordkaap');
const clazz = await getClassRepository().findById('8764b861-90a6-42e5-9732-c0d9eb2f55f9'); const clazz = await getClassRepository().findById('8764b861-90a6-42e5-9732-c0d9eb2f55f9');
const assignment = await getAssignmentRepository().findByClassAndId(clazz!, 1); const assignment = await getAssignmentRepository().findByClassAndId(clazz!, 21000);
const group = await getGroupRepository().findByAssignmentAndGroupNumber(assignment!, 1); const group = await getGroupRepository().findByAssignmentAndGroupNumber(assignment!, 21001);
await questionRepository.createQuestion({ await questionRepository.createQuestion({
loId: id, loId: id,
inGroup: group!, inGroup: group!,
@ -57,7 +57,7 @@ describe('QuestionRepository', () => {
let loId: LearningObjectIdentifier; let loId: LearningObjectIdentifier;
it('should find all questions for a certain learning object and assignment', async () => { it('should find all questions for a certain learning object and assignment', async () => {
clazz = await getClassRepository().findById('8764b861-90a6-42e5-9732-c0d9eb2f55f9'); clazz = await getClassRepository().findById('8764b861-90a6-42e5-9732-c0d9eb2f55f9');
assignment = await getAssignmentRepository().findByClassAndId(clazz!, 1); assignment = await getAssignmentRepository().findByClassAndId(clazz!, 21000);
loId = { loId = {
hruid: 'id05', hruid: 'id05',
language: Language.English, language: Language.English,

View file

@ -7,8 +7,8 @@ import { getClassWithTestleerlingAndTestleerkracht } from '../classes/classes.te
export function makeTestAssignemnts(em: EntityManager, classes: Class[]): Assignment[] { export function makeTestAssignemnts(em: EntityManager, classes: Class[]): Assignment[] {
assignment01 = em.create(Assignment, { assignment01 = em.create(Assignment, {
id: 21000,
within: classes[0], within: classes[0],
id: 1,
title: 'dire straits', title: 'dire straits',
description: 'reading', description: 'reading',
learningPathHruid: 'id02', learningPathHruid: 'id02',
@ -17,8 +17,8 @@ export function makeTestAssignemnts(em: EntityManager, classes: Class[]): Assign
}); });
assignment02 = em.create(Assignment, { assignment02 = em.create(Assignment, {
id: 21001,
within: classes[1], within: classes[1],
id: 2,
title: 'tool', title: 'tool',
description: 'reading', description: 'reading',
learningPathHruid: 'id01', learningPathHruid: 'id01',
@ -27,8 +27,8 @@ export function makeTestAssignemnts(em: EntityManager, classes: Class[]): Assign
}); });
assignment03 = em.create(Assignment, { assignment03 = em.create(Assignment, {
id: 21002,
within: classes[0], within: classes[0],
id: 3,
title: 'delete', title: 'delete',
description: 'will be deleted', description: 'will be deleted',
learningPathHruid: 'id02', learningPathHruid: 'id02',
@ -37,8 +37,8 @@ export function makeTestAssignemnts(em: EntityManager, classes: Class[]): Assign
}); });
assignment04 = em.create(Assignment, { assignment04 = em.create(Assignment, {
id: 21003,
within: classes[0], within: classes[0],
id: 4,
title: 'another assignment', title: 'another assignment',
description: 'with a description', description: 'with a description',
learningPathHruid: 'id01', learningPathHruid: 'id01',
@ -48,7 +48,7 @@ export function makeTestAssignemnts(em: EntityManager, classes: Class[]): Assign
conditionalPathAssignment = em.create(Assignment, { conditionalPathAssignment = em.create(Assignment, {
within: getClassWithTestleerlingAndTestleerkracht(), within: getClassWithTestleerlingAndTestleerkracht(),
id: 1, id: 21004,
title: 'Assignment: Conditional Learning Path', title: 'Assignment: Conditional Learning Path',
description: 'You have to do the testing learning path with a condition.', description: 'You have to do the testing learning path with a condition.',
learningPathHruid: testLearningPathWithConditions.hruid, learningPathHruid: testLearningPathWithConditions.hruid,

View file

@ -12,7 +12,7 @@ export function makeTestGroups(em: EntityManager, students: Student[], assignmen
*/ */
group01 = em.create(Group, { group01 = em.create(Group, {
assignment: assignments[0], assignment: assignments[0],
groupNumber: 1, groupNumber: 21001,
members: students.slice(0, 2), members: students.slice(0, 2),
}); });
@ -22,7 +22,7 @@ export function makeTestGroups(em: EntityManager, students: Student[], assignmen
*/ */
group02 = em.create(Group, { group02 = em.create(Group, {
assignment: assignments[0], assignment: assignments[0],
groupNumber: 2, groupNumber: 21002,
members: students.slice(2, 4), members: students.slice(2, 4),
}); });
@ -32,7 +32,7 @@ export function makeTestGroups(em: EntityManager, students: Student[], assignmen
*/ */
group03 = em.create(Group, { group03 = em.create(Group, {
assignment: assignments[0], assignment: assignments[0],
groupNumber: 3, groupNumber: 21003,
members: students.slice(4, 6), members: students.slice(4, 6),
}); });
@ -42,7 +42,7 @@ export function makeTestGroups(em: EntityManager, students: Student[], assignmen
*/ */
group04 = em.create(Group, { group04 = em.create(Group, {
assignment: assignments[1], assignment: assignments[1],
groupNumber: 4, groupNumber: 21004,
members: students.slice(3, 4), members: students.slice(3, 4),
}); });
@ -52,7 +52,7 @@ export function makeTestGroups(em: EntityManager, students: Student[], assignmen
*/ */
group05 = em.create(Group, { group05 = em.create(Group, {
assignment: assignments[3], assignment: assignments[3],
groupNumber: 1, groupNumber: 21001,
members: students.slice(0, 2), members: students.slice(0, 2),
}); });

View file

@ -34,6 +34,7 @@ export async function seedDatabase(): Promise<void> {
const learningPaths = makeTestLearningPaths(em); const learningPaths = makeTestLearningPaths(em);
const classes = makeTestClasses(em, students, teachers); const classes = makeTestClasses(em, students, teachers);
const assignments = makeTestAssignemnts(em, classes); const assignments = makeTestAssignemnts(em, classes);
const groups = makeTestGroups(em, students, assignments); const groups = makeTestGroups(em, students, assignments);
assignments[0].groups = new Collection<Group>(groups.slice(0, 3)); assignments[0].groups = new Collection<Group>(groups.slice(0, 3));

View file

@ -7,5 +7,10 @@ export interface AssignmentDTO {
description: string; description: string;
learningPath: string; learningPath: string;
language: string; language: string;
groups?: GroupDTO[] | string[]; // TODO groups: GroupDTO[] | string[][];
}
export interface AssignmentDTOId {
id: number;
within: string;
} }

View file

@ -8,3 +8,9 @@ export interface GroupDTO {
groupNumber: number; groupNumber: number;
members?: string[] | StudentDTO[]; members?: string[] | StudentDTO[];
} }
export interface GroupDTOId {
class: string;
assignment: number;
groupNumber: number;
}

View file

@ -9,7 +9,7 @@ export interface SubmissionDTO {
submissionNumber?: number; submissionNumber?: number;
submitter: StudentDTO; submitter: StudentDTO;
time?: Date; time?: Date;
group: GroupDTO; group?: GroupDTO;
content: string; content: string;
} }

View file

@ -1,11 +1,11 @@
import { BaseController } from "./base-controller"; import { BaseController } from "./base-controller";
import type { AssignmentDTO } from "@dwengo-1/common/interfaces/assignment"; import type { AssignmentDTO, AssignmentDTOId } from "@dwengo-1/common/interfaces/assignment";
import type { SubmissionsResponse } from "./submissions"; import type { SubmissionsResponse } from "./submissions";
import type { QuestionsResponse } from "./questions"; import type { QuestionsResponse } from "./questions";
import type { GroupsResponse } from "./groups"; import type { GroupsResponse } from "./groups";
export interface AssignmentsResponse { export interface AssignmentsResponse {
assignments: AssignmentDTO[] | string[]; assignments: AssignmentDTO[] | AssignmentDTOId[];
} }
export interface AssignmentResponse { export interface AssignmentResponse {

View file

@ -1,10 +1,10 @@
import { BaseController } from "./base-controller"; import { BaseController } from "./base-controller";
import type { GroupDTO } from "@dwengo-1/common/interfaces/group"; import type { GroupDTO, GroupDTOId } from "@dwengo-1/common/interfaces/group";
import type { SubmissionsResponse } from "./submissions"; import type { SubmissionsResponse } from "./submissions";
import type { QuestionsResponse } from "./questions"; import type { QuestionsResponse } from "./questions";
export interface GroupsResponse { export interface GroupsResponse {
groups: GroupDTO[]; groups: GroupDTO[] | GroupDTOId[];
} }
export interface GroupResponse { export interface GroupResponse {

1187
package-lock.json generated

File diff suppressed because it is too large Load diff