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)) });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue