feat: question answer frontend controller en queries
This commit is contained in:
		
							parent
							
								
									7f7a4fe936
								
							
						
					
					
						commit
						09a11589d2
					
				
					 6 changed files with 229 additions and 8 deletions
				
			
		|  | @ -16,6 +16,6 @@ router.delete('/:seq', deleteQuestionHandler); | ||||||
| // Information about a question with id
 | // Information about a question with id
 | ||||||
| router.get('/:seq', getQuestionHandler); | router.get('/:seq', getQuestionHandler); | ||||||
| 
 | 
 | ||||||
| router.use('/:seq/answer', answerRoutes); | router.use('/:seq/answers', answerRoutes); | ||||||
| 
 | 
 | ||||||
| export default router; | export default router; | ||||||
|  |  | ||||||
							
								
								
									
										40
									
								
								frontend/src/controllers/answers.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								frontend/src/controllers/answers.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,40 @@ | ||||||
|  | import type {AnswerData, AnswerDTO, AnswerId} from "@dwengo-1/common/interfaces/answer"; | ||||||
|  | import {BaseController} from "@/controllers/base-controller.ts"; | ||||||
|  | import type {QuestionId} from "@dwengo-1/common/interfaces/question"; | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | export interface AnswersResponse { | ||||||
|  |     answers: AnswerDTO[] | AnswerId[] | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export interface AnswerResponse { | ||||||
|  |     answer: AnswerDTO | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export class AnswerController extends BaseController { | ||||||
|  |     constructor(questionId: QuestionId) { | ||||||
|  |         this.loId = questionId.learningObjectIdentifier; | ||||||
|  |         this.sequenceNumber = questionId.sequenceNumber; | ||||||
|  |         super(`learningObject/${loId.hruid}/:${loId.version}/questions/${this.sequenceNumber}/answers`) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     async getAll(full = true): Promise<AnswersResponse> { | ||||||
|  |         return this.get<AnswersResponse>("/", {lang: this.loId.lang, full}); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     async getBy(seq: number): Promise<AnswerResponse> { | ||||||
|  |         return this.get<AnswerResponse>(`/${seq}`, {lang: this.loId.lang}); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     async create(answerData: AnswerData) { | ||||||
|  |         return this.post<AnswerResponse>("/", answerData, {lang: this.loId.lang}); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     async remove(seq: number): Promise<AnswerResponse> { | ||||||
|  |         return this.delete<AnswerResponse>(`/${seq}`, {lang: this.loId.lang}); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     async update(seq: number, answerData: AnswerData): Promise<AnswerResponse> { | ||||||
|  |         return this.put<AnswerResponse>(`/${seq}`, answerData,{lang: this.loId.lang}); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | @ -21,20 +21,20 @@ export abstract class BaseController { | ||||||
|         return response.data; |         return response.data; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     protected async post<T>(path: string, body: unknown): Promise<T> { |     protected async post<T>(path: string, body: unknown, queryParams?: QueryParams): Promise<T> { | ||||||
|         const response = await apiClient.post<T>(this.absolutePathFor(path), body); |         const response = await apiClient.post<T>(this.absolutePathFor(path), body, { params: queryParams }); | ||||||
|         BaseController.assertSuccessResponse(response); |         BaseController.assertSuccessResponse(response); | ||||||
|         return response.data; |         return response.data; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     protected async delete<T>(path: string): Promise<T> { |     protected async delete<T>(path: string, queryParams?: QueryParams): Promise<T> { | ||||||
|         const response = await apiClient.delete<T>(this.absolutePathFor(path)); |         const response = await apiClient.delete<T>(this.absolutePathFor(path), { params: queryParams} ); | ||||||
|         BaseController.assertSuccessResponse(response); |         BaseController.assertSuccessResponse(response); | ||||||
|         return response.data; |         return response.data; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     protected async put<T>(path: string, body: unknown): Promise<T> { |     protected async put<T>(path: string, body: unknown, queryParams?: QueryParams): Promise<T> { | ||||||
|         const response = await apiClient.put<T>(this.absolutePathFor(path), body); |         const response = await apiClient.put<T>(this.absolutePathFor(path), body, { params: queryParams}); | ||||||
|         BaseController.assertSuccessResponse(response); |         BaseController.assertSuccessResponse(response); | ||||||
|         return response.data; |         return response.data; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  | @ -1,5 +1,38 @@ | ||||||
| import type { QuestionDTO, QuestionId } from "@dwengo-1/interfaces/question"; | import type {QuestionData, QuestionDTO, QuestionId} from "@dwengo-1/common/interfaces/question"; | ||||||
|  | import {BaseController} from "@/controllers/base-controller.ts"; | ||||||
|  | import type {LearningObjectIdentifierDTO} from "@dwengo-1/common/interfaces/learning-content"; | ||||||
| 
 | 
 | ||||||
| export interface QuestionsResponse { | export interface QuestionsResponse { | ||||||
|     questions: QuestionDTO[] | QuestionId[]; |     questions: QuestionDTO[] | QuestionId[]; | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | export interface QuestionResponse { | ||||||
|  |     question: QuestionDTO; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export class QuestionController extends BaseController { | ||||||
|  |     constructor(loId: LearningObjectIdentifierDTO) { | ||||||
|  |         this.loId = loId; | ||||||
|  |         super(`learningObject/${loId.hruid}/:${loId.version}/questions`); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     async getAll(full = true): Promise<QuestionsResponse> { | ||||||
|  |         return this.get<QuestionsResponse>("/", {lang: this.loId.lang, full}); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     async getBy(sequenceNumber: number): Promise<QuestionResponse> { | ||||||
|  |         return this.get<QuestionResponse>(`/${sequenceNumber}`, {lang: this.loId.lang}); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     async create(questionData: QuestionData): Promise<QuestionResponse> { | ||||||
|  |         return this.post<QuestionResponse>("/", questionData, {lang: this.loId.lang}) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     async remove(sequenceNumber: number) { | ||||||
|  |         return this.delete<QuestionResponse>(`/${sequenceNumber}`, {lang: this.loId.lang}); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     async update(sequenceNumber: number, questionData: QuestionData) { | ||||||
|  |         return this.put<QuestionResponse>(`/${sequenceNumber}`, questionData, {lang: this.loId.lang}); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
							
								
								
									
										57
									
								
								frontend/src/queries/answers.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								frontend/src/queries/answers.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,57 @@ | ||||||
|  | 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"; | ||||||
|  | 
 | ||||||
|  | // TODO caching
 | ||||||
|  | 
 | ||||||
|  | export function useAnswersQuery( | ||||||
|  |     questionId: MaybeRefOrGetter<QuestionId>, | ||||||
|  |     full: MaybeRefOrGetter<boolean> = true, | ||||||
|  | ): UseQueryReturnType<AnswersResponse, Error> { | ||||||
|  |     return useQuery({ | ||||||
|  |         queryFn: async () => new AnswerController(toValue(questionId)).getAll(toValue(full)), | ||||||
|  |         enabled: () => Boolean(toValue(questionId)), | ||||||
|  |     }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export function useAnswerQuery( | ||||||
|  |     questionId: MaybeRefOrGetter<QuestionId>, | ||||||
|  |     sequenceNumber: MaybeRefOrGetter<number> | ||||||
|  | ): UseQueryReturnType<AnswerResponse, Error> { | ||||||
|  |     return useQuery({ | ||||||
|  |         queryFn: async () => new AnswerController(toValue(questionId)).getBy(toValue(sequenceNumber)), | ||||||
|  |         enabled: () => Boolean(toValue(questionId)), | ||||||
|  |     }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export function useCreateAnswerMutation( | ||||||
|  |     questionId: MaybeRefOrGetter<QuestionId>, | ||||||
|  | ): UseMutationReturnType<AnswerResponse, Error, AnswerData, unknown> { | ||||||
|  |     return useMutation({ | ||||||
|  |         mutationFn: async (data) => new AnswerController(toValue(questionId)).create(data), | ||||||
|  |     }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export function useDeleteAnswerMutation( | ||||||
|  |     questionId: MaybeRefOrGetter<QuestionId>, | ||||||
|  | ): UseMutationReturnType<AnswerResponse, Error, number, unknown> { | ||||||
|  |     return useMutation({ | ||||||
|  |         mutationFn: async (seq) => new AnswerController(toValue(questionId)).remove(seq), | ||||||
|  |     }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export function useUpdateAnswerMutation( | ||||||
|  |     questionId: MaybeRefOrGetter<QuestionId>, | ||||||
|  | ): UseMutationReturnType<AnswerResponse, Error, { answerData: AnswerData, seq: number }, unknown> { | ||||||
|  |     return useMutation({ | ||||||
|  |         mutationFn: async (data, seq) => new AnswerController(toValue(questionId)).update(seq, data), | ||||||
|  |     }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
							
								
								
									
										91
									
								
								frontend/src/queries/questions.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										91
									
								
								frontend/src/queries/questions.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,91 @@ | ||||||
|  | import {QuestionController, type QuestionResponse, type QuestionsResponse} from "@/controllers/questions.ts"; | ||||||
|  | import type {QuestionData, QuestionId} from "@dwengo-1/common/interfaces/question"; | ||||||
|  | import type {LearningObjectIdentifierDTO} from "@dwengo-1/common/interfaces/learning-content"; | ||||||
|  | import {computed, type MaybeRefOrGetter, toValue} from "vue"; | ||||||
|  | import { | ||||||
|  |     useMutation, | ||||||
|  |     type UseMutationReturnType, | ||||||
|  |     useQuery, | ||||||
|  |     useQueryClient, | ||||||
|  |     type UseQueryReturnType | ||||||
|  | } from "@tanstack/vue-query"; | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | export function questionsQueryKey(loId: LearningObjectIdentifierDTO, full: boolean): [string, string, number, string, boolean] { | ||||||
|  |     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]; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export function useQuestionsQuery( | ||||||
|  |     loId: MaybeRefOrGetter<LearningObjectIdentifierDTO>, | ||||||
|  |     full: MaybeRefOrGetter<boolean> = true, | ||||||
|  | ): UseQueryReturnType<QuestionsResponse, Error> { | ||||||
|  |     return useQuery({ | ||||||
|  |         queryKey: computed(() => questionsQueryKey(toValue(loId), toValue(full))), | ||||||
|  |         queryFn: async () => new QuestionController(toValue(loId)).getAll(toValue(full)), | ||||||
|  |         enabled: () => Boolean(toValue(loId)), | ||||||
|  |     }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export function useQuestionQuery( | ||||||
|  |     questionId: MaybeRefOrGetter<QuestionId>, | ||||||
|  | ): UseQueryReturnType<QuestionResponse, Error> { | ||||||
|  |     const loId = toValue(questionId).learningObjectIdentifier; | ||||||
|  |     const sequenceNumber = toValue(questionId).sequenceNumber; | ||||||
|  |     return useQuery({ | ||||||
|  |         queryKey: computed(() => questionQueryKey(loId, sequenceNumber)), | ||||||
|  |         queryFn: async () => new QuestionController(loId).getBy(sequenceNumber), | ||||||
|  |         enabled: () => Boolean(toValue(questionId)), | ||||||
|  |     }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export function useCreateQuestionMutation( | ||||||
|  |     loId: MaybeRefOrGetter<LearningObjectIdentifierDTO>, | ||||||
|  | ): UseMutationReturnType<QuestionResponse, Error, QuestionData, unknown> { | ||||||
|  |     const queryClient = useQueryClient(); | ||||||
|  | 
 | ||||||
|  |     return useMutation({ | ||||||
|  |         mutationFn: async (data) => new QuestionController(toValue(loId)).create(data), | ||||||
|  |         onSuccess: async () => { | ||||||
|  |             await queryClient.invalidateQueries({ queryKey: questionsQueryKey(toValue(loId), true) }); | ||||||
|  |             await queryClient.invalidateQueries({ queryKey: questionsQueryKey(toValue(loId), false) }); | ||||||
|  |         }, | ||||||
|  |     }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export function useUpdateQuestionMutation( | ||||||
|  |     questionId: MaybeRefOrGetter<QuestionId>, | ||||||
|  | ): UseMutationReturnType<QuestionResponse, Error, QuestionData, unknown> { | ||||||
|  |     const queryClient = useQueryClient(); | ||||||
|  |     const loId = toValue(questionId).learningObjectIdentifier; | ||||||
|  |     const sequenceNumber = toValue(questionId).sequenceNumber; | ||||||
|  | 
 | ||||||
|  |     return useMutation({ | ||||||
|  |         mutationFn: async ( data ) => new QuestionController(loId).update(sequenceNumber, data), | ||||||
|  |         onSuccess: async () => { | ||||||
|  |             await queryClient.invalidateQueries({ queryKey: questionsQueryKey(toValue(loId), true) }); | ||||||
|  |             await queryClient.invalidateQueries({ queryKey: questionsQueryKey(toValue(loId), false) }); | ||||||
|  |             await queryClient.invalidateQueries({ queryKey: questionQueryKey(toValue(questionId)) }); | ||||||
|  |         }, | ||||||
|  |     }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export function useDeleteQuestionMutation( | ||||||
|  |     questionId: MaybeRefOrGetter<QuestionId>, | ||||||
|  | ): UseMutationReturnType<QuestionResponse, Error, void, unknown> { | ||||||
|  |     const queryClient = useQueryClient(); | ||||||
|  |     const loId = toValue(questionId).learningObjectIdentifier; | ||||||
|  |     const sequenceNumber = toValue(questionId).sequenceNumber; | ||||||
|  |     return useMutation({ | ||||||
|  |         mutationFn: async () => new QuestionController(loId).remove(sequenceNumber), | ||||||
|  |         onSuccess: async () => { | ||||||
|  |             await queryClient.invalidateQueries({ queryKey: questionsQueryKey(toValue(loId), true) }); | ||||||
|  |             await queryClient.invalidateQueries({ queryKey: questionsQueryKey(toValue(loId), false) }); | ||||||
|  |             await queryClient.invalidateQueries({ queryKey: questionQueryKey(toValue(questionId)) }); | ||||||
|  |         }, | ||||||
|  |     }); | ||||||
|  | } | ||||||
		Reference in a new issue
	
	 Gabriellvl
						Gabriellvl