diff --git a/backend/src/controllers/teacher-invitations.ts b/backend/src/controllers/teacher-invitations.ts index 4956f3e2..5f003f7f 100644 --- a/backend/src/controllers/teacher-invitations.ts +++ b/backend/src/controllers/teacher-invitations.ts @@ -1,6 +1,6 @@ import { Request, Response } from 'express'; -import { requireFields } from './error-helper'; -import { createInvitation, deleteInvitation, getAllInvitations, getInvitation, updateInvitation } from '../services/teacher-invitations'; +import { requireFields } from './error-helper.js'; +import { createInvitation, deleteInvitation, getAllInvitations, getInvitation, updateInvitation } from '../services/teacher-invitations.js'; import { TeacherInvitationData } from '@dwengo-1/common/interfaces/teacher-invitation'; export async function getAllInvitationsHandler(req: Request, res: Response): Promise { diff --git a/backend/src/interfaces/group.ts b/backend/src/interfaces/group.ts index 2a8287a3..792086d4 100644 --- a/backend/src/interfaces/group.ts +++ b/backend/src/interfaces/group.ts @@ -8,7 +8,7 @@ import { getGroupRepository } from '../data/repositories.js'; import { AssignmentDTO } from '@dwengo-1/common/interfaces/assignment'; import { Class } from '../entities/classes/class.entity.js'; import { StudentDTO } from '@dwengo-1/common/interfaces/student'; -import { mapToClassDTO } from './class'; +import { mapToClassDTO } from './class.js'; export function mapToGroup(groupDto: GroupDTO, clazz: Class): Group { const assignmentDto = groupDto.assignment as AssignmentDTO; diff --git a/backend/src/interfaces/submission.ts b/backend/src/interfaces/submission.ts index aa88f4a1..e179d458 100644 --- a/backend/src/interfaces/submission.ts +++ b/backend/src/interfaces/submission.ts @@ -2,9 +2,9 @@ import { Submission } from '../entities/assignments/submission.entity.js'; import { mapToGroupDTO } from './group.js'; import { mapToStudentDTO } from './student.js'; import { SubmissionDTO, SubmissionDTOId } from '@dwengo-1/common/interfaces/submission'; -import { getSubmissionRepository } from '../data/repositories'; -import { Student } from '../entities/users/student.entity'; -import { Group } from '../entities/assignments/group.entity'; +import { getSubmissionRepository } from '../data/repositories.js'; +import { Student } from '../entities/users/student.entity.js'; +import { Group } from '../entities/assignments/group.entity.js'; export function mapToSubmissionDTO(submission: Submission): SubmissionDTO { return { diff --git a/backend/src/interfaces/teacher-invitation.ts b/backend/src/interfaces/teacher-invitation.ts index 8fef17af..88b66f7a 100644 --- a/backend/src/interfaces/teacher-invitation.ts +++ b/backend/src/interfaces/teacher-invitation.ts @@ -1,9 +1,9 @@ import { TeacherInvitation } from '../entities/classes/teacher-invitation.entity.js'; import { mapToUserDTO } from './user.js'; import { TeacherInvitationDTO } from '@dwengo-1/common/interfaces/teacher-invitation'; -import { getTeacherInvitationRepository } from '../data/repositories'; -import { Teacher } from '../entities/users/teacher.entity'; -import { Class } from '../entities/classes/class.entity'; +import { getTeacherInvitationRepository } from '../data/repositories.js'; +import { Teacher } from '../entities/users/teacher.entity.js'; +import { Class } from '../entities/classes/class.entity.js'; import { ClassStatus } from '@dwengo-1/common/util/class-join-request'; export function mapToTeacherInvitationDTO(invitation: TeacherInvitation): TeacherInvitationDTO { diff --git a/backend/src/routes/teacher-invitations.ts b/backend/src/routes/teacher-invitations.ts index 772b1351..23b943d0 100644 --- a/backend/src/routes/teacher-invitations.ts +++ b/backend/src/routes/teacher-invitations.ts @@ -5,7 +5,7 @@ import { getAllInvitationsHandler, getInvitationHandler, updateInvitationHandler, -} from '../controllers/teacher-invitations'; +} from '../controllers/teacher-invitations.js'; const router = express.Router({ mergeParams: true }); diff --git a/backend/src/services/questions.ts b/backend/src/services/questions.ts index 49bf9e92..a16c277b 100644 --- a/backend/src/services/questions.ts +++ b/backend/src/services/questions.ts @@ -10,7 +10,7 @@ import { AnswerDTO, AnswerId } from '@dwengo-1/common/interfaces/answer'; import { mapToAssignment } from '../interfaces/assignment.js'; import { AssignmentDTO } from '@dwengo-1/common/interfaces/assignment'; import { fetchStudent } from './students.js'; -import { NotFoundException } from '../exceptions/not-found-exception'; +import { NotFoundException } from '../exceptions/not-found-exception.js'; import { FALLBACK_VERSION_NUM } from '../config.js'; export async function getQuestionsAboutLearningObjectInAssignment( diff --git a/backend/src/services/teacher-invitations.ts b/backend/src/services/teacher-invitations.ts index 07f61bae..aead8715 100644 --- a/backend/src/services/teacher-invitations.ts +++ b/backend/src/services/teacher-invitations.ts @@ -1,11 +1,11 @@ -import { fetchTeacher } from './teachers'; -import { getTeacherInvitationRepository } from '../data/repositories'; -import { mapToInvitation, mapToTeacherInvitationDTO } from '../interfaces/teacher-invitation'; -import { addClassTeacher, fetchClass } from './classes'; +import { fetchTeacher } from './teachers.js'; +import { getTeacherInvitationRepository } from '../data/repositories.js'; +import { mapToInvitation, mapToTeacherInvitationDTO } from '../interfaces/teacher-invitation.js'; +import { addClassTeacher, fetchClass } from './classes.js'; import { TeacherInvitationData, TeacherInvitationDTO } from '@dwengo-1/common/interfaces/teacher-invitation'; -import { ConflictException } from '../exceptions/conflict-exception'; -import { NotFoundException } from '../exceptions/not-found-exception'; -import { TeacherInvitation } from '../entities/classes/teacher-invitation.entity'; +import { ConflictException } from '../exceptions/conflict-exception.js'; +import { NotFoundException } from '../exceptions/not-found-exception.js'; +import { TeacherInvitation } from '../entities/classes/teacher-invitation.entity.js'; import { ClassStatus } from '@dwengo-1/common/util/class-join-request'; export async function getAllInvitations(username: string, sent: boolean): Promise { diff --git a/frontend/src/controllers/teacher-invitations.ts b/frontend/src/controllers/teacher-invitations.ts index 7750dea5..b296e771 100644 --- a/frontend/src/controllers/teacher-invitations.ts +++ b/frontend/src/controllers/teacher-invitations.ts @@ -11,10 +11,11 @@ export interface TeacherInvitationResponse { export class TeacherInvitationController extends BaseController { constructor() { - super("teachers/invitations"); + super("teacher/invitations"); } - async getAll(username: string, sent: boolean): Promise { + async getAll(username: string, s: boolean): Promise { + const sent = s.toString(); return this.get(`/${username}`, { sent }); } diff --git a/frontend/src/queries/answers.ts b/frontend/src/queries/answers.ts index f2d0f9c4..830f3066 100644 --- a/frontend/src/queries/answers.ts +++ b/frontend/src/queries/answers.ts @@ -1,16 +1,35 @@ -import type { QuestionId } from "@dwengo-1/common/dist/interfaces/question.ts"; -import { type MaybeRefOrGetter, toValue } from "vue"; -import { useMutation, type UseMutationReturnType, useQuery, type UseQueryReturnType } from "@tanstack/vue-query"; -import { AnswerController, type AnswerResponse, type AnswersResponse } from "@/controllers/answers.ts"; -import type { AnswerData } from "@dwengo-1/common/dist/interfaces/answer.ts"; +import {computed, type MaybeRefOrGetter, toValue} from "vue"; +import { + useMutation, + type UseMutationReturnType, + useQuery, + type UseQueryReturnType, + useQueryClient, +} from "@tanstack/vue-query"; +import { + AnswerController, + type AnswerResponse, + type AnswersResponse, +} from "@/controllers/answers.ts"; +import type { AnswerData } from "@dwengo-1/common/interfaces/answer"; +import type {QuestionId} from "@dwengo-1/common/interfaces/question"; -// TODO caching +/** 🔑 Query keys */ +export function answersQueryKey(questionId: QuestionId, full: boolean): [string, string, number, string, number, boolean] { + const loId = questionId.learningObjectIdentifier; + return ["answers", loId.hruid, loId.version!, loId.language, questionId.sequenceNumber, full]; +} +export function answerQueryKey(questionId: QuestionId, sequenceNumber: number): [string, string, number, string, number, number] { + const loId = questionId.learningObjectIdentifier; + return ["answer", loId.hruid, loId.version!, loId.language, questionId.sequenceNumber, sequenceNumber]; +} export function useAnswersQuery( questionId: MaybeRefOrGetter, full: MaybeRefOrGetter = true, ): UseQueryReturnType { return useQuery({ + queryKey: computed(() => answersQueryKey(toValue(questionId), toValue(full))), queryFn: async () => new AnswerController(toValue(questionId)).getAll(toValue(full)), enabled: () => Boolean(toValue(questionId)), }); @@ -21,31 +40,69 @@ export function useAnswerQuery( sequenceNumber: MaybeRefOrGetter, ): UseQueryReturnType { return useQuery({ + queryKey: computed(() => answerQueryKey(toValue(questionId), toValue(sequenceNumber))), queryFn: async () => new AnswerController(toValue(questionId)).getBy(toValue(sequenceNumber)), - enabled: () => Boolean(toValue(questionId)), + enabled: () => Boolean(toValue(questionId)) && Boolean(toValue(sequenceNumber)), }); } export function useCreateAnswerMutation( questionId: MaybeRefOrGetter, ): UseMutationReturnType { + const queryClient = useQueryClient(); + return useMutation({ mutationFn: async (data) => new AnswerController(toValue(questionId)).create(data), + onSuccess: async () => { + await queryClient.invalidateQueries({ + queryKey: answersQueryKey(toValue(questionId), true), + }); + await queryClient.invalidateQueries({ + queryKey: answersQueryKey(toValue(questionId), false), + }); + }, }); } export function useDeleteAnswerMutation( questionId: MaybeRefOrGetter, ): UseMutationReturnType { + const queryClient = useQueryClient(); + return useMutation({ mutationFn: async (seq) => new AnswerController(toValue(questionId)).remove(seq), + onSuccess: async () => { + await queryClient.invalidateQueries({ + queryKey: answersQueryKey(toValue(questionId), true), + }); + await queryClient.invalidateQueries({ + queryKey: answersQueryKey(toValue(questionId), false), + }); + }, }); } export function useUpdateAnswerMutation( questionId: MaybeRefOrGetter, ): UseMutationReturnType { + const queryClient = useQueryClient(); + return useMutation({ - mutationFn: async (data, seq) => new AnswerController(toValue(questionId)).update(seq, data), + mutationFn: async ({ answerData, seq }) => + new AnswerController(toValue(questionId)).update(seq, answerData), + onSuccess: async (_, { seq }) => { + await queryClient.invalidateQueries({ + queryKey: answerQueryKey(toValue(questionId), seq), + }); + await queryClient.invalidateQueries({ + queryKey: answersQueryKey(toValue(questionId), true), + }); + await queryClient.invalidateQueries({ + queryKey: answersQueryKey(toValue(questionId), true), + }); + await queryClient.invalidateQueries({ + queryKey: answersQueryKey(toValue(questionId), false), + }); + }, }); } diff --git a/frontend/src/queries/questions.ts b/frontend/src/queries/questions.ts index b69164a4..66f1492f 100644 --- a/frontend/src/queries/questions.ts +++ b/frontend/src/queries/questions.ts @@ -14,12 +14,12 @@ export function questionsQueryKey( loId: LearningObjectIdentifierDTO, full: boolean, ): [string, string, number, string, boolean] { - return ["questions", loId.hruid, loId.version, loId.language, full]; + return ["questions", loId.hruid, loId.version!, loId.language, full]; } export function questionQueryKey(questionId: QuestionId): [string, string, number, string, number] { const loId = questionId.learningObjectIdentifier; - return ["question", loId.hruid, loId.version, loId.language, questionId.sequenceNumber]; + return ["question", loId.hruid, loId.version!, loId.language, questionId.sequenceNumber]; } export function useQuestionsQuery( @@ -39,7 +39,7 @@ export function useQuestionQuery( const loId = toValue(questionId).learningObjectIdentifier; const sequenceNumber = toValue(questionId).sequenceNumber; return useQuery({ - queryKey: computed(() => questionQueryKey(loId, sequenceNumber)), + queryKey: computed(() => questionQueryKey(toValue(questionId))), queryFn: async () => new QuestionController(loId).getBy(sequenceNumber), enabled: () => Boolean(toValue(questionId)), }); @@ -55,6 +55,7 @@ export function useCreateQuestionMutation( onSuccess: async () => { await queryClient.invalidateQueries({ queryKey: questionsQueryKey(toValue(loId), true) }); await queryClient.invalidateQueries({ queryKey: questionsQueryKey(toValue(loId), false) }); + await queryClient.invalidateQueries({ queryKey: ["answers"] }); }, }); } @@ -88,6 +89,8 @@ export function useDeleteQuestionMutation( await queryClient.invalidateQueries({ queryKey: questionsQueryKey(toValue(loId), true) }); await queryClient.invalidateQueries({ queryKey: questionsQueryKey(toValue(loId), false) }); await queryClient.invalidateQueries({ queryKey: questionQueryKey(toValue(questionId)) }); + await queryClient.invalidateQueries({ queryKey: ["answers"] }); + await queryClient.invalidateQueries({ queryKey: ["answer"] }); }, }); } diff --git a/frontend/src/queries/teacher-invitations.ts b/frontend/src/queries/teacher-invitations.ts index 59357c32..3e650343 100644 --- a/frontend/src/queries/teacher-invitations.ts +++ b/frontend/src/queries/teacher-invitations.ts @@ -1,36 +1,56 @@ -import { useMutation, useQuery, type UseMutationReturnType, type UseQueryReturnType } from "@tanstack/vue-query"; -import { computed, toValue } from "vue"; +import { + useMutation, + useQuery, + useQueryClient, + type UseMutationReturnType, + type UseQueryReturnType, +} from "@tanstack/vue-query"; +import { toValue } from "vue"; import type { MaybeRefOrGetter } from "vue"; import { TeacherInvitationController, type TeacherInvitationResponse, type TeacherInvitationsResponse, -} from "@/controllers/teacher-invitations.ts"; +} from "@/controllers/teacher-invitations"; import type { TeacherInvitationData } from "@dwengo-1/common/interfaces/teacher-invitation"; -import type { TeacherDTO } from "@dwengo-1/common/interfaces/teacher"; const controller = new TeacherInvitationController(); +/** 🔑 Query keys */ +export function teacherInvitationsSentQueryKey(username: string): [string, string, string] { + return ["teacher-invitations", "sent", username]; +} + +export function teacherInvitationsReceivedQueryKey(username: string): [string, string, string] { + return ["teacher-invitations", "received", username ]; +} + +export function teacherInvitationQueryKey(data: TeacherInvitationData): [string, string, string, string] { + return ["teacher-invitation", data.sender, data.receiver, data.class]; +} + /** - All the invitations the teacher sent -**/ + * All the invitations the teacher sent + */ export function useTeacherInvitationsSentQuery( username: MaybeRefOrGetter, ): UseQueryReturnType { return useQuery({ - queryFn: computed(async () => controller.getAll(toValue(username), true)), + queryKey: teacherInvitationsSentQueryKey(toValue(username)!), + queryFn: async () => controller.getAll(toValue(username)!, true), enabled: () => Boolean(toValue(username)), }); } /** - All the pending invitations sent to this teacher + * All the pending invitations sent to this teacher */ export function useTeacherInvitationsReceivedQuery( username: MaybeRefOrGetter, ): UseQueryReturnType { return useQuery({ - queryFn: computed(async () => controller.getAll(toValue(username), false)), + queryKey: teacherInvitationsReceivedQueryKey(toValue(username)!), + queryFn: async () => controller.getAll(toValue(username)!, false), enabled: () => Boolean(toValue(username)), }); } @@ -39,7 +59,8 @@ export function useTeacherInvitationQuery( data: MaybeRefOrGetter, ): UseQueryReturnType { return useQuery({ - queryFn: computed(async () => controller.getBy(toValue(data))), + queryKey: teacherInvitationQueryKey(toValue(data)!), + queryFn: async () => controller.getBy(toValue(data)!), enabled: () => Boolean(toValue(data)), }); } @@ -47,32 +68,68 @@ export function useTeacherInvitationQuery( export function useCreateTeacherInvitationMutation(): UseMutationReturnType< TeacherInvitationResponse, Error, - TeacherDTO, + TeacherInvitationData, unknown > { + const queryClient = useQueryClient(); + return useMutation({ - mutationFn: async (data: TeacherInvitationData) => controller.create(data), + mutationFn: async (data) => controller.create(data), + onSuccess: async (_, data) => { + await queryClient.invalidateQueries({ + queryKey: teacherInvitationsSentQueryKey(data.sender), + }); + await queryClient.invalidateQueries({ + queryKey: teacherInvitationsReceivedQueryKey(data.receiver), + }); + }, }); } export function useRespondTeacherInvitationMutation(): UseMutationReturnType< TeacherInvitationResponse, Error, - TeacherDTO, + TeacherInvitationData, unknown > { + const queryClient = useQueryClient(); + return useMutation({ - mutationFn: async (data: TeacherInvitationData) => controller.respond(data), + mutationFn: async (data) => controller.respond(data), + onSuccess: async (_, data) => { + await queryClient.invalidateQueries({ + queryKey: teacherInvitationsSentQueryKey(data.sender), + }); + await queryClient.invalidateQueries({ + queryKey: teacherInvitationsReceivedQueryKey(data.receiver), + }); + await queryClient.invalidateQueries({ + queryKey: teacherInvitationQueryKey(data), + }); + }, }); } export function useDeleteTeacherInvitationMutation(): UseMutationReturnType< TeacherInvitationResponse, Error, - TeacherDTO, + TeacherInvitationData, unknown > { + const queryClient = useQueryClient(); + return useMutation({ - mutationFn: async (data: TeacherInvitationData) => controller.remove(data), + mutationFn: async (data) => controller.remove(data), + onSuccess: async (_, data) => { + await queryClient.invalidateQueries({ + queryKey: teacherInvitationsSentQueryKey(data.sender), + }); + await queryClient.invalidateQueries({ + queryKey: teacherInvitationsReceivedQueryKey(data.receiver), + }); + await queryClient.invalidateQueries({ + queryKey: teacherInvitationQueryKey(data), + }); + }, }); } diff --git a/frontend/src/queries/themes.ts b/frontend/src/queries/themes.ts index c3be25ae..ee197d14 100644 --- a/frontend/src/queries/themes.ts +++ b/frontend/src/queries/themes.ts @@ -1,7 +1,7 @@ import { useQuery, type UseQueryReturnType } from "@tanstack/vue-query"; import { type MaybeRefOrGetter, toValue } from "vue"; -import type { Theme } from "@dwengo-1/interfaces/theme"; import { getThemeController } from "@/controllers/controllers.ts"; +import type {Theme} from "@dwengo-1/common/interfaces/theme"; const themeController = getThemeController();