Merge pull request #191 from SELab-2/feat/queries-adriaan
feat: frontend tan-stack queries (deel Adriaan)
This commit is contained in:
		
						commit
						72c1b91644
					
				
					 6 changed files with 766 additions and 6 deletions
				
			
		|  | @ -36,11 +36,11 @@ export class GroupController extends BaseController { | ||||||
|         return this.put<GroupResponse>(`/${num}`, data); |         return this.put<GroupResponse>(`/${num}`, data); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     async getSubmissions(groupNumber: number, full = true): Promise<SubmissionsResponse> { |     async getSubmissions(num: number, full = true): Promise<SubmissionsResponse> { | ||||||
|         return this.get<SubmissionsResponse>(`/${groupNumber}/submissions`, { full }); |         return this.get<SubmissionsResponse>(`/${num}/submissions`, { full }); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     async getQuestions(groupNumber: number, full = true): Promise<QuestionsResponse> { |     async getQuestions(num: number, full = true): Promise<QuestionsResponse> { | ||||||
|         return this.get<QuestionsResponse>(`/${groupNumber}/questions`, { full }); |         return this.get<QuestionsResponse>(`/${num}/questions`, { full }); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -11,7 +11,7 @@ export interface SubmissionResponse { | ||||||
| 
 | 
 | ||||||
| export class SubmissionController extends BaseController { | export class SubmissionController extends BaseController { | ||||||
|     constructor(classid: string, assignmentNumber: number, groupNumber: number) { |     constructor(classid: string, assignmentNumber: number, groupNumber: number) { | ||||||
|         super(`class/${classid}/assignments/${assignmentNumber}/groups/${groupNumber}`); |         super(`class/${classid}/assignments/${assignmentNumber}/groups/${groupNumber}/submissions`); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     async getAll(full = true): Promise<SubmissionsResponse> { |     async getAll(full = true): Promise<SubmissionsResponse> { | ||||||
|  | @ -22,7 +22,7 @@ export class SubmissionController extends BaseController { | ||||||
|         return this.get<SubmissionResponse>(`/${submissionNumber}`); |         return this.get<SubmissionResponse>(`/${submissionNumber}`); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     async createSubmission(data: unknown): Promise<SubmissionResponse> { |     async createSubmission(data: SubmissionDTO): Promise<SubmissionResponse> { | ||||||
|         return this.post<SubmissionResponse>(`/`, data); |         return this.post<SubmissionResponse>(`/`, data); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
							
								
								
									
										188
									
								
								frontend/src/queries/assignments.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										188
									
								
								frontend/src/queries/assignments.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,188 @@ | ||||||
|  | import { AssignmentController, type AssignmentResponse, type AssignmentsResponse } from "@/controllers/assignments"; | ||||||
|  | import type { QuestionsResponse } from "@/controllers/questions"; | ||||||
|  | import type { SubmissionsResponse } from "@/controllers/submissions"; | ||||||
|  | import { | ||||||
|  |     useMutation, | ||||||
|  |     useQuery, | ||||||
|  |     useQueryClient, | ||||||
|  |     type UseMutationReturnType, | ||||||
|  |     type UseQueryReturnType, | ||||||
|  | } from "@tanstack/vue-query"; | ||||||
|  | import { computed, toValue, type MaybeRefOrGetter } from "vue"; | ||||||
|  | import { groupsQueryKey, invalidateAllGroupKeys } from "./groups"; | ||||||
|  | import type { GroupsResponse } from "@/controllers/groups"; | ||||||
|  | import type { AssignmentDTO } from "@dwengo-1/common/interfaces/assignment"; | ||||||
|  | import type { QueryClient } from "@tanstack/react-query"; | ||||||
|  | import { invalidateAllSubmissionKeys } from "./submissions"; | ||||||
|  | 
 | ||||||
|  | function assignmentsQueryKey(classid: string, full: boolean) { | ||||||
|  |     return ["assignments", classid, full]; | ||||||
|  | } | ||||||
|  | function assignmentQueryKey(classid: string, assignmentNumber: number) { | ||||||
|  |     return ["assignment", classid, assignmentNumber]; | ||||||
|  | } | ||||||
|  | function assignmentSubmissionsQueryKey(classid: string, assignmentNumber: number, full: boolean) { | ||||||
|  |     return ["assignment-submissions", classid, assignmentNumber, full]; | ||||||
|  | } | ||||||
|  | function assignmentQuestionsQueryKey(classid: string, assignmentNumber: number, full: boolean) { | ||||||
|  |     return ["assignment-questions", classid, assignmentNumber, full]; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export async function invalidateAllAssignmentKeys( | ||||||
|  |     queryClient: QueryClient, | ||||||
|  |     classid?: string, | ||||||
|  |     assignmentNumber?: number, | ||||||
|  | ) { | ||||||
|  |     const keys = ["assignment", "assignment-submissions", "assignment-questions"]; | ||||||
|  | 
 | ||||||
|  |     for (const key of keys) { | ||||||
|  |         const queryKey = [key, classid, assignmentNumber].filter((arg) => arg !== undefined); | ||||||
|  |         await queryClient.invalidateQueries({ queryKey: queryKey }); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     await queryClient.invalidateQueries({ queryKey: ["assignments", classid].filter((arg) => arg !== undefined) }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function checkEnabled( | ||||||
|  |     classid: string | undefined, | ||||||
|  |     assignmentNumber: number | undefined, | ||||||
|  |     groupNumber: number | undefined, | ||||||
|  | ): boolean { | ||||||
|  |     return Boolean(classid) && !isNaN(Number(groupNumber)) && !isNaN(Number(assignmentNumber)); | ||||||
|  | } | ||||||
|  | function toValues( | ||||||
|  |     classid: MaybeRefOrGetter<string | undefined>, | ||||||
|  |     assignmentNumber: MaybeRefOrGetter<number | undefined>, | ||||||
|  |     groupNumber: MaybeRefOrGetter<number | undefined>, | ||||||
|  |     full: MaybeRefOrGetter<boolean>, | ||||||
|  | ) { | ||||||
|  |     return { cid: toValue(classid), an: toValue(assignmentNumber), gn: toValue(groupNumber), f: toValue(full) }; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export function useAssignmentsQuery( | ||||||
|  |     classid: MaybeRefOrGetter<string | undefined>, | ||||||
|  |     full: MaybeRefOrGetter<boolean> = true, | ||||||
|  | ): UseQueryReturnType<AssignmentsResponse, Error> { | ||||||
|  |     const { cid, f } = toValues(classid, 1, 1, full); | ||||||
|  | 
 | ||||||
|  |     return useQuery({ | ||||||
|  |         queryKey: computed(() => assignmentsQueryKey(cid!, f)), | ||||||
|  |         queryFn: async () => new AssignmentController(cid!).getAll(f), | ||||||
|  |         enabled: () => checkEnabled(cid, 1, 1), | ||||||
|  |     }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export function useAssignmentQuery( | ||||||
|  |     classid: MaybeRefOrGetter<string | undefined>, | ||||||
|  |     assignmentNumber: MaybeRefOrGetter<number | undefined>, | ||||||
|  | ): UseQueryReturnType<AssignmentsResponse, Error> { | ||||||
|  |     const { cid, an } = toValues(classid, assignmentNumber, 1, true); | ||||||
|  | 
 | ||||||
|  |     return useQuery({ | ||||||
|  |         queryKey: computed(() => assignmentQueryKey(cid!, an!)), | ||||||
|  |         queryFn: async () => new AssignmentController(cid!).getByNumber(an!), | ||||||
|  |         enabled: () => checkEnabled(cid, an, 1), | ||||||
|  |     }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export function useCreateAssignmentMutation(): UseMutationReturnType< | ||||||
|  |     AssignmentResponse, | ||||||
|  |     Error, | ||||||
|  |     { cid: string; data: AssignmentDTO }, | ||||||
|  |     unknown | ||||||
|  | > { | ||||||
|  |     const queryClient = useQueryClient(); | ||||||
|  | 
 | ||||||
|  |     return useMutation({ | ||||||
|  |         mutationFn: async ({ cid, data }) => new AssignmentController(cid).createAssignment(data), | ||||||
|  |         onSuccess: async (_) => { | ||||||
|  |             await queryClient.invalidateQueries({ queryKey: ["assignments"] }); | ||||||
|  |         }, | ||||||
|  |     }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export function useDeleteAssignmentMutation(): UseMutationReturnType< | ||||||
|  |     AssignmentResponse, | ||||||
|  |     Error, | ||||||
|  |     { cid: string; an: number }, | ||||||
|  |     unknown | ||||||
|  | > { | ||||||
|  |     const queryClient = useQueryClient(); | ||||||
|  | 
 | ||||||
|  |     return useMutation({ | ||||||
|  |         mutationFn: async ({ cid, an }) => new AssignmentController(cid).deleteAssignment(an), | ||||||
|  |         onSuccess: async (response) => { | ||||||
|  |             const cid = response.assignment.within; | ||||||
|  |             const an = response.assignment.id; | ||||||
|  | 
 | ||||||
|  |             await invalidateAllAssignmentKeys(queryClient, cid, an); | ||||||
|  |             await invalidateAllGroupKeys(queryClient, cid, an); | ||||||
|  |             await invalidateAllSubmissionKeys(queryClient, cid, an); | ||||||
|  |         }, | ||||||
|  |     }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export function useUpdateAssignmentMutation(): UseMutationReturnType< | ||||||
|  |     AssignmentResponse, | ||||||
|  |     Error, | ||||||
|  |     { cid: string; an: number; data: Partial<AssignmentDTO> }, | ||||||
|  |     unknown | ||||||
|  | > { | ||||||
|  |     const queryClient = useQueryClient(); | ||||||
|  | 
 | ||||||
|  |     return useMutation({ | ||||||
|  |         mutationFn: async ({ cid, an, data }) => new AssignmentController(cid).updateAssignment(an, data), | ||||||
|  |         onSuccess: async (response) => { | ||||||
|  |             const cid = response.assignment.within; | ||||||
|  |             const an = response.assignment.id; | ||||||
|  | 
 | ||||||
|  |             await invalidateAllGroupKeys(queryClient, cid, an); | ||||||
|  |             await queryClient.invalidateQueries({ queryKey: ["assignments"] }); | ||||||
|  |         }, | ||||||
|  |     }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export function useAssignmentSubmissionsQuery( | ||||||
|  |     classid: MaybeRefOrGetter<string | undefined>, | ||||||
|  |     assignmentNumber: MaybeRefOrGetter<number | undefined>, | ||||||
|  |     groupNumber: MaybeRefOrGetter<number | undefined>, | ||||||
|  |     full: MaybeRefOrGetter<boolean> = true, | ||||||
|  | ): UseQueryReturnType<SubmissionsResponse, Error> { | ||||||
|  |     const { cid, an, gn, f } = toValues(classid, assignmentNumber, groupNumber, full); | ||||||
|  | 
 | ||||||
|  |     return useQuery({ | ||||||
|  |         queryKey: computed(() => assignmentSubmissionsQueryKey(cid!, an!, f)), | ||||||
|  |         queryFn: async () => new AssignmentController(cid!).getSubmissions(gn!, f), | ||||||
|  |         enabled: () => checkEnabled(cid, an, gn), | ||||||
|  |     }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export function useAssignmentQuestionsQuery( | ||||||
|  |     classid: MaybeRefOrGetter<string | undefined>, | ||||||
|  |     assignmentNumber: MaybeRefOrGetter<number | undefined>, | ||||||
|  |     groupNumber: MaybeRefOrGetter<number | undefined>, | ||||||
|  |     full: MaybeRefOrGetter<boolean> = true, | ||||||
|  | ): UseQueryReturnType<QuestionsResponse, Error> { | ||||||
|  |     const { cid, an, gn, f } = toValues(classid, assignmentNumber, groupNumber, full); | ||||||
|  | 
 | ||||||
|  |     return useQuery({ | ||||||
|  |         queryKey: computed(() => assignmentQuestionsQueryKey(cid!, an!, f)), | ||||||
|  |         queryFn: async () => new AssignmentController(cid!).getQuestions(gn!, f), | ||||||
|  |         enabled: () => checkEnabled(cid, an, gn), | ||||||
|  |     }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export function useAssignmentGroupsQuery( | ||||||
|  |     classid: MaybeRefOrGetter<string | undefined>, | ||||||
|  |     assignmentNumber: MaybeRefOrGetter<number | undefined>, | ||||||
|  |     groupNumber: MaybeRefOrGetter<number | undefined>, | ||||||
|  |     full: MaybeRefOrGetter<boolean> = true, | ||||||
|  | ): UseQueryReturnType<GroupsResponse, Error> { | ||||||
|  |     const { cid, an, gn, f } = toValues(classid, assignmentNumber, groupNumber, full); | ||||||
|  | 
 | ||||||
|  |     return useQuery({ | ||||||
|  |         queryKey: computed(() => groupsQueryKey(cid!, an!, f)), | ||||||
|  |         queryFn: async () => new AssignmentController(cid!).getQuestions(gn!, f), | ||||||
|  |         enabled: () => checkEnabled(cid, an, gn), | ||||||
|  |     }); | ||||||
|  | } | ||||||
							
								
								
									
										224
									
								
								frontend/src/queries/classes.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										224
									
								
								frontend/src/queries/classes.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,224 @@ | ||||||
|  | import { ClassController, type ClassesResponse, type ClassResponse } from "@/controllers/classes"; | ||||||
|  | import type { StudentsResponse } from "@/controllers/students"; | ||||||
|  | import type { ClassDTO } from "@dwengo-1/common/interfaces/class"; | ||||||
|  | import { | ||||||
|  |     QueryClient, | ||||||
|  |     useMutation, | ||||||
|  |     useQuery, | ||||||
|  |     useQueryClient, | ||||||
|  |     type UseMutationReturnType, | ||||||
|  |     type UseQueryReturnType, | ||||||
|  | } from "@tanstack/vue-query"; | ||||||
|  | import { computed, toValue, type MaybeRefOrGetter } from "vue"; | ||||||
|  | import { invalidateAllAssignmentKeys } from "./assignments"; | ||||||
|  | import { invalidateAllGroupKeys } from "./groups"; | ||||||
|  | import { invalidateAllSubmissionKeys } from "./submissions"; | ||||||
|  | 
 | ||||||
|  | const classController = new ClassController(); | ||||||
|  | 
 | ||||||
|  | /* Query cache keys */ | ||||||
|  | function classesQueryKey(full: boolean) { | ||||||
|  |     return ["classes", full]; | ||||||
|  | } | ||||||
|  | function classQueryKey(classid: string) { | ||||||
|  |     return ["class", classid]; | ||||||
|  | } | ||||||
|  | function classStudentsKey(classid: string, full: boolean) { | ||||||
|  |     return ["class-students", classid, full]; | ||||||
|  | } | ||||||
|  | function classTeachersKey(classid: string, full: boolean) { | ||||||
|  |     return ["class-teachers", classid, full]; | ||||||
|  | } | ||||||
|  | function classTeacherInvitationsKey(classid: string, full: boolean) { | ||||||
|  |     return ["class-teacher-invitations", classid, full]; | ||||||
|  | } | ||||||
|  | function classAssignmentsKey(classid: string, full: boolean) { | ||||||
|  |     return ["class-assignments", classid, full]; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export async function invalidateAllClassKeys(queryClient: QueryClient, classid?: string) { | ||||||
|  |     const keys = ["class", "class-students", "class-teachers", "class-teacher-invitations", "class-assignments"]; | ||||||
|  | 
 | ||||||
|  |     for (const key of keys) { | ||||||
|  |         const queryKey = [key, classid].filter((arg) => arg !== undefined); | ||||||
|  |         await queryClient.invalidateQueries({ queryKey: queryKey }); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     await queryClient.invalidateQueries({ queryKey: ["classes"] }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /* Queries */ | ||||||
|  | export function useClassesQuery(full: MaybeRefOrGetter<boolean> = true): UseQueryReturnType<ClassesResponse, Error> { | ||||||
|  |     return useQuery({ | ||||||
|  |         queryKey: computed(() => classesQueryKey(toValue(full))), | ||||||
|  |         queryFn: async () => classController.getAll(toValue(full)), | ||||||
|  |     }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export function useClassQuery(id: MaybeRefOrGetter<string | undefined>): UseQueryReturnType<ClassResponse, Error> { | ||||||
|  |     return useQuery({ | ||||||
|  |         queryKey: computed(() => classQueryKey(toValue(id)!)), | ||||||
|  |         queryFn: async () => classController.getById(toValue(id)!), | ||||||
|  |         enabled: () => Boolean(toValue(id)), | ||||||
|  |     }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export function useCreateClassMutation(): UseMutationReturnType<ClassResponse, Error, ClassDTO, unknown> { | ||||||
|  |     const queryClient = useQueryClient(); | ||||||
|  | 
 | ||||||
|  |     return useMutation({ | ||||||
|  |         mutationFn: async (data) => classController.createClass(data), | ||||||
|  |         onSuccess: async () => { | ||||||
|  |             await queryClient.invalidateQueries({ queryKey: ["classes"] }); | ||||||
|  |         }, | ||||||
|  |     }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export function useDeleteClassMutation(): UseMutationReturnType<ClassResponse, Error, string, unknown> { | ||||||
|  |     const queryClient = useQueryClient(); | ||||||
|  | 
 | ||||||
|  |     return useMutation({ | ||||||
|  |         mutationFn: async (id) => classController.deleteClass(id), | ||||||
|  |         onSuccess: async (data) => { | ||||||
|  |             await invalidateAllClassKeys(queryClient, data.class.id); | ||||||
|  |             await invalidateAllAssignmentKeys(queryClient, data.class.id); | ||||||
|  |             await invalidateAllGroupKeys(queryClient, data.class.id); | ||||||
|  |             await invalidateAllSubmissionKeys(queryClient, data.class.id); | ||||||
|  |         }, | ||||||
|  |     }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export function useUpdateClassMutation(): UseMutationReturnType< | ||||||
|  |     ClassResponse, | ||||||
|  |     Error, | ||||||
|  |     { cid: string; data: Partial<ClassDTO> }, | ||||||
|  |     unknown | ||||||
|  | > { | ||||||
|  |     const queryClient = useQueryClient(); | ||||||
|  | 
 | ||||||
|  |     return useMutation({ | ||||||
|  |         mutationFn: async ({ cid, data }) => classController.updateClass(cid, data), | ||||||
|  |         onSuccess: async (data) => { | ||||||
|  |             await invalidateAllClassKeys(queryClient, data.class.id); | ||||||
|  |             await invalidateAllAssignmentKeys(queryClient, data.class.id); | ||||||
|  |             await invalidateAllGroupKeys(queryClient, data.class.id); | ||||||
|  |             await invalidateAllSubmissionKeys(queryClient, data.class.id); | ||||||
|  |         }, | ||||||
|  |     }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export function useClassStudentsQuery( | ||||||
|  |     id: MaybeRefOrGetter<string | undefined>, | ||||||
|  |     full: MaybeRefOrGetter<boolean> = true, | ||||||
|  | ): UseQueryReturnType<StudentsResponse, Error> { | ||||||
|  |     return useQuery({ | ||||||
|  |         queryKey: computed(() => classStudentsKey(toValue(id)!, toValue(full))), | ||||||
|  |         queryFn: async () => classController.getStudents(toValue(id)!, toValue(full)), | ||||||
|  |         enabled: () => Boolean(toValue(id)), | ||||||
|  |     }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export function useClassAddStudentMutation(): UseMutationReturnType< | ||||||
|  |     ClassResponse, | ||||||
|  |     Error, | ||||||
|  |     { id: string; username: string }, | ||||||
|  |     unknown | ||||||
|  | > { | ||||||
|  |     const queryClient = useQueryClient(); | ||||||
|  | 
 | ||||||
|  |     return useMutation({ | ||||||
|  |         mutationFn: async ({ id, username }) => classController.addStudent(id, username), | ||||||
|  |         onSuccess: async (data) => { | ||||||
|  |             await queryClient.invalidateQueries({ queryKey: classQueryKey(data.class.id) }); | ||||||
|  |             await queryClient.invalidateQueries({ queryKey: classStudentsKey(data.class.id, true) }); | ||||||
|  |             await queryClient.invalidateQueries({ queryKey: classStudentsKey(data.class.id, false) }); | ||||||
|  |         }, | ||||||
|  |     }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export function useClassDeleteStudentMutation(): UseMutationReturnType< | ||||||
|  |     ClassResponse, | ||||||
|  |     Error, | ||||||
|  |     { id: string; username: string }, | ||||||
|  |     unknown | ||||||
|  | > { | ||||||
|  |     const queryClient = useQueryClient(); | ||||||
|  | 
 | ||||||
|  |     return useMutation({ | ||||||
|  |         mutationFn: async ({ id, username }) => classController.deleteStudent(id, username), | ||||||
|  |         onSuccess: async (data) => { | ||||||
|  |             await queryClient.invalidateQueries({ queryKey: classQueryKey(data.class.id) }); | ||||||
|  |             await queryClient.invalidateQueries({ queryKey: classStudentsKey(data.class.id, true) }); | ||||||
|  |             await queryClient.invalidateQueries({ queryKey: classStudentsKey(data.class.id, false) }); | ||||||
|  |         }, | ||||||
|  |     }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export function useClassTeachersQuery( | ||||||
|  |     id: MaybeRefOrGetter<string | undefined>, | ||||||
|  |     full: MaybeRefOrGetter<boolean> = true, | ||||||
|  | ): UseQueryReturnType<StudentsResponse, Error> { | ||||||
|  |     return useQuery({ | ||||||
|  |         queryKey: computed(() => classTeachersKey(toValue(id)!, toValue(full))), | ||||||
|  |         queryFn: async () => classController.getTeachers(toValue(id)!, toValue(full)), | ||||||
|  |         enabled: () => Boolean(toValue(id)), | ||||||
|  |     }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export function useClassAddTeacherMutation(): UseMutationReturnType< | ||||||
|  |     ClassResponse, | ||||||
|  |     Error, | ||||||
|  |     { id: string; username: string }, | ||||||
|  |     unknown | ||||||
|  | > { | ||||||
|  |     const queryClient = useQueryClient(); | ||||||
|  | 
 | ||||||
|  |     return useMutation({ | ||||||
|  |         mutationFn: async ({ id, username }) => classController.addTeacher(id, username), | ||||||
|  |         onSuccess: async (data) => { | ||||||
|  |             await queryClient.invalidateQueries({ queryKey: classQueryKey(data.class.id) }); | ||||||
|  |             await queryClient.invalidateQueries({ queryKey: classTeachersKey(data.class.id, true) }); | ||||||
|  |             await queryClient.invalidateQueries({ queryKey: classTeachersKey(data.class.id, false) }); | ||||||
|  |         }, | ||||||
|  |     }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export function useClassDeleteTeacherMutation(): UseMutationReturnType< | ||||||
|  |     ClassResponse, | ||||||
|  |     Error, | ||||||
|  |     { id: string; username: string }, | ||||||
|  |     unknown | ||||||
|  | > { | ||||||
|  |     const queryClient = useQueryClient(); | ||||||
|  | 
 | ||||||
|  |     return useMutation({ | ||||||
|  |         mutationFn: async ({ id, username }) => classController.deleteTeacher(id, username), | ||||||
|  |         onSuccess: async (data) => { | ||||||
|  |             await queryClient.invalidateQueries({ queryKey: classQueryKey(data.class.id) }); | ||||||
|  |             await queryClient.invalidateQueries({ queryKey: classTeachersKey(data.class.id, true) }); | ||||||
|  |             await queryClient.invalidateQueries({ queryKey: classTeachersKey(data.class.id, false) }); | ||||||
|  |         }, | ||||||
|  |     }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export function useClassTeacherInvitationsQuery( | ||||||
|  |     id: MaybeRefOrGetter<string | undefined>, | ||||||
|  |     full: MaybeRefOrGetter<boolean> = true, | ||||||
|  | ): UseQueryReturnType<StudentsResponse, Error> { | ||||||
|  |     return useQuery({ | ||||||
|  |         queryKey: computed(() => classTeacherInvitationsKey(toValue(id)!, toValue(full))), | ||||||
|  |         queryFn: async () => classController.getTeacherInvitations(toValue(id)!, toValue(full)), | ||||||
|  |         enabled: () => Boolean(toValue(id)), | ||||||
|  |     }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export function useClassAssignmentsQuery( | ||||||
|  |     id: MaybeRefOrGetter<string | undefined>, | ||||||
|  |     full: MaybeRefOrGetter<boolean> = true, | ||||||
|  | ): UseQueryReturnType<StudentsResponse, Error> { | ||||||
|  |     return useQuery({ | ||||||
|  |         queryKey: computed(() => classAssignmentsKey(toValue(id)!, toValue(full))), | ||||||
|  |         queryFn: async () => classController.getAssignments(toValue(id)!, toValue(full)), | ||||||
|  |         enabled: () => Boolean(toValue(id)), | ||||||
|  |     }); | ||||||
|  | } | ||||||
							
								
								
									
										191
									
								
								frontend/src/queries/groups.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										191
									
								
								frontend/src/queries/groups.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,191 @@ | ||||||
|  | import type { ClassesResponse } from "@/controllers/classes"; | ||||||
|  | import { GroupController, type GroupResponse, type GroupsResponse } from "@/controllers/groups"; | ||||||
|  | import type { QuestionsResponse } from "@/controllers/questions"; | ||||||
|  | import type { SubmissionsResponse } from "@/controllers/submissions"; | ||||||
|  | import type { GroupDTO } from "@dwengo-1/common/interfaces/group"; | ||||||
|  | import { | ||||||
|  |     QueryClient, | ||||||
|  |     useMutation, | ||||||
|  |     useQuery, | ||||||
|  |     useQueryClient, | ||||||
|  |     type UseMutationReturnType, | ||||||
|  |     type UseQueryReturnType, | ||||||
|  | } from "@tanstack/vue-query"; | ||||||
|  | import { computed, toValue, type MaybeRefOrGetter } from "vue"; | ||||||
|  | import { invalidateAllAssignmentKeys } from "./assignments"; | ||||||
|  | import { invalidateAllSubmissionKeys } from "./submissions"; | ||||||
|  | 
 | ||||||
|  | export function groupsQueryKey(classid: string, assignmentNumber: number, full: boolean) { | ||||||
|  |     return ["groups", classid, assignmentNumber, full]; | ||||||
|  | } | ||||||
|  | function groupQueryKey(classid: string, assignmentNumber: number, groupNumber: number) { | ||||||
|  |     return ["group", classid, assignmentNumber, groupNumber]; | ||||||
|  | } | ||||||
|  | function groupSubmissionsQueryKey(classid: string, assignmentNumber: number, groupNumber: number, full: boolean) { | ||||||
|  |     return ["group-submissions", classid, assignmentNumber, groupNumber, full]; | ||||||
|  | } | ||||||
|  | function groupQuestionsQueryKey(classid: string, assignmentNumber: number, groupNumber: number, full: boolean) { | ||||||
|  |     return ["group-questions", classid, assignmentNumber, groupNumber, full]; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export async function invalidateAllGroupKeys( | ||||||
|  |     queryClient: QueryClient, | ||||||
|  |     classid?: string, | ||||||
|  |     assignmentNumber?: number, | ||||||
|  |     groupNumber?: number, | ||||||
|  | ) { | ||||||
|  |     const keys = ["group", "group-submissions", "group-questions"]; | ||||||
|  | 
 | ||||||
|  |     for (const key of keys) { | ||||||
|  |         const queryKey = [key, classid, assignmentNumber, groupNumber].filter((arg) => arg !== undefined); | ||||||
|  |         await queryClient.invalidateQueries({ queryKey: queryKey }); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     await queryClient.invalidateQueries({ | ||||||
|  |         queryKey: ["groups", classid, assignmentNumber].filter((arg) => arg !== undefined), | ||||||
|  |     }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function checkEnabled( | ||||||
|  |     classid: string | undefined, | ||||||
|  |     assignmentNumber: number | undefined, | ||||||
|  |     groupNumber: number | undefined, | ||||||
|  | ): boolean { | ||||||
|  |     return Boolean(classid) && !isNaN(Number(groupNumber)) && !isNaN(Number(assignmentNumber)); | ||||||
|  | } | ||||||
|  | function toValues( | ||||||
|  |     classid: MaybeRefOrGetter<string | undefined>, | ||||||
|  |     assignmentNumber: MaybeRefOrGetter<number | undefined>, | ||||||
|  |     groupNumber: MaybeRefOrGetter<number | undefined>, | ||||||
|  |     full: MaybeRefOrGetter<boolean>, | ||||||
|  | ) { | ||||||
|  |     return { cid: toValue(classid), an: toValue(assignmentNumber), gn: toValue(groupNumber), f: toValue(full) }; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export function useGroupsQuery( | ||||||
|  |     classid: MaybeRefOrGetter<string | undefined>, | ||||||
|  |     assignmentNumber: MaybeRefOrGetter<number | undefined>, | ||||||
|  |     full: MaybeRefOrGetter<boolean> = true, | ||||||
|  | ): UseQueryReturnType<GroupsResponse, Error> { | ||||||
|  |     const { cid, an, f } = toValues(classid, assignmentNumber, 1, full); | ||||||
|  | 
 | ||||||
|  |     return useQuery({ | ||||||
|  |         queryKey: computed(() => groupsQueryKey(cid!, an!, f)), | ||||||
|  |         queryFn: async () => new GroupController(cid!, an!).getAll(f), | ||||||
|  |         enabled: () => checkEnabled(cid, an, 1), | ||||||
|  |     }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export function useGroupQuery( | ||||||
|  |     classid: MaybeRefOrGetter<string | undefined>, | ||||||
|  |     assignmentNumber: MaybeRefOrGetter<number | undefined>, | ||||||
|  |     groupNumber: MaybeRefOrGetter<number | undefined>, | ||||||
|  | ): UseQueryReturnType<GroupResponse, Error> { | ||||||
|  |     const { cid, an, gn } = toValues(classid, assignmentNumber, groupNumber, true); | ||||||
|  | 
 | ||||||
|  |     return useQuery({ | ||||||
|  |         queryKey: computed(() => groupQueryKey(cid!, an!, gn!)), | ||||||
|  |         queryFn: async () => new GroupController(cid!, an!).getByNumber(gn!), | ||||||
|  |         enabled: () => checkEnabled(cid, an, gn), | ||||||
|  |     }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export function useCreateGroupMutation(): UseMutationReturnType< | ||||||
|  |     GroupResponse, | ||||||
|  |     Error, | ||||||
|  |     { cid: string; an: number; data: GroupDTO }, | ||||||
|  |     unknown | ||||||
|  | > { | ||||||
|  |     const queryClient = useQueryClient(); | ||||||
|  | 
 | ||||||
|  |     return useMutation({ | ||||||
|  |         mutationFn: async ({ cid, an, data }) => new GroupController(cid, an).createGroup(data), | ||||||
|  |         onSuccess: async (response) => { | ||||||
|  |             const cid = typeof response.group.class === "string" ? response.group.class : response.group.class.id; | ||||||
|  |             const an = | ||||||
|  |                 typeof response.group.assignment === "number" | ||||||
|  |                     ? response.group.assignment | ||||||
|  |                     : response.group.assignment.id; | ||||||
|  | 
 | ||||||
|  |             await queryClient.invalidateQueries({ queryKey: groupsQueryKey(cid, an, true) }); | ||||||
|  |             await queryClient.invalidateQueries({ queryKey: groupsQueryKey(cid, an, false) }); | ||||||
|  |         }, | ||||||
|  |     }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export function useDeleteGroupMutation(): UseMutationReturnType< | ||||||
|  |     GroupResponse, | ||||||
|  |     Error, | ||||||
|  |     { cid: string; an: number; gn: number }, | ||||||
|  |     unknown | ||||||
|  | > { | ||||||
|  |     const queryClient = useQueryClient(); | ||||||
|  | 
 | ||||||
|  |     return useMutation({ | ||||||
|  |         mutationFn: async ({ cid, an, gn }) => new GroupController(cid, an).deleteGroup(gn), | ||||||
|  |         onSuccess: async (response) => { | ||||||
|  |             const cid = typeof response.group.class === "string" ? response.group.class : response.group.class.id; | ||||||
|  |             const an = | ||||||
|  |                 typeof response.group.assignment === "number" | ||||||
|  |                     ? response.group.assignment | ||||||
|  |                     : response.group.assignment.id; | ||||||
|  |             const gn = response.group.groupNumber; | ||||||
|  | 
 | ||||||
|  |             await invalidateAllGroupKeys(queryClient, cid, an, gn); | ||||||
|  |             await invalidateAllSubmissionKeys(queryClient, cid, an, gn); | ||||||
|  |         }, | ||||||
|  |     }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export function useUpdateGroupMutation(): UseMutationReturnType< | ||||||
|  |     GroupResponse, | ||||||
|  |     Error, | ||||||
|  |     { cid: string; an: number; gn: number; data: Partial<GroupDTO> }, | ||||||
|  |     unknown | ||||||
|  | > { | ||||||
|  |     const queryClient = useQueryClient(); | ||||||
|  | 
 | ||||||
|  |     return useMutation({ | ||||||
|  |         mutationFn: async ({ cid, an, gn, data }) => new GroupController(cid, an).updateGroup(gn, data), | ||||||
|  |         onSuccess: async (response) => { | ||||||
|  |             const cid = typeof response.group.class === "string" ? response.group.class : response.group.class.id; | ||||||
|  |             const an = | ||||||
|  |                 typeof response.group.assignment === "number" | ||||||
|  |                     ? response.group.assignment | ||||||
|  |                     : response.group.assignment.id; | ||||||
|  |             const gn = response.group.groupNumber; | ||||||
|  | 
 | ||||||
|  |             await invalidateAllGroupKeys(queryClient, cid, an, gn); | ||||||
|  |         }, | ||||||
|  |     }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export function useGroupSubmissionsQuery( | ||||||
|  |     classid: MaybeRefOrGetter<string | undefined>, | ||||||
|  |     assignmentNumber: MaybeRefOrGetter<number | undefined>, | ||||||
|  |     groupNumber: MaybeRefOrGetter<number | undefined>, | ||||||
|  |     full: MaybeRefOrGetter<boolean> = true, | ||||||
|  | ): UseQueryReturnType<SubmissionsResponse, Error> { | ||||||
|  |     const { cid, an, gn, f } = toValues(classid, assignmentNumber, groupNumber, full); | ||||||
|  | 
 | ||||||
|  |     return useQuery({ | ||||||
|  |         queryKey: computed(() => groupSubmissionsQueryKey(cid!, an!, gn!, f)), | ||||||
|  |         queryFn: async () => new GroupController(cid!, an!).getSubmissions(gn!, f), | ||||||
|  |         enabled: () => checkEnabled(cid, an, gn), | ||||||
|  |     }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export function useGroupQuestionsQuery( | ||||||
|  |     classid: MaybeRefOrGetter<string | undefined>, | ||||||
|  |     assignmentNumber: MaybeRefOrGetter<number | undefined>, | ||||||
|  |     groupNumber: MaybeRefOrGetter<number | undefined>, | ||||||
|  |     full: MaybeRefOrGetter<boolean> = true, | ||||||
|  | ): UseQueryReturnType<QuestionsResponse, Error> { | ||||||
|  |     const { cid, an, gn, f } = toValues(classid, assignmentNumber, groupNumber, full); | ||||||
|  | 
 | ||||||
|  |     return useQuery({ | ||||||
|  |         queryKey: computed(() => groupQuestionsQueryKey(cid!, an!, gn!, f)), | ||||||
|  |         queryFn: async () => new GroupController(cid!, an!).getSubmissions(gn!, f), | ||||||
|  |         enabled: () => checkEnabled(cid, an, gn), | ||||||
|  |     }); | ||||||
|  | } | ||||||
							
								
								
									
										157
									
								
								frontend/src/queries/submissions.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										157
									
								
								frontend/src/queries/submissions.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,157 @@ | ||||||
|  | import { SubmissionController, type SubmissionResponse, type SubmissionsResponse } from "@/controllers/submissions"; | ||||||
|  | import type { SubmissionDTO } from "@dwengo-1/common/interfaces/submission"; | ||||||
|  | import { | ||||||
|  |     QueryClient, | ||||||
|  |     useMutation, | ||||||
|  |     useQuery, | ||||||
|  |     useQueryClient, | ||||||
|  |     type UseMutationReturnType, | ||||||
|  |     type UseQueryReturnType, | ||||||
|  | } from "@tanstack/vue-query"; | ||||||
|  | import { computed, toValue, type MaybeRefOrGetter } from "vue"; | ||||||
|  | 
 | ||||||
|  | function submissionsQueryKey(classid: string, assignmentNumber: number, groupNumber: number, full: boolean) { | ||||||
|  |     return ["submissions", classid, assignmentNumber, groupNumber, full]; | ||||||
|  | } | ||||||
|  | function submissionQueryKey(classid: string, assignmentNumber: number, groupNumber: number, submissionNumber: number) { | ||||||
|  |     return ["submission", classid, assignmentNumber, groupNumber, submissionNumber]; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export async function invalidateAllSubmissionKeys( | ||||||
|  |     queryClient: QueryClient, | ||||||
|  |     classid?: string, | ||||||
|  |     assignmentNumber?: number, | ||||||
|  |     groupNumber?: number, | ||||||
|  |     submissionNumber?: number, | ||||||
|  | ) { | ||||||
|  |     const keys = ["submission"]; | ||||||
|  | 
 | ||||||
|  |     for (const key of keys) { | ||||||
|  |         const queryKey = [key, classid, assignmentNumber, groupNumber, submissionNumber].filter( | ||||||
|  |             (arg) => arg !== undefined, | ||||||
|  |         ); | ||||||
|  |         await queryClient.invalidateQueries({ queryKey: queryKey }); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     await queryClient.invalidateQueries({ | ||||||
|  |         queryKey: ["submissions", classid, assignmentNumber, groupNumber].filter((arg) => arg !== undefined), | ||||||
|  |     }); | ||||||
|  |     await queryClient.invalidateQueries({ | ||||||
|  |         queryKey: ["group-submissions", classid, assignmentNumber, groupNumber].filter((arg) => arg !== undefined), | ||||||
|  |     }); | ||||||
|  |     await queryClient.invalidateQueries({ | ||||||
|  |         queryKey: ["assignment-submissions", classid, assignmentNumber].filter((arg) => arg !== undefined), | ||||||
|  |     }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function checkEnabled( | ||||||
|  |     classid: string | undefined, | ||||||
|  |     assignmentNumber: number | undefined, | ||||||
|  |     groupNumber: number | undefined, | ||||||
|  |     submissionNumber: number | undefined, | ||||||
|  | ): boolean { | ||||||
|  |     return ( | ||||||
|  |         Boolean(classid) && | ||||||
|  |         !isNaN(Number(groupNumber)) && | ||||||
|  |         !isNaN(Number(assignmentNumber)) && | ||||||
|  |         !isNaN(Number(submissionNumber)) | ||||||
|  |     ); | ||||||
|  | } | ||||||
|  | function toValues( | ||||||
|  |     classid: MaybeRefOrGetter<string | undefined>, | ||||||
|  |     assignmentNumber: MaybeRefOrGetter<number | undefined>, | ||||||
|  |     groupNumber: MaybeRefOrGetter<number | undefined>, | ||||||
|  |     submissionNumber: MaybeRefOrGetter<number | undefined>, | ||||||
|  |     full: MaybeRefOrGetter<boolean>, | ||||||
|  | ) { | ||||||
|  |     return { | ||||||
|  |         cid: toValue(classid), | ||||||
|  |         an: toValue(assignmentNumber), | ||||||
|  |         gn: toValue(groupNumber), | ||||||
|  |         sn: toValue(submissionNumber), | ||||||
|  |         f: toValue(full), | ||||||
|  |     }; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export function useSubmissionsQuery( | ||||||
|  |     classid: MaybeRefOrGetter<string | undefined>, | ||||||
|  |     assignmentNumber: MaybeRefOrGetter<number | undefined>, | ||||||
|  |     groupNumber: MaybeRefOrGetter<number | undefined>, | ||||||
|  |     full: MaybeRefOrGetter<boolean> = true, | ||||||
|  | ): UseQueryReturnType<SubmissionsResponse, Error> { | ||||||
|  |     const { cid, an, gn, sn, f } = toValues(classid, assignmentNumber, groupNumber, 1, full); | ||||||
|  | 
 | ||||||
|  |     return useQuery({ | ||||||
|  |         queryKey: computed(() => submissionsQueryKey(cid!, an!, gn!, f)), | ||||||
|  |         queryFn: async () => new SubmissionController(cid!, an!, gn!).getAll(f), | ||||||
|  |         enabled: () => checkEnabled(cid, an, gn, sn), | ||||||
|  |     }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export function useSubmissionQuery( | ||||||
|  |     classid: MaybeRefOrGetter<string | undefined>, | ||||||
|  |     assignmentNumber: MaybeRefOrGetter<number | undefined>, | ||||||
|  |     groupNumber: MaybeRefOrGetter<number | undefined>, | ||||||
|  | ): UseQueryReturnType<SubmissionResponse, Error> { | ||||||
|  |     const { cid, an, gn, sn, f } = toValues(classid, assignmentNumber, groupNumber, 1, true); | ||||||
|  | 
 | ||||||
|  |     return useQuery({ | ||||||
|  |         queryKey: computed(() => submissionQueryKey(cid!, an!, gn!, sn!)), | ||||||
|  |         queryFn: async () => new SubmissionController(cid!, an!, gn!).getByNumber(sn!), | ||||||
|  |         enabled: () => checkEnabled(cid, an, gn, sn), | ||||||
|  |     }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export function useCreateSubmissionMutation(): UseMutationReturnType< | ||||||
|  |     SubmissionResponse, | ||||||
|  |     Error, | ||||||
|  |     { cid: string; an: number; gn: number; data: SubmissionDTO }, | ||||||
|  |     unknown | ||||||
|  | > { | ||||||
|  |     const queryClient = useQueryClient(); | ||||||
|  | 
 | ||||||
|  |     return useMutation({ | ||||||
|  |         mutationFn: async ({ cid, an, gn, data }) => new SubmissionController(cid, an, gn).createSubmission(data), | ||||||
|  |         onSuccess: async (response) => { | ||||||
|  |             if (!response.submission.group) { | ||||||
|  |                 await invalidateAllSubmissionKeys(queryClient); | ||||||
|  |             } else { | ||||||
|  |                 const cls = response.submission.group.class; | ||||||
|  |                 const assignment = response.submission.group.assignment; | ||||||
|  | 
 | ||||||
|  |                 const cid = typeof cls === "string" ? cls : cls.id; | ||||||
|  |                 const an = typeof assignment === "number" ? assignment : assignment.id; | ||||||
|  |                 const gn = response.submission.group.groupNumber; | ||||||
|  | 
 | ||||||
|  |                 await invalidateAllSubmissionKeys(queryClient, cid, an, gn); | ||||||
|  |             } | ||||||
|  |         }, | ||||||
|  |     }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export function useDeleteSubmissionMutation(): UseMutationReturnType< | ||||||
|  |     SubmissionResponse, | ||||||
|  |     Error, | ||||||
|  |     { cid: string; an: number; gn: number; sn: number }, | ||||||
|  |     unknown | ||||||
|  | > { | ||||||
|  |     const queryClient = useQueryClient(); | ||||||
|  | 
 | ||||||
|  |     return useMutation({ | ||||||
|  |         mutationFn: async ({ cid, an, gn, sn }) => new SubmissionController(cid, an, gn).deleteSubmission(sn), | ||||||
|  |         onSuccess: async (response) => { | ||||||
|  |             if (!response.submission.group) { | ||||||
|  |                 await invalidateAllSubmissionKeys(queryClient); | ||||||
|  |             } else { | ||||||
|  |                 const cls = response.submission.group.class; | ||||||
|  |                 const assignment = response.submission.group.assignment; | ||||||
|  | 
 | ||||||
|  |                 const cid = typeof cls === "string" ? cls : cls.id; | ||||||
|  |                 const an = typeof assignment === "number" ? assignment : assignment.id; | ||||||
|  |                 const gn = response.submission.group.groupNumber; | ||||||
|  | 
 | ||||||
|  |                 await invalidateAllSubmissionKeys(queryClient, cid, an, gn); | ||||||
|  |             } | ||||||
|  |         }, | ||||||
|  |     }); | ||||||
|  | } | ||||||
		Reference in a new issue
	
	 Adriaan J.
						Adriaan J.