diff --git a/frontend/src/controllers/groups.ts b/frontend/src/controllers/groups.ts index de6592b5..4c38290f 100644 --- a/frontend/src/controllers/groups.ts +++ b/frontend/src/controllers/groups.ts @@ -36,11 +36,11 @@ export class GroupController extends BaseController { return this.put(`/${num}`, data); } - async getSubmissions(groupNumber: number, full = true): Promise { - return this.get(`/${groupNumber}/submissions`, { full }); + async getSubmissions(num: number, full = true): Promise { + return this.get(`/${num}/submissions`, { full }); } - async getQuestions(groupNumber: number, full = true): Promise { - return this.get(`/${groupNumber}/questions`, { full }); + async getQuestions(num: number, full = true): Promise { + return this.get(`/${num}/questions`, { full }); } } diff --git a/frontend/src/controllers/submissions.ts b/frontend/src/controllers/submissions.ts index 837d356c..0d9c73f0 100644 --- a/frontend/src/controllers/submissions.ts +++ b/frontend/src/controllers/submissions.ts @@ -11,7 +11,7 @@ export interface SubmissionResponse { export class SubmissionController extends BaseController { 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 { @@ -22,7 +22,7 @@ export class SubmissionController extends BaseController { return this.get(`/${submissionNumber}`); } - async createSubmission(data: unknown): Promise { + async createSubmission(data: SubmissionDTO): Promise { return this.post(`/`, data); } diff --git a/frontend/src/queries/assignments.ts b/frontend/src/queries/assignments.ts new file mode 100644 index 00000000..3251836a --- /dev/null +++ b/frontend/src/queries/assignments.ts @@ -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, + assignmentNumber: MaybeRefOrGetter, + groupNumber: MaybeRefOrGetter, + full: MaybeRefOrGetter, +) { + return { cid: toValue(classid), an: toValue(assignmentNumber), gn: toValue(groupNumber), f: toValue(full) }; +} + +export function useAssignmentsQuery( + classid: MaybeRefOrGetter, + full: MaybeRefOrGetter = true, +): UseQueryReturnType { + 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, + assignmentNumber: MaybeRefOrGetter, +): UseQueryReturnType { + 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 }, + 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, + assignmentNumber: MaybeRefOrGetter, + groupNumber: MaybeRefOrGetter, + full: MaybeRefOrGetter = true, +): UseQueryReturnType { + 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, + assignmentNumber: MaybeRefOrGetter, + groupNumber: MaybeRefOrGetter, + full: MaybeRefOrGetter = true, +): UseQueryReturnType { + 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, + assignmentNumber: MaybeRefOrGetter, + groupNumber: MaybeRefOrGetter, + full: MaybeRefOrGetter = true, +): UseQueryReturnType { + 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), + }); +} diff --git a/frontend/src/queries/classes.ts b/frontend/src/queries/classes.ts new file mode 100644 index 00000000..68ba0127 --- /dev/null +++ b/frontend/src/queries/classes.ts @@ -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 = true): UseQueryReturnType { + return useQuery({ + queryKey: computed(() => classesQueryKey(toValue(full))), + queryFn: async () => classController.getAll(toValue(full)), + }); +} + +export function useClassQuery(id: MaybeRefOrGetter): UseQueryReturnType { + return useQuery({ + queryKey: computed(() => classQueryKey(toValue(id)!)), + queryFn: async () => classController.getById(toValue(id)!), + enabled: () => Boolean(toValue(id)), + }); +} + +export function useCreateClassMutation(): UseMutationReturnType { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: async (data) => classController.createClass(data), + onSuccess: async () => { + await queryClient.invalidateQueries({ queryKey: ["classes"] }); + }, + }); +} + +export function useDeleteClassMutation(): UseMutationReturnType { + 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 }, + 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, + full: MaybeRefOrGetter = true, +): UseQueryReturnType { + 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, + full: MaybeRefOrGetter = true, +): UseQueryReturnType { + 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, + full: MaybeRefOrGetter = true, +): UseQueryReturnType { + 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, + full: MaybeRefOrGetter = true, +): UseQueryReturnType { + return useQuery({ + queryKey: computed(() => classAssignmentsKey(toValue(id)!, toValue(full))), + queryFn: async () => classController.getAssignments(toValue(id)!, toValue(full)), + enabled: () => Boolean(toValue(id)), + }); +} diff --git a/frontend/src/queries/groups.ts b/frontend/src/queries/groups.ts new file mode 100644 index 00000000..cdef2899 --- /dev/null +++ b/frontend/src/queries/groups.ts @@ -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, + assignmentNumber: MaybeRefOrGetter, + groupNumber: MaybeRefOrGetter, + full: MaybeRefOrGetter, +) { + return { cid: toValue(classid), an: toValue(assignmentNumber), gn: toValue(groupNumber), f: toValue(full) }; +} + +export function useGroupsQuery( + classid: MaybeRefOrGetter, + assignmentNumber: MaybeRefOrGetter, + full: MaybeRefOrGetter = true, +): UseQueryReturnType { + 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, + assignmentNumber: MaybeRefOrGetter, + groupNumber: MaybeRefOrGetter, +): UseQueryReturnType { + 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 }, + 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, + assignmentNumber: MaybeRefOrGetter, + groupNumber: MaybeRefOrGetter, + full: MaybeRefOrGetter = true, +): UseQueryReturnType { + 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, + assignmentNumber: MaybeRefOrGetter, + groupNumber: MaybeRefOrGetter, + full: MaybeRefOrGetter = true, +): UseQueryReturnType { + 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), + }); +} diff --git a/frontend/src/queries/submissions.ts b/frontend/src/queries/submissions.ts new file mode 100644 index 00000000..97effd15 --- /dev/null +++ b/frontend/src/queries/submissions.ts @@ -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, + assignmentNumber: MaybeRefOrGetter, + groupNumber: MaybeRefOrGetter, + submissionNumber: MaybeRefOrGetter, + full: MaybeRefOrGetter, +) { + return { + cid: toValue(classid), + an: toValue(assignmentNumber), + gn: toValue(groupNumber), + sn: toValue(submissionNumber), + f: toValue(full), + }; +} + +export function useSubmissionsQuery( + classid: MaybeRefOrGetter, + assignmentNumber: MaybeRefOrGetter, + groupNumber: MaybeRefOrGetter, + full: MaybeRefOrGetter = true, +): UseQueryReturnType { + 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, + assignmentNumber: MaybeRefOrGetter, + groupNumber: MaybeRefOrGetter, +): UseQueryReturnType { + 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); + } + }, + }); +}