diff --git a/backend/package.json b/backend/package.json index a95cd334..4590067b 100644 --- a/backend/package.json +++ b/backend/package.json @@ -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", diff --git a/backend/src/entities/classes/class.entity.ts b/backend/src/entities/classes/class.entity.ts index b2c59ade..5bedf560 100644 --- a/backend/src/entities/classes/class.entity.ts +++ b/backend/src/entities/classes/class.entity.ts @@ -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; diff --git a/backend/tests/controllers/classes.test.ts b/backend/tests/controllers/classes.test.ts new file mode 100644 index 00000000..ab941773 --- /dev/null +++ b/backend/tests/controllers/classes.test.ts @@ -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; + let res: Partial; + + 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() })); + }); +}); diff --git a/backend/tests/controllers/students.test.ts b/backend/tests/controllers/students.test.ts index aca29de1..dbd22d46 100644 --- a/backend/tests/controllers/students.test.ts +++ b/backend/tests/controllers/students.test.ts @@ -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; @@ -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); diff --git a/backend/tests/controllers/teacher-invitations.test.ts b/backend/tests/controllers/teacher-invitations.test.ts index ed2f5ebf..675efea1 100644 --- a/backend/tests/controllers/teacher-invitations.test.ts +++ b/backend/tests/controllers/teacher-invitations.test.ts @@ -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; @@ -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, }, }; diff --git a/backend/tests/controllers/teachers.test.ts b/backend/tests/controllers/teachers.test.ts index a73a79a5..720365a4 100644 --- a/backend/tests/controllers/teachers.test.ts +++ b/backend/tests/controllers/teachers.test.ts @@ -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; @@ -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); diff --git a/backend/tests/data/assignments/assignments.test.ts b/backend/tests/data/assignments/assignments.test.ts index 74c858b3..f5452035 100644 --- a/backend/tests/data/assignments/assignments.test.ts +++ b/backend/tests/data/assignments/assignments.test.ts @@ -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(); diff --git a/backend/tests/data/assignments/groups.test.ts b/backend/tests/data/assignments/groups.test.ts index efd477ab..2368085d 100644 --- a/backend/tests/data/assignments/groups.test.ts +++ b/backend/tests/data/assignments/groups.test.ts @@ -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); diff --git a/backend/tests/data/assignments/submissions.test.ts b/backend/tests/data/assignments/submissions.test.ts index ea2341bc..77c163d2 100644 --- a/backend/tests/data/assignments/submissions.test.ts +++ b/backend/tests/data/assignments/submissions.test.ts @@ -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', diff --git a/backend/tests/data/classes/class-join-request.test.ts b/backend/tests/data/classes/class-join-request.test.ts index afb83766..4e18c116 100644 --- a/backend/tests/data/classes/class-join-request.test.ts +++ b/backend/tests/data/classes/class-join-request.test.ts @@ -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!); diff --git a/backend/tests/data/classes/classes.test.ts b/backend/tests/data/classes/classes.test.ts index f87f83ed..6930e5ac 100644 --- a/backend/tests/data/classes/classes.test.ts +++ b/backend/tests/data/classes/classes.test.ts @@ -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(); }); diff --git a/backend/tests/data/classes/teacher-invitation.test.ts b/backend/tests/data/classes/teacher-invitation.test.ts index f8afa36d..664e41d2 100644 --- a/backend/tests/data/classes/teacher-invitation.test.ts +++ b/backend/tests/data/classes/teacher-invitation.test.ts @@ -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!); diff --git a/backend/tests/data/questions/questions.test.ts b/backend/tests/data/questions/questions.test.ts index 8ad2d47c..950845a6 100644 --- a/backend/tests/data/questions/questions.test.ts +++ b/backend/tests/data/questions/questions.test.ts @@ -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', diff --git a/backend/tests/test_assets/classes/classes.testdata.ts b/backend/tests/test_assets/classes/classes.testdata.ts index 7b5f2976..ae522a81 100644 --- a/backend/tests/test_assets/classes/classes.testdata.ts +++ b/backend/tests/test_assets/classes/classes.testdata.ts @@ -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()], diff --git a/frontend/src/components/MenuBar.vue b/frontend/src/components/MenuBar.vue index e3734976..a58be2f8 100644 --- a/frontend/src/components/MenuBar.vue +++ b/frontend/src/components/MenuBar.vue @@ -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 @@ - - - - - {{ language.name }} - - - + + + + + {{ language.name }} + + + - {{ initials }} + + + + +
+ + {{ initials }} + +

{{ name }}

+

{{ email }}

+ + {{ t("logout") }} + +
+
+
+
- +