feat: (frontend) queries teacher + test controller teacher
This commit is contained in:
		
							parent
							
								
									5e0f284131
								
							
						
					
					
						commit
						44c242fc57
					
				
					 11 changed files with 184 additions and 59 deletions
				
			
		|  | @ -28,27 +28,20 @@ export class BaseController { | |||
|         return res.json(); | ||||
|     } | ||||
| 
 | ||||
|     protected async post<T>(path: string, body?: unknown): Promise<T> { | ||||
|         const options: RequestInit = { | ||||
|     protected async post(path: string, body: unknown): Promise<void> { | ||||
|         const res = await fetch(`${this.baseUrl}${path}`, { | ||||
|             method: "POST", | ||||
|             headers: { "Content-Type": "application/json" }, | ||||
|         }; | ||||
| 
 | ||||
|         if (body !== undefined) { | ||||
|             options.body = JSON.stringify(body); | ||||
|         } | ||||
| 
 | ||||
|         const res = await fetch(`${this.baseUrl}${path}`, options); | ||||
|             body: JSON.stringify(body), | ||||
|         }); | ||||
| 
 | ||||
|         if (!res.ok) { | ||||
|             const errorData = await res.json().catch(() => ({})); | ||||
|             throw new Error(errorData?.error || `Error ${res.status}: ${res.statusText}`); | ||||
|         } | ||||
| 
 | ||||
|         return res.json(); | ||||
|     } | ||||
| 
 | ||||
|     protected async delete<T>(path: string): Promise<T> { | ||||
|     protected async delete(path: string): Promise<void> { | ||||
|         const res = await fetch(`${this.baseUrl}${path}`, { | ||||
|             method: "DELETE", | ||||
|         }); | ||||
|  | @ -57,11 +50,9 @@ export class BaseController { | |||
|             const errorData = await res.json().catch(() => ({})); | ||||
|             throw new Error(errorData?.error || `Error ${res.status}: ${res.statusText}`); | ||||
|         } | ||||
| 
 | ||||
|         return res.json(); | ||||
|     } | ||||
| 
 | ||||
|     protected async put<T>(path: string, body: unknown): Promise<T> { | ||||
|     protected async put(path: string, body: unknown): Promise<void> { | ||||
|         const res = await fetch(`${this.baseUrl}${path}`, { | ||||
|             method: "PUT", | ||||
|             headers: { "Content-Type": "application/json" }, | ||||
|  | @ -72,7 +63,5 @@ export class BaseController { | |||
|             const errorData = await res.json().catch(() => ({})); | ||||
|             throw new Error(errorData?.error || `Error ${res.status}: ${res.statusText}`); | ||||
|         } | ||||
| 
 | ||||
|         return res.json(); | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -1,18 +0,0 @@ | |||
| import { StudentController } from "@/controllers/students.ts"; | ||||
| import { TeacherController } from "@/controllers/teachers.ts"; | ||||
| import {ThemeController} from "@/controllers/themes.ts"; | ||||
| 
 | ||||
| export function controllerGetter<T>(Factory: new () => T): () => T { | ||||
|     let instance: T | undefined; | ||||
| 
 | ||||
|     return (): T => { | ||||
|         if (!instance) { | ||||
|             instance = new Factory(); | ||||
|         } | ||||
|         return instance; | ||||
|     }; | ||||
| } | ||||
| 
 | ||||
| export const getStudentController = controllerGetter(StudentController); | ||||
| export const getTeacherController = controllerGetter(TeacherController); | ||||
| export const getThemeController = controllerGetter(ThemeController); | ||||
|  | @ -14,11 +14,11 @@ export class StudentController extends BaseController { | |||
|     } | ||||
| 
 | ||||
|     createStudent(data: any) { | ||||
|         return this.post<{ student: any }>("/", data); | ||||
|         return this.post("/", data); | ||||
|     } | ||||
| 
 | ||||
|     deleteStudent(username: string) { | ||||
|         return this.delete<{ student: any }>(`/${username}`); | ||||
|         return this.delete(`/${username}`); | ||||
|     } | ||||
| 
 | ||||
|     getClasses(username: string, full = true) { | ||||
|  | @ -46,10 +46,10 @@ export class StudentController extends BaseController { | |||
|     } | ||||
| 
 | ||||
|     createJoinRequest(username: string, classId: string) { | ||||
|         return this.post<any>(`/${username}/joinRequests/${classId}`); | ||||
|         return this.post(`/${username}/joinRequests}`, classId); | ||||
|     } | ||||
| 
 | ||||
|     deleteJoinRequest(username: string, classId: string) { | ||||
|         return this.delete<any>(`/${username}/joinRequests/${classId}`); | ||||
|         return this.delete(`/${username}/joinRequests/${classId}`); | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -14,11 +14,11 @@ export class TeacherController extends BaseController { | |||
|     } | ||||
| 
 | ||||
|     createTeacher(data: any) { | ||||
|         return this.post<any>("/", data); | ||||
|         return this.post("/", data); | ||||
|     } | ||||
| 
 | ||||
|     deleteTeacher(username: string) { | ||||
|         return this.delete<any>(`/${username}`); | ||||
|         return this.delete(`/${username}`); | ||||
|     } | ||||
| 
 | ||||
|     getClasses(username: string, full = false) { | ||||
|  | @ -33,5 +33,13 @@ export class TeacherController extends BaseController { | |||
|         return this.get<{ questions: any[] }>(`/${username}/questions`, { full }); | ||||
|     } | ||||
| 
 | ||||
|     getStudentJoinRequests(username: string, classId: string){ | ||||
|         return this.get<{ joinRequests: any[] }>(`/${username}/joinRequests/${classId}`); | ||||
|     } | ||||
| 
 | ||||
|     updateStudentJoinRequest(teacherUsername: string, classId: string, studentUsername: string, accepted: boolean){ | ||||
|         return this.put(`/${teacherUsername}/joinRequests/${classId}/${studentUsername}`, accepted) | ||||
|     } | ||||
| 
 | ||||
|     // GetInvitations(id: string) {return this.get<{ invitations: string[] }>(`/${id}/invitations`);}
 | ||||
| } | ||||
|  |  | |||
|  | @ -1,9 +1,9 @@ | |||
| import { computed, toValue } from "vue"; | ||||
| import type { MaybeRefOrGetter } from "vue"; | ||||
| import {useMutation, useQuery, useQueryClient} from "@tanstack/vue-query"; | ||||
| import { getStudentController } from "@/controllers/controllers.ts"; | ||||
| import {StudentController} from "@/controllers/students.ts"; | ||||
| 
 | ||||
| const studentController = getStudentController(); | ||||
| const studentController = new StudentController(); | ||||
| 
 | ||||
| /** 🔑 Query keys */ | ||||
| const STUDENTS_QUERY_KEY = (full: boolean) => ['students', full]; | ||||
|  |  | |||
							
								
								
									
										108
									
								
								frontend/src/queries/teachers.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										108
									
								
								frontend/src/queries/teachers.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,108 @@ | |||
| import { computed, toValue } from "vue"; | ||||
| import type { MaybeRefOrGetter } from "vue"; | ||||
| import { useQuery, useMutation, useQueryClient } from "@tanstack/vue-query"; | ||||
| import {TeacherController} from "@/controllers/teachers.ts"; | ||||
| 
 | ||||
| const teacherController = new TeacherController(); | ||||
| 
 | ||||
| /** 🔑 Query keys */ | ||||
| const TEACHERS_QUERY_KEY = (full: boolean) => ["teachers", full]; | ||||
| const TEACHER_QUERY_KEY = (username: string) => ["teacher", username]; | ||||
| const TEACHER_CLASSES_QUERY_KEY = (username: string, full: boolean) => ["teacher-classes", username, full]; | ||||
| const TEACHER_STUDENTS_QUERY_KEY = (username: string, full: boolean) => ["teacher-students", username, full]; | ||||
| const TEACHER_QUESTIONS_QUERY_KEY = (username: string, full: boolean) => ["teacher-questions", username, full]; | ||||
| const JOIN_REQUESTS_QUERY_KEY = (username: string, classId: string) => ["join-requests", username, classId]; | ||||
| 
 | ||||
| export function useTeachersQuery(full: MaybeRefOrGetter<boolean> = false) { | ||||
|     return useQuery({ | ||||
|         queryKey: computed(() => TEACHERS_QUERY_KEY(toValue(full))), | ||||
|         queryFn: () => teacherController.getAll(toValue(full)), | ||||
|     }); | ||||
| } | ||||
| 
 | ||||
| export function useTeacherQuery(username: MaybeRefOrGetter<string | undefined>) { | ||||
|     return useQuery({ | ||||
|         queryKey: computed(() => TEACHER_QUERY_KEY(toValue(username)!)), | ||||
|         queryFn: () => teacherController.getByUsername(toValue(username)!), | ||||
|         enabled: () => !!toValue(username), | ||||
|     }); | ||||
| } | ||||
| 
 | ||||
| export function useTeacherClassesQuery(username: MaybeRefOrGetter<string | undefined>, full: MaybeRefOrGetter<boolean> = false) { | ||||
|     return useQuery({ | ||||
|         queryKey: computed(() => TEACHER_CLASSES_QUERY_KEY(toValue(username)!, toValue(full))), | ||||
|         queryFn: () => teacherController.getClasses(toValue(username)!, toValue(full)), | ||||
|         enabled: () => !!toValue(username), | ||||
|     }); | ||||
| } | ||||
| 
 | ||||
| export function useTeacherStudentsQuery(username: MaybeRefOrGetter<string | undefined>, full: MaybeRefOrGetter<boolean> = false) { | ||||
|     return useQuery({ | ||||
|         queryKey: computed(() => TEACHER_STUDENTS_QUERY_KEY(toValue(username)!, toValue(full))), | ||||
|         queryFn: () => teacherController.getStudents(toValue(username)!, toValue(full)), | ||||
|         enabled: () => !!toValue(username), | ||||
|     }); | ||||
| } | ||||
| 
 | ||||
| export function useTeacherQuestionsQuery(username: MaybeRefOrGetter<string | undefined>, full: MaybeRefOrGetter<boolean> = false) { | ||||
|     return useQuery({ | ||||
|         queryKey: computed(() => TEACHER_QUESTIONS_QUERY_KEY(toValue(username)!, toValue(full))), | ||||
|         queryFn: () => teacherController.getQuestions(toValue(username)!, toValue(full)), | ||||
|         enabled: () => !!toValue(username), | ||||
|     }); | ||||
| } | ||||
| 
 | ||||
| export function useTeacherJoinRequestsQuery(username: MaybeRefOrGetter<string | undefined>, classId: MaybeRefOrGetter<string | undefined>) { | ||||
|     return useQuery({ | ||||
|         queryKey: computed(() => JOIN_REQUESTS_QUERY_KEY(toValue(username)!, toValue(classId)!)), | ||||
|         queryFn: () => teacherController.getStudentJoinRequests(toValue(username)!, toValue(classId)!), | ||||
|         enabled: () => !!toValue(username) && !!toValue(classId), | ||||
|     }); | ||||
| } | ||||
| 
 | ||||
| export function useCreateTeacherMutation() { | ||||
|     const queryClient = useQueryClient(); | ||||
| 
 | ||||
|     return useMutation({ | ||||
|         mutationFn: (data: any) => teacherController.createTeacher(data), | ||||
|         onSuccess: () => { | ||||
|             await queryClient.invalidateQueries({ queryKey: ['teachers'] }); | ||||
|         }, | ||||
|         onError: (err) => { | ||||
|             alert("Create teacher failed:", err); | ||||
|         }, | ||||
|     }); | ||||
| } | ||||
| 
 | ||||
| export function useDeleteTeacherMutation() { | ||||
|     const queryClient = useQueryClient(); | ||||
| 
 | ||||
|     return useMutation({ | ||||
|         mutationFn: (username: string) => teacherController.deleteTeacher(username), | ||||
|         onSuccess: () => { | ||||
|             await queryClient.invalidateQueries({ queryKey: ['teachers'] }); | ||||
|         }, | ||||
|         onError: (err) => { | ||||
|             alert("Delete teacher failed:", err); | ||||
|         }, | ||||
|     }); | ||||
| } | ||||
| 
 | ||||
| export function useUpdateJoinRequestMutation() { | ||||
|     const queryClient = useQueryClient(); | ||||
| 
 | ||||
|     return useMutation({ | ||||
|         mutationFn: ({ teacherUsername, classId, studentUsername, accepted }: { | ||||
|             teacherUsername: string; | ||||
|             classId: string; | ||||
|             studentUsername: string; | ||||
|             accepted: boolean; | ||||
|         }) => teacherController.updateStudentJoinRequest(teacherUsername, classId, studentUsername, accepted), | ||||
|         onSuccess: (_, { teacherUsername, classId }) => { | ||||
|             queryClient.invalidateQueries({ queryKey: JOIN_REQUESTS_QUERY_KEY(teacherUsername, classId) }); | ||||
|         }, | ||||
|         onError: (err) => { | ||||
|             alert("Failed to update join request:", err); | ||||
|         }, | ||||
|     }); | ||||
| } | ||||
|  | @ -1,8 +1,8 @@ | |||
| import { useQuery } from "@tanstack/vue-query"; | ||||
| import { getThemeController } from "@/controllers/controllers"; | ||||
| import { type MaybeRefOrGetter, toValue } from "vue"; | ||||
| import {ThemeController} from "@/controllers/themes.ts"; | ||||
| 
 | ||||
| const themeController = getThemeController(); | ||||
| const themeController = new ThemeController(); | ||||
| 
 | ||||
| export const useThemeQuery = (language: MaybeRefOrGetter<string>) => | ||||
|     useQuery({ | ||||
|  |  | |||
|  | @ -1,26 +1,22 @@ | |||
| import { describe, it, expect, beforeAll } from 'vitest'; | ||||
| import {getStudentController} from "../../src/controllers/controllers"; | ||||
| import {StudentController} from "../../src/controllers/students"; | ||||
| 
 | ||||
| const controller = getStudentController(); | ||||
| const controller = new StudentController(); | ||||
| 
 | ||||
| describe('StudentController', () => { | ||||
|     const newStudent = { | ||||
|         username: 'TestStudent', | ||||
|         username: 'teststudent1', | ||||
|         firstName: 'Testy', | ||||
|         lastName: 'McTestface', | ||||
|     }; | ||||
| 
 | ||||
|     beforeAll(() => { | ||||
|         // Zet eventueel mock server op hier als je dat gebruikt
 | ||||
|         // Start backend
 | ||||
|     }); | ||||
| 
 | ||||
|     it('creates a student and fetches it by username', async () => { | ||||
|         // Create student
 | ||||
|         const created = await controller.createStudent(newStudent); | ||||
| 
 | ||||
|         expect(created).toBeDefined(); | ||||
|         expect(created.username).toBe(newStudent.username); | ||||
| 
 | ||||
|         await controller.createStudent(newStudent); | ||||
| 
 | ||||
|         // Fetch same student
 | ||||
|         const fetched = await controller.getByUsername(newStudent.username); | ||||
|  | @ -33,9 +29,7 @@ describe('StudentController', () => { | |||
|         expect(student.firstName).toBe(newStudent.firstName); | ||||
|         expect(student.lastName).toBe(newStudent.lastName); | ||||
| 
 | ||||
| 
 | ||||
|         await controller.deleteStudent(newStudent.username); | ||||
| 
 | ||||
|         await expect(controller.getByUsername(newStudent.username)).rejects.toThrow(); | ||||
| 
 | ||||
|     }); | ||||
| }); | ||||
|  |  | |||
							
								
								
									
										35
									
								
								frontend/tests/controllers/teacher.test.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								frontend/tests/controllers/teacher.test.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,35 @@ | |||
| import { describe, it, expect, beforeAll } from 'vitest'; | ||||
| import {TeacherController} from "../../src/controllers/teachers"; | ||||
| 
 | ||||
| const controller = new TeacherController(); | ||||
| 
 | ||||
| describe('TeacherController', () => { | ||||
|     const newTeacher = { | ||||
|         username: 'testteacher', | ||||
|         firstName: 'Testy', | ||||
|         lastName: 'McTestface', | ||||
|     }; | ||||
| 
 | ||||
|     beforeAll(() => { | ||||
|         // Start backend
 | ||||
|     }); | ||||
| 
 | ||||
|     it('creates a student and fetches it by username', async () => { | ||||
|         // Create student
 | ||||
|         await controller.createTeacher(newTeacher); | ||||
| 
 | ||||
|         // Fetch same student
 | ||||
|         const fetched = await controller.getByUsername(newTeacher.username); | ||||
| 
 | ||||
|         expect(fetched).toBeDefined(); | ||||
|         expect(fetched.teacher).toBeDefined(); | ||||
| 
 | ||||
|         const teacher = fetched.teacher; | ||||
|         expect(teacher.username).toBe(newTeacher.username); | ||||
|         expect(teacher.firstName).toBe(newTeacher.firstName); | ||||
|         expect(teacher.lastName).toBe(newTeacher.lastName); | ||||
| 
 | ||||
| 
 | ||||
|         await controller.deleteTeacher(newTeacher.username); | ||||
|     }); | ||||
| }); | ||||
		Reference in a new issue
	
	 Gabriellvl
						Gabriellvl