Merge remote-tracking branch 'origin/dev' into feat/endpoints-in-backend-om-eigen-leerpaden-en-leerobjecten-toe-te-voegen-aan-de-databank-#248
This commit is contained in:
		
						commit
						db92eff759
					
				
					 26 changed files with 9291 additions and 659 deletions
				
			
		|  | @ -41,6 +41,7 @@ | |||
|         "loki-logger-ts": "^1.0.2", | ||||
|         "marked": "^15.0.7", | ||||
|         "mime-types": "^3.0.1", | ||||
|         "nanoid": "^5.1.5", | ||||
|         "response-time": "^2.3.3", | ||||
|         "swagger-ui-express": "^5.0.1", | ||||
|         "unzipper": "^0.12.3", | ||||
|  |  | |||
|  | @ -1,15 +1,17 @@ | |||
| import { Collection, Entity, ManyToMany, PrimaryKey, Property } from '@mikro-orm/core'; | ||||
| import { v4 } from 'uuid'; | ||||
| import { Teacher } from '../users/teacher.entity.js'; | ||||
| import { Student } from '../users/student.entity.js'; | ||||
| import { ClassRepository } from '../../data/classes/class-repository.js'; | ||||
| import { customAlphabet } from 'nanoid'; | ||||
| 
 | ||||
| const generateClassId = customAlphabet('ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', 6); | ||||
| 
 | ||||
| @Entity({ | ||||
|     repository: () => ClassRepository, | ||||
| }) | ||||
| export class Class { | ||||
|     @PrimaryKey() | ||||
|     classId? = v4(); | ||||
|     classId? = generateClassId(); | ||||
| 
 | ||||
|     @Property({ type: 'string' }) | ||||
|     displayName!: string; | ||||
|  |  | |||
							
								
								
									
										47
									
								
								backend/tests/controllers/classes.test.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								backend/tests/controllers/classes.test.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,47 @@ | |||
| import { setupTestApp } from '../setup-tests.js'; | ||||
| import { describe, it, expect, beforeAll, beforeEach, vi, Mock } from 'vitest'; | ||||
| import { Request, Response } from 'express'; | ||||
| import { createClassHandler, deleteClassHandler } from '../../src/controllers/classes'; | ||||
| 
 | ||||
| describe('Class controllers', () => { | ||||
|     let req: Partial<Request>; | ||||
|     let res: Partial<Response>; | ||||
| 
 | ||||
|     let jsonMock: Mock; | ||||
|     let statusMock: Mock; | ||||
| 
 | ||||
|     beforeAll(async () => { | ||||
|         await setupTestApp(); | ||||
|     }); | ||||
| 
 | ||||
|     beforeEach(async () => { | ||||
|         jsonMock = vi.fn(); | ||||
|         statusMock = vi.fn().mockReturnThis(); | ||||
| 
 | ||||
|         res = { | ||||
|             json: jsonMock, | ||||
|             status: statusMock, | ||||
|         }; | ||||
|     }); | ||||
| 
 | ||||
|     it('create and delete class', async () => { | ||||
|         req = { | ||||
|             body: { displayName: 'coole_nieuwe_klas' }, | ||||
|         }; | ||||
| 
 | ||||
|         await createClassHandler(req as Request, res as Response); | ||||
| 
 | ||||
|         const result = jsonMock.mock.lastCall?.[0]; | ||||
|         // Console.log('class', result.class);
 | ||||
| 
 | ||||
|         expect(jsonMock).toHaveBeenCalledWith(expect.objectContaining({ class: expect.anything() })); | ||||
| 
 | ||||
|         req = { | ||||
|             params: { id: result.class.id }, | ||||
|         }; | ||||
| 
 | ||||
|         await deleteClassHandler(req as Request, res as Response); | ||||
| 
 | ||||
|         expect(jsonMock).toHaveBeenCalledWith(expect.objectContaining({ class: expect.anything() })); | ||||
|     }); | ||||
| }); | ||||
|  | @ -21,6 +21,7 @@ import { BadRequestException } from '../../src/exceptions/bad-request-exception. | |||
| import { ConflictException } from '../../src/exceptions/conflict-exception.js'; | ||||
| import { EntityAlreadyExistsException } from '../../src/exceptions/entity-already-exists-exception.js'; | ||||
| import { StudentDTO } from '@dwengo-1/common/interfaces/student'; | ||||
| import { getClass02 } from '../test_assets/classes/classes.testdata'; | ||||
| 
 | ||||
| describe('Student controllers', () => { | ||||
|     let req: Partial<Request>; | ||||
|  | @ -186,7 +187,7 @@ describe('Student controllers', () => { | |||
| 
 | ||||
|     it('Get join request by student and class', async () => { | ||||
|         req = { | ||||
|             params: { username: 'PinkFloyd', classId: '34d484a1-295f-4e9f-bfdc-3e7a23d86a89' }, | ||||
|             params: { username: 'PinkFloyd', classId: getClass02().classId }, | ||||
|         }; | ||||
| 
 | ||||
|         await getStudentRequestHandler(req as Request, res as Response); | ||||
|  | @ -201,7 +202,7 @@ describe('Student controllers', () => { | |||
|     it('Create and delete join request', async () => { | ||||
|         req = { | ||||
|             params: { username: 'TheDoors' }, | ||||
|             body: { classId: '34d484a1-295f-4e9f-bfdc-3e7a23d86a89' }, | ||||
|             body: { classId: getClass02().classId }, | ||||
|         }; | ||||
| 
 | ||||
|         await createStudentRequestHandler(req as Request, res as Response); | ||||
|  | @ -209,7 +210,7 @@ describe('Student controllers', () => { | |||
|         expect(jsonMock).toHaveBeenCalledWith(expect.objectContaining({ request: expect.anything() })); | ||||
| 
 | ||||
|         req = { | ||||
|             params: { username: 'TheDoors', classId: '34d484a1-295f-4e9f-bfdc-3e7a23d86a89' }, | ||||
|             params: { username: 'TheDoors', classId: getClass02().classId }, | ||||
|         }; | ||||
| 
 | ||||
|         await deleteClassJoinRequestHandler(req as Request, res as Response); | ||||
|  | @ -222,7 +223,7 @@ describe('Student controllers', () => { | |||
|     it('Create join request student already in class error', async () => { | ||||
|         req = { | ||||
|             params: { username: 'Noordkaap' }, | ||||
|             body: { classId: '34d484a1-295f-4e9f-bfdc-3e7a23d86a89' }, | ||||
|             body: { classId: getClass02().classId }, | ||||
|         }; | ||||
| 
 | ||||
|         await expect(async () => createStudentRequestHandler(req as Request, res as Response)).rejects.toThrow(ConflictException); | ||||
|  | @ -231,7 +232,7 @@ describe('Student controllers', () => { | |||
|     it('Create join request duplicate', async () => { | ||||
|         req = { | ||||
|             params: { username: 'Tool' }, | ||||
|             body: { classId: '34d484a1-295f-4e9f-bfdc-3e7a23d86a89' }, | ||||
|             body: { classId: getClass02().classId }, | ||||
|         }; | ||||
| 
 | ||||
|         await expect(async () => createStudentRequestHandler(req as Request, res as Response)).rejects.toThrow(ConflictException); | ||||
|  |  | |||
|  | @ -12,6 +12,7 @@ import { TeacherInvitationData } from '@dwengo-1/common/interfaces/teacher-invit | |||
| import { getClassHandler } from '../../src/controllers/classes'; | ||||
| import { BadRequestException } from '../../src/exceptions/bad-request-exception'; | ||||
| import { ClassStatus } from '@dwengo-1/common/util/class-join-request'; | ||||
| import { getClass02 } from '../test_assets/classes/classes.testdata'; | ||||
| 
 | ||||
| describe('Teacher controllers', () => { | ||||
|     let req: Partial<Request>; | ||||
|  | @ -57,7 +58,7 @@ describe('Teacher controllers', () => { | |||
|         const body = { | ||||
|             sender: 'LimpBizkit', | ||||
|             receiver: 'testleerkracht1', | ||||
|             class: '34d484a1-295f-4e9f-bfdc-3e7a23d86a89', | ||||
|             class: getClass02().classId, | ||||
|         } as TeacherInvitationData; | ||||
|         req = { body }; | ||||
| 
 | ||||
|  | @ -67,7 +68,7 @@ describe('Teacher controllers', () => { | |||
|             params: { | ||||
|                 sender: 'LimpBizkit', | ||||
|                 receiver: 'testleerkracht1', | ||||
|                 classId: '34d484a1-295f-4e9f-bfdc-3e7a23d86a89', | ||||
|                 classId: getClass02().classId, | ||||
|             }, | ||||
|             body: { accepted: 'false' }, | ||||
|         }; | ||||
|  | @ -80,7 +81,7 @@ describe('Teacher controllers', () => { | |||
|             params: { | ||||
|                 sender: 'LimpBizkit', | ||||
|                 receiver: 'FooFighters', | ||||
|                 classId: '34d484a1-295f-4e9f-bfdc-3e7a23d86a89', | ||||
|                 classId: getClass02().classId, | ||||
|             }, | ||||
|         }; | ||||
|         await getInvitationHandler(req as Request, res as Response); | ||||
|  | @ -100,7 +101,7 @@ describe('Teacher controllers', () => { | |||
|         const body = { | ||||
|             sender: 'LimpBizkit', | ||||
|             receiver: 'FooFighters', | ||||
|             class: '34d484a1-295f-4e9f-bfdc-3e7a23d86a89', | ||||
|             class: getClass02().classId, | ||||
|         } as TeacherInvitationData; | ||||
|         req = { body }; | ||||
| 
 | ||||
|  | @ -111,7 +112,7 @@ describe('Teacher controllers', () => { | |||
| 
 | ||||
|         req = { | ||||
|             params: { | ||||
|                 id: '34d484a1-295f-4e9f-bfdc-3e7a23d86a89', | ||||
|                 id: getClass02().classId, | ||||
|             }, | ||||
|         }; | ||||
| 
 | ||||
|  |  | |||
|  | @ -17,6 +17,7 @@ import { EntityAlreadyExistsException } from '../../src/exceptions/entity-alread | |||
| import { getStudentRequestsHandler } from '../../src/controllers/students.js'; | ||||
| import { TeacherDTO } from '@dwengo-1/common/interfaces/teacher'; | ||||
| import { getClassHandler } from '../../src/controllers/classes'; | ||||
| import { getClass02 } from '../test_assets/classes/classes.testdata'; | ||||
| 
 | ||||
| describe('Teacher controllers', () => { | ||||
|     let req: Partial<Request>; | ||||
|  | @ -169,7 +170,7 @@ describe('Teacher controllers', () => { | |||
| 
 | ||||
|     it('Get join requests by class', async () => { | ||||
|         req = { | ||||
|             params: { classId: '34d484a1-295f-4e9f-bfdc-3e7a23d86a89' }, | ||||
|             params: { classId: getClass02().classId }, | ||||
|         }; | ||||
| 
 | ||||
|         await getStudentJoinRequestHandler(req as Request, res as Response); | ||||
|  | @ -183,7 +184,7 @@ describe('Teacher controllers', () => { | |||
| 
 | ||||
|     it('Update join request status', async () => { | ||||
|         req = { | ||||
|             params: { classId: '34d484a1-295f-4e9f-bfdc-3e7a23d86a89', studentUsername: 'PinkFloyd' }, | ||||
|             params: { classId: getClass02().classId, studentUsername: 'PinkFloyd' }, | ||||
|             body: { accepted: 'true' }, | ||||
|         }; | ||||
| 
 | ||||
|  | @ -201,7 +202,7 @@ describe('Teacher controllers', () => { | |||
|         expect(status).toBeTruthy(); | ||||
| 
 | ||||
|         req = { | ||||
|             params: { id: '34d484a1-295f-4e9f-bfdc-3e7a23d86a89' }, | ||||
|             params: { id: getClass02().classId }, | ||||
|         }; | ||||
| 
 | ||||
|         await getClassHandler(req as Request, res as Response); | ||||
|  |  | |||
|  | @ -3,6 +3,7 @@ import { setupTestApp } from '../../setup-tests'; | |||
| import { AssignmentRepository } from '../../../src/data/assignments/assignment-repository'; | ||||
| import { getAssignmentRepository, getClassRepository } from '../../../src/data/repositories'; | ||||
| import { ClassRepository } from '../../../src/data/classes/class-repository'; | ||||
| import { getClass02 } from '../../test_assets/classes/classes.testdata'; | ||||
| 
 | ||||
| describe('AssignmentRepository', () => { | ||||
|     let assignmentRepository: AssignmentRepository; | ||||
|  | @ -15,7 +16,7 @@ describe('AssignmentRepository', () => { | |||
|     }); | ||||
| 
 | ||||
|     it('should return the requested assignment', async () => { | ||||
|         const class_ = await classRepository.findById('34d484a1-295f-4e9f-bfdc-3e7a23d86a89'); | ||||
|         const class_ = await classRepository.findById(getClass02().classId); | ||||
|         const assignment = await assignmentRepository.findByClassAndId(class_!, 21001); | ||||
| 
 | ||||
|         expect(assignment).toBeTruthy(); | ||||
|  | @ -23,7 +24,7 @@ describe('AssignmentRepository', () => { | |||
|     }); | ||||
| 
 | ||||
|     it('should return all assignments for a class', async () => { | ||||
|         const class_ = await classRepository.findById('34d484a1-295f-4e9f-bfdc-3e7a23d86a89'); | ||||
|         const class_ = await classRepository.findById(getClass02().classId); | ||||
|         const assignments = await assignmentRepository.findAllAssignmentsInClass(class_!); | ||||
| 
 | ||||
|         expect(assignments).toBeTruthy(); | ||||
|  |  | |||
|  | @ -4,6 +4,7 @@ import { GroupRepository } from '../../../src/data/assignments/group-repository' | |||
| import { getAssignmentRepository, getClassRepository, getGroupRepository } from '../../../src/data/repositories'; | ||||
| import { AssignmentRepository } from '../../../src/data/assignments/assignment-repository'; | ||||
| import { ClassRepository } from '../../../src/data/classes/class-repository'; | ||||
| import { getClass01, getClass02 } from '../../test_assets/classes/classes.testdata'; | ||||
| 
 | ||||
| describe('GroupRepository', () => { | ||||
|     let groupRepository: GroupRepository; | ||||
|  | @ -18,7 +19,8 @@ describe('GroupRepository', () => { | |||
|     }); | ||||
| 
 | ||||
|     it('should return the requested group', async () => { | ||||
|         const class_ = await classRepository.findById('8764b861-90a6-42e5-9732-c0d9eb2f55f9'); | ||||
|         const id = getClass01().classId; | ||||
|         const class_ = await classRepository.findById(id); | ||||
|         const assignment = await assignmentRepository.findByClassAndId(class_!, 21000); | ||||
| 
 | ||||
|         const group = await groupRepository.findByAssignmentAndGroupNumber(assignment!, 21001); | ||||
|  | @ -27,7 +29,7 @@ describe('GroupRepository', () => { | |||
|     }); | ||||
| 
 | ||||
|     it('should return all groups for assignment', async () => { | ||||
|         const class_ = await classRepository.findById('8764b861-90a6-42e5-9732-c0d9eb2f55f9'); | ||||
|         const class_ = await classRepository.findById(getClass01().classId); | ||||
|         const assignment = await assignmentRepository.findByClassAndId(class_!, 21000); | ||||
| 
 | ||||
|         const groups = await groupRepository.findAllGroupsForAssignment(assignment!); | ||||
|  | @ -37,7 +39,7 @@ describe('GroupRepository', () => { | |||
|     }); | ||||
| 
 | ||||
|     it('should not find removed group', async () => { | ||||
|         const class_ = await classRepository.findById('34d484a1-295f-4e9f-bfdc-3e7a23d86a89'); | ||||
|         const class_ = await classRepository.findById(getClass02().classId); | ||||
|         const assignment = await assignmentRepository.findByClassAndId(class_!, 21001); | ||||
| 
 | ||||
|         await groupRepository.deleteByAssignmentAndGroupNumber(assignment!, 21001); | ||||
|  |  | |||
|  | @ -18,6 +18,7 @@ import { Submission } from '../../../src/entities/assignments/submission.entity' | |||
| import { Class } from '../../../src/entities/classes/class.entity'; | ||||
| import { Assignment } from '../../../src/entities/assignments/assignment.entity'; | ||||
| import { testLearningObject01 } from '../../test_assets/content/learning-objects.testdata'; | ||||
| import { getClass01 } from '../../test_assets/classes/classes.testdata'; | ||||
| 
 | ||||
| describe('SubmissionRepository', () => { | ||||
|     let submissionRepository: SubmissionRepository; | ||||
|  | @ -54,7 +55,7 @@ describe('SubmissionRepository', () => { | |||
| 
 | ||||
|     it('should find the most recent submission for a group', async () => { | ||||
|         const id = new LearningObjectIdentifier('id03', Language.English, 1); | ||||
|         const class_ = await classRepository.findById('8764b861-90a6-42e5-9732-c0d9eb2f55f9'); | ||||
|         const class_ = await classRepository.findById(getClass01().classId); | ||||
|         const assignment = await assignmentRepository.findByClassAndId(class_!, 21000); | ||||
|         const group = await groupRepository.findByAssignmentAndGroupNumber(assignment!, 21001); | ||||
|         const submission = await submissionRepository.findMostRecentSubmissionForGroup(id, group!); | ||||
|  | @ -67,7 +68,7 @@ describe('SubmissionRepository', () => { | |||
|     let assignment: Assignment | null; | ||||
|     let loId: LearningObjectIdentifier; | ||||
|     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(getClass01().classId); | ||||
|         assignment = await assignmentRepository.findByClassAndId(clazz!, 21000); | ||||
|         loId = { | ||||
|             hruid: 'id02', | ||||
|  |  | |||
|  | @ -4,6 +4,7 @@ import { ClassJoinRequestRepository } from '../../../src/data/classes/class-join | |||
| import { getClassJoinRequestRepository, getClassRepository, getStudentRepository } from '../../../src/data/repositories'; | ||||
| import { StudentRepository } from '../../../src/data/users/student-repository'; | ||||
| import { ClassRepository } from '../../../src/data/classes/class-repository'; | ||||
| import { getClass02, getClass03 } from '../../test_assets/classes/classes.testdata'; | ||||
| 
 | ||||
| describe('ClassJoinRequestRepository', () => { | ||||
|     let classJoinRequestRepository: ClassJoinRequestRepository; | ||||
|  | @ -26,7 +27,7 @@ describe('ClassJoinRequestRepository', () => { | |||
|     }); | ||||
| 
 | ||||
|     it('should list all requests to a single class', async () => { | ||||
|         const class_ = await cassRepository.findById('34d484a1-295f-4e9f-bfdc-3e7a23d86a89'); | ||||
|         const class_ = await cassRepository.findById(getClass02().classId); | ||||
|         const requests = await classJoinRequestRepository.findAllOpenRequestsTo(class_!); | ||||
| 
 | ||||
|         expect(requests).toBeTruthy(); | ||||
|  | @ -35,7 +36,7 @@ describe('ClassJoinRequestRepository', () => { | |||
| 
 | ||||
|     it('should not find a removed request', async () => { | ||||
|         const student = await studentRepository.findByUsername('SmashingPumpkins'); | ||||
|         const class_ = await cassRepository.findById('80dcc3e0-1811-4091-9361-42c0eee91cfa'); | ||||
|         const class_ = await cassRepository.findById(getClass03().classId); | ||||
|         await classJoinRequestRepository.deleteBy(student!, class_!); | ||||
| 
 | ||||
|         const request = await classJoinRequestRepository.findAllRequestsBy(student!); | ||||
|  |  | |||
|  | @ -2,6 +2,7 @@ import { beforeAll, describe, expect, it } from 'vitest'; | |||
| import { ClassRepository } from '../../../src/data/classes/class-repository'; | ||||
| import { setupTestApp } from '../../setup-tests'; | ||||
| import { getClassRepository } from '../../../src/data/repositories'; | ||||
| import { getClass01, getClass04 } from '../../test_assets/classes/classes.testdata'; | ||||
| 
 | ||||
| describe('ClassRepository', () => { | ||||
|     let classRepository: ClassRepository; | ||||
|  | @ -18,16 +19,16 @@ describe('ClassRepository', () => { | |||
|     }); | ||||
| 
 | ||||
|     it('should return requested class', async () => { | ||||
|         const classVar = await classRepository.findById('8764b861-90a6-42e5-9732-c0d9eb2f55f9'); | ||||
|         const classVar = await classRepository.findById(getClass01().classId); | ||||
| 
 | ||||
|         expect(classVar).toBeTruthy(); | ||||
|         expect(classVar?.displayName).toBe('class01'); | ||||
|     }); | ||||
| 
 | ||||
|     it('class should be gone after deletion', async () => { | ||||
|         await classRepository.deleteById('33d03536-83b8-4880-9982-9bbf2f908ddf'); | ||||
|         await classRepository.deleteById(getClass04().classId); | ||||
| 
 | ||||
|         const classVar = await classRepository.findById('33d03536-83b8-4880-9982-9bbf2f908ddf'); | ||||
|         const classVar = await classRepository.findById(getClass04().classId); | ||||
| 
 | ||||
|         expect(classVar).toBeNull(); | ||||
|     }); | ||||
|  |  | |||
|  | @ -4,6 +4,7 @@ import { getClassRepository, getTeacherInvitationRepository, getTeacherRepositor | |||
| import { TeacherInvitationRepository } from '../../../src/data/classes/teacher-invitation-repository'; | ||||
| import { TeacherRepository } from '../../../src/data/users/teacher-repository'; | ||||
| import { ClassRepository } from '../../../src/data/classes/class-repository'; | ||||
| import { getClass01, getClass02 } from '../../test_assets/classes/classes.testdata'; | ||||
| 
 | ||||
| describe('ClassRepository', () => { | ||||
|     let teacherInvitationRepository: TeacherInvitationRepository; | ||||
|  | @ -34,7 +35,7 @@ describe('ClassRepository', () => { | |||
|     }); | ||||
| 
 | ||||
|     it('should return all invitations for a class', async () => { | ||||
|         const class_ = await classRepository.findById('34d484a1-295f-4e9f-bfdc-3e7a23d86a89'); | ||||
|         const class_ = await classRepository.findById(getClass02().classId); | ||||
|         const invitations = await teacherInvitationRepository.findAllInvitationsForClass(class_!); | ||||
| 
 | ||||
|         expect(invitations).toBeTruthy(); | ||||
|  | @ -42,7 +43,7 @@ describe('ClassRepository', () => { | |||
|     }); | ||||
| 
 | ||||
|     it('should not find a removed invitation', async () => { | ||||
|         const class_ = await classRepository.findById('8764b861-90a6-42e5-9732-c0d9eb2f55f9'); | ||||
|         const class_ = await classRepository.findById(getClass01().classId); | ||||
|         const sender = await teacherRepository.findByUsername('FooFighters'); | ||||
|         const receiver = await teacherRepository.findByUsername('LimpBizkit'); | ||||
|         await teacherInvitationRepository.deleteBy(class_!, sender!, receiver!); | ||||
|  |  | |||
|  | @ -14,6 +14,7 @@ import { Language } from '@dwengo-1/common/util/language'; | |||
| import { Question } from '../../../src/entities/questions/question.entity'; | ||||
| import { Class } from '../../../src/entities/classes/class.entity'; | ||||
| import { Assignment } from '../../../src/entities/assignments/assignment.entity'; | ||||
| import { getClass01 } from '../../test_assets/classes/classes.testdata'; | ||||
| 
 | ||||
| describe('QuestionRepository', () => { | ||||
|     let questionRepository: QuestionRepository; | ||||
|  | @ -37,7 +38,7 @@ describe('QuestionRepository', () => { | |||
|         const id = new LearningObjectIdentifier('id03', Language.English, 1); | ||||
|         const student = await studentRepository.findByUsername('Noordkaap'); | ||||
| 
 | ||||
|         const clazz = await getClassRepository().findById('8764b861-90a6-42e5-9732-c0d9eb2f55f9'); | ||||
|         const clazz = await getClassRepository().findById(getClass01().classId); | ||||
|         const assignment = await getAssignmentRepository().findByClassAndId(clazz!, 21000); | ||||
|         const group = await getGroupRepository().findByAssignmentAndGroupNumber(assignment!, 21001); | ||||
|         await questionRepository.createQuestion({ | ||||
|  | @ -56,7 +57,7 @@ describe('QuestionRepository', () => { | |||
|     let assignment: Assignment | null; | ||||
|     let loId: LearningObjectIdentifier; | ||||
|     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(getClass01().classId); | ||||
|         assignment = await getAssignmentRepository().findByClassAndId(clazz!, 21000); | ||||
|         loId = { | ||||
|             hruid: 'id05', | ||||
|  |  | |||
|  | @ -10,7 +10,7 @@ export function makeTestClasses(em: EntityManager, students: Student[], teachers | |||
|     const teacherClass01: Teacher[] = teachers.slice(4, 5); | ||||
| 
 | ||||
|     class01 = em.create(Class, { | ||||
|         classId: '8764b861-90a6-42e5-9732-c0d9eb2f55f9', | ||||
|         classId: 'X2J9QT', // 8764b861-90a6-42e5-9732-c0d9eb2f55f9
 | ||||
|         displayName: 'class01', | ||||
|         teachers: teacherClass01, | ||||
|         students: studentsClass01, | ||||
|  | @ -20,7 +20,7 @@ export function makeTestClasses(em: EntityManager, students: Student[], teachers | |||
|     const teacherClass02: Teacher[] = teachers.slice(1, 2); | ||||
| 
 | ||||
|     class02 = em.create(Class, { | ||||
|         classId: '34d484a1-295f-4e9f-bfdc-3e7a23d86a89', | ||||
|         classId: '7KLPMA', // 34d484a1-295f-4e9f-bfdc-3e7a23d86a89
 | ||||
|         displayName: 'class02', | ||||
|         teachers: teacherClass02, | ||||
|         students: studentsClass02, | ||||
|  | @ -30,7 +30,7 @@ export function makeTestClasses(em: EntityManager, students: Student[], teachers | |||
|     const teacherClass03: Teacher[] = teachers.slice(2, 3); | ||||
| 
 | ||||
|     class03 = em.create(Class, { | ||||
|         classId: '80dcc3e0-1811-4091-9361-42c0eee91cfa', | ||||
|         classId: 'R0D3UZ', // 80dcc3e0-1811-4091-9361-42c0eee91cfa
 | ||||
|         displayName: 'class03', | ||||
|         teachers: teacherClass03, | ||||
|         students: studentsClass03, | ||||
|  | @ -40,14 +40,14 @@ export function makeTestClasses(em: EntityManager, students: Student[], teachers | |||
|     const teacherClass04: Teacher[] = teachers.slice(2, 3); | ||||
| 
 | ||||
|     class04 = em.create(Class, { | ||||
|         classId: '33d03536-83b8-4880-9982-9bbf2f908ddf', | ||||
|         classId: 'Q8N5YC', // 33d03536-83b8-4880-9982-9bbf2f908ddf
 | ||||
|         displayName: 'class04', | ||||
|         teachers: teacherClass04, | ||||
|         students: studentsClass04, | ||||
|     }); | ||||
| 
 | ||||
|     classWithTestleerlingAndTestleerkracht = em.create(Class, { | ||||
|         classId: 'a75298b5-18aa-471d-8eeb-5d77eb989393', | ||||
|         classId: 'ZAV71B', // Was a75298b5-18aa-471d-8eeb-5d77eb989393
 | ||||
|         displayName: 'Testklasse', | ||||
|         teachers: [getTestleerkracht1()], | ||||
|         students: [getTestleerling1()], | ||||
|  |  | |||
|  | @ -14,6 +14,7 @@ | |||
|     const _router = useRouter(); // Zonder '_' gaf dit een linter error voor unused variable | ||||
| 
 | ||||
|     const name: string = auth.authState.user!.profile.name!; | ||||
|     const email = auth.authState.user!.profile.email; | ||||
|     const initials: string = name | ||||
|         .split(" ") | ||||
|         .map((n) => n[0]) | ||||
|  | @ -90,31 +91,34 @@ | |||
|             <!--            >--> | ||||
|             <!--                {{ t("discussions") }}--> | ||||
|             <!--            </v-btn>--> | ||||
|             <v-menu open-on-hover> | ||||
|                 <template v-slot:activator="{ props }"> | ||||
|                     <v-btn | ||||
|                         v-bind="props" | ||||
|                         icon | ||||
|                         variant="text" | ||||
|                     > | ||||
|                         <v-icon | ||||
|                             icon="mdi-translate" | ||||
|                             size="small" | ||||
|                             color="#0e6942" | ||||
|                         ></v-icon> | ||||
|                     </v-btn> | ||||
|                 </template> | ||||
|                 <v-list> | ||||
|                     <v-list-item | ||||
|                         v-for="(language, index) in languages" | ||||
|                         :key="index" | ||||
|                         @click="changeLanguage(language.code)" | ||||
|                     > | ||||
|                         <v-list-item-title>{{ language.name }}</v-list-item-title> | ||||
|                     </v-list-item> | ||||
|                 </v-list> | ||||
|             </v-menu> | ||||
|         </v-toolbar-items> | ||||
|         <v-menu | ||||
|             open-on-hover | ||||
|             open-on-click | ||||
|         > | ||||
|             <template v-slot:activator="{ props }"> | ||||
|                 <v-btn | ||||
|                     v-bind="props" | ||||
|                     icon | ||||
|                     variant="text" | ||||
|                 > | ||||
|                     <v-icon | ||||
|                         icon="mdi-translate" | ||||
|                         size="small" | ||||
|                         color="#0e6942" | ||||
|                     ></v-icon> | ||||
|                 </v-btn> | ||||
|             </template> | ||||
|             <v-list> | ||||
|                 <v-list-item | ||||
|                     v-for="(language, index) in languages" | ||||
|                     :key="index" | ||||
|                     @click="changeLanguage(language.code)" | ||||
|                 > | ||||
|                     <v-list-item-title>{{ language.name }}</v-list-item-title> | ||||
|                 </v-list-item> | ||||
|             </v-list> | ||||
|         </v-menu> | ||||
|         <v-spacer></v-spacer> | ||||
|         <v-dialog max-width="500"> | ||||
|             <template v-slot:activator="{ props: activatorProps }"> | ||||
|  | @ -158,12 +162,43 @@ | |||
|                 </v-card> | ||||
|             </template> | ||||
|         </v-dialog> | ||||
|         <v-avatar | ||||
|             size="large" | ||||
|             color="#0e6942" | ||||
|             class="user-button" | ||||
|             >{{ initials }}</v-avatar | ||||
|         > | ||||
|         <v-menu min-width="200px"> | ||||
|             <template v-slot:activator="{ props }"> | ||||
|                 <v-btn | ||||
|                     icon | ||||
|                     v-bind="props" | ||||
|                 > | ||||
|                     <v-avatar | ||||
|                         color="#0e6942" | ||||
|                         size="large" | ||||
|                         class="user-button" | ||||
|                     > | ||||
|                         <span>{{ initials }}</span> | ||||
|                     </v-avatar> | ||||
|                 </v-btn> | ||||
|             </template> | ||||
|             <v-card> | ||||
|                 <v-card-text> | ||||
|                     <div class="mx-auto text-center"> | ||||
|                         <v-avatar color="#0e6942"> | ||||
|                             <span class="text-h5">{{ initials }}</span> | ||||
|                         </v-avatar> | ||||
|                         <h3>{{ name }}</h3> | ||||
|                         <p class="text-caption mt-1">{{ email }}</p> | ||||
|                         <v-divider class="my-3"></v-divider> | ||||
|                         <v-btn | ||||
|                             variant="text" | ||||
|                             rounded | ||||
|                             append-icon="mdi-logout" | ||||
|                             @click="performLogout" | ||||
|                             to="/login" | ||||
|                             >{{ t("logout") }}</v-btn | ||||
|                         > | ||||
|                         <v-divider class="my-3"></v-divider> | ||||
|                     </div> | ||||
|                 </v-card-text> | ||||
|             </v-card> | ||||
|         </v-menu> | ||||
|     </v-app-bar> | ||||
|     <v-navigation-drawer | ||||
|         v-model="drawer" | ||||
|  | @ -248,6 +283,12 @@ | |||
|         text-transform: none; | ||||
|     } | ||||
| 
 | ||||
|     .translate-button { | ||||
|         z-index: 1; | ||||
|         position: relative; | ||||
|         margin-left: 10px; | ||||
|     } | ||||
| 
 | ||||
|     @media (max-width: 700px) { | ||||
|         .menu { | ||||
|             display: none; | ||||
|  |  | |||
|  | @ -84,7 +84,10 @@ | |||
|                 </div> | ||||
|             </div> | ||||
|             <div class="container_right"> | ||||
|                 <v-menu open-on-hover> | ||||
|                 <v-menu | ||||
|                     open-on-hover | ||||
|                     open-on-click | ||||
|                 > | ||||
|                     <template v-slot:activator="{ props }"> | ||||
|                         <v-btn | ||||
|                             v-bind="props" | ||||
|  |  | |||
|  | @ -1,6 +1,20 @@ | |||
| <script setup lang="ts"> | ||||
|     import { useRouter } from "vue-router"; | ||||
|     import dwengoLogo from "../../../assets/img/dwengo-groen-zwart.svg"; | ||||
|     import auth from "@/services/auth/auth-service.ts"; | ||||
|     import { watch } from "vue"; | ||||
| 
 | ||||
|     const router = useRouter(); | ||||
| 
 | ||||
|     watch( | ||||
|         () => auth.isLoggedIn.value, | ||||
|         async (newVal) => { | ||||
|             if (newVal) { | ||||
|                 await router.push("/user"); | ||||
|             } | ||||
|         }, | ||||
|         { immediate: true }, | ||||
|     ); | ||||
| 
 | ||||
|     async function loginAsStudent(): Promise<void> { | ||||
|         await auth.loginAs("student"); | ||||
|  | @ -9,10 +23,6 @@ | |||
|     async function loginAsTeacher(): Promise<void> { | ||||
|         await auth.loginAs("teacher"); | ||||
|     } | ||||
| 
 | ||||
|     async function performLogout(): Promise<void> { | ||||
|         await auth.logout(); | ||||
|     } | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|  | @ -65,13 +75,6 @@ | |||
|                 </div> | ||||
|             </ul> | ||||
|         </div> | ||||
|         <div v-if="auth.isLoggedIn.value"> | ||||
|             <p> | ||||
|                 You are currently logged in as {{ auth.authState.user!.profile.name }} ({{ auth.authState.activeRole }}) | ||||
|             </p> | ||||
|             <v-btn @click="performLogout">Logout</v-btn> | ||||
|             <v-btn to="/user">home</v-btn> | ||||
|         </div> | ||||
|     </main> | ||||
| </template> | ||||
| 
 | ||||
|  |  | |||
|  | @ -22,8 +22,7 @@ | |||
|         ) => { groupProgressMap: Map<number, number> }; | ||||
|     }>(); | ||||
| 
 | ||||
|     const { t, locale } = useI18n(); | ||||
|     const language = ref<Language>(locale.value as Language); | ||||
|     const { t } = useI18n(); | ||||
|     const learningPath = ref(); | ||||
|     // Get the user's username/id | ||||
|     const username = asyncComputed(async () => { | ||||
|  | @ -38,7 +37,7 @@ | |||
| 
 | ||||
|     const lpQueryResult = useGetLearningPathQuery( | ||||
|         computed(() => assignmentQueryResult.data.value?.assignment?.learningPath ?? ""), | ||||
|         computed(() => language.value), | ||||
|         computed(() => assignmentQueryResult.data.value?.assignment.language as Language), | ||||
|     ); | ||||
| 
 | ||||
|     const groupsQueryResult = useGroupsQuery(props.classId, props.assignmentId, true); | ||||
|  | @ -100,7 +99,7 @@ language | |||
|                     > | ||||
|                         <v-btn | ||||
|                             v-if="lpData" | ||||
|                             :to="`/learningPath/${lpData.hruid}/${language}/${lpData.startNode.learningobjectHruid}?forGroup=${group?.groupNumber}&assignmentNo=${assignmentId}&classId=${classId}`" | ||||
|                             :to="`/learningPath/${lpData.hruid}/${assignmentQueryResult.data.value?.assignment.language}/${lpData.startNode.learningobjectHruid}?forGroup=${group?.groupNumber}&assignmentNo=${assignmentId}&classId=${classId}`" | ||||
|                             variant="tonal" | ||||
|                             color="primary" | ||||
|                         > | ||||
|  |  | |||
|  | @ -19,8 +19,7 @@ | |||
|         ) => { groupProgressMap: Map<number, number> }; | ||||
|     }>(); | ||||
| 
 | ||||
|     const { t, locale } = useI18n(); | ||||
|     const language = computed(() => locale.value); | ||||
|     const { t } = useI18n(); | ||||
|     const groups = ref(); | ||||
|     const learningPath = ref(); | ||||
| 
 | ||||
|  | @ -29,7 +28,7 @@ | |||
|     // Get learning path object | ||||
|     const lpQueryResult = useGetLearningPathQuery( | ||||
|         computed(() => assignmentQueryResult.data.value?.assignment?.learningPath ?? ""), | ||||
|         computed(() => language.value as Language), | ||||
|         computed(() => assignmentQueryResult.data.value?.assignment.language as Language), | ||||
|     ); | ||||
| 
 | ||||
|     // Get all the groups withing the assignment | ||||
|  | @ -38,9 +37,9 @@ | |||
| 
 | ||||
|     /* Crashes right now cause api data has inexistent hruid TODO: uncomment later and use it in progress bar | ||||
| Const {groupProgressMap} = props.useGroupsWithProgress( | ||||
|     groups, | ||||
|     learningPath, | ||||
|     language | ||||
| groups, | ||||
| learningPath, | ||||
| language | ||||
| ); | ||||
| */ | ||||
| 
 | ||||
|  | @ -121,7 +120,7 @@ Const {groupProgressMap} = props.useGroupsWithProgress( | |||
|                     > | ||||
|                         <v-btn | ||||
|                             v-if="lpData" | ||||
|                             :to="`/learningPath/${lpData.hruid}/${language}/${lpData.startNode.learningobjectHruid}?assignmentNo=${assignmentId}&classId=${classId}`" | ||||
|                             :to="`/learningPath/${lpData.hruid}/${assignmentQueryResult.data.value?.assignment.language}/${lpData.startNode.learningobjectHruid}?assignmentNo=${assignmentId}&classId=${classId}`" | ||||
|                             variant="tonal" | ||||
|                             color="primary" | ||||
|                         > | ||||
|  | @ -203,8 +202,8 @@ Const {groupProgressMap} = props.useGroupsWithProgress( | |||
|                             <v-btn | ||||
|                                 color="primary" | ||||
|                                 @click="dialog = false" | ||||
|                                 >Close</v-btn | ||||
|                             > | ||||
|                                 >Close | ||||
|                             </v-btn> | ||||
|                         </v-card-actions> | ||||
|                     </v-card> | ||||
|                 </v-dialog> | ||||
|  |  | |||
|  | @ -143,6 +143,13 @@ | |||
|         box-sizing: border-box; | ||||
|     } | ||||
| 
 | ||||
|     h1 { | ||||
|         color: #0e6942; | ||||
|         text-transform: uppercase; | ||||
|         font-weight: bolder; | ||||
|         font-size: 50px; | ||||
|     } | ||||
| 
 | ||||
|     .center-btn { | ||||
|         display: block; | ||||
|         margin-left: auto; | ||||
|  |  | |||
|  | @ -95,6 +95,13 @@ | |||
|         justify-content: center; | ||||
|     } | ||||
| 
 | ||||
|     h1 { | ||||
|         color: #0e6942; | ||||
|         text-transform: uppercase; | ||||
|         font-weight: bolder; | ||||
|         font-size: 50px; | ||||
|     } | ||||
| 
 | ||||
|     .dropdowns { | ||||
|         display: flex; | ||||
|         justify-content: space-between; | ||||
|  |  | |||
|  | @ -287,6 +287,8 @@ | |||
|                     <template v-slot:default> | ||||
|                         <v-btn | ||||
|                             class="button-in-nav" | ||||
|                             width="100%" | ||||
|                             :color="COLORS.teacherExclusive" | ||||
|                             @click="assign()" | ||||
|                             >{{ t("assignLearningPath") }}</v-btn | ||||
|                         > | ||||
|  |  | |||
|  | @ -5,7 +5,7 @@ describe("AssignmentController Tests", () => { | |||
|     let controller: AssignmentController; | ||||
| 
 | ||||
|     beforeEach(() => { | ||||
|         controller = new AssignmentController("8764b861-90a6-42e5-9732-c0d9eb2f55f9"); // Example class ID
 | ||||
|         controller = new AssignmentController("X2J9QT"); // Example class ID (class01)
 | ||||
|     }); | ||||
| 
 | ||||
|     it("should fetch all assignments", async () => { | ||||
|  |  | |||
|  | @ -3,7 +3,7 @@ import { GroupController } from "../../src/controllers/groups"; | |||
| 
 | ||||
| describe("Test controller groups", () => { | ||||
|     it("Get groups", async () => { | ||||
|         const classId = "8764b861-90a6-42e5-9732-c0d9eb2f55f9"; | ||||
|         const classId = "X2J9QT"; // Class01
 | ||||
|         const assignmentNumber = 21000; | ||||
| 
 | ||||
|         const controller = new GroupController(classId, assignmentNumber); | ||||
|  |  | |||
|  | @ -5,7 +5,7 @@ import { Language } from "../../src/data-objects/language"; | |||
| describe("Test controller submissions", () => { | ||||
|     it("Get submission by number", async () => { | ||||
|         const hruid = "id03"; | ||||
|         const classId = "8764b861-90a6-42e5-9732-c0d9eb2f55f9"; | ||||
|         const classId = "X2J9QT"; // Class01
 | ||||
|         const controller = new SubmissionController(hruid); | ||||
| 
 | ||||
|         const data = await controller.getByNumber(Language.English, 1, classId, 1, 1, 1); | ||||
|  |  | |||
							
								
								
									
										9640
									
								
								package-lock.json
									
										
									
										generated
									
									
									
								
							
							
						
						
									
										9640
									
								
								package-lock.json
									
										
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							
		Reference in a new issue
	
	 Gerald Schmittinger
						Gerald Schmittinger