Merge pull request #171 from SELab-2/feat/question-routes

feat: Question en Answer routes
This commit is contained in:
Gabriellvl 2025-04-09 12:04:22 +02:00 committed by GitHub
commit 1405fd66d1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
32 changed files with 815 additions and 218 deletions

View file

@ -0,0 +1,39 @@
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): Promise<AnswerResponse> {
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 });
}
}

View file

@ -21,20 +21,20 @@ export abstract class BaseController {
return response.data;
}
protected async post<T>(path: string, body: unknown): Promise<T> {
const response = await apiClient.post<T>(this.absolutePathFor(path), body);
protected async post<T>(path: string, body: unknown, queryParams?: QueryParams): Promise<T> {
const response = await apiClient.post<T>(this.absolutePathFor(path), body, { params: queryParams });
BaseController.assertSuccessResponse(response);
return response.data;
}
protected async delete<T>(path: string): Promise<T> {
const response = await apiClient.delete<T>(this.absolutePathFor(path));
protected async delete<T>(path: string, queryParams?: QueryParams): Promise<T> {
const response = await apiClient.delete<T>(this.absolutePathFor(path), { params: queryParams });
BaseController.assertSuccessResponse(response);
return response.data;
}
protected async put<T>(path: string, body: unknown): Promise<T> {
const response = await apiClient.put<T>(this.absolutePathFor(path), body);
protected async put<T>(path: string, body: unknown, queryParams?: QueryParams): Promise<T> {
const response = await apiClient.put<T>(this.absolutePathFor(path), body, { params: queryParams });
BaseController.assertSuccessResponse(response);
return response.data;
}

View file

@ -1,5 +1,38 @@
import type { QuestionDTO, QuestionId } from "@dwengo-1/common/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 {
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): Promise<QuestionResponse> {
return this.delete<QuestionResponse>(`/${sequenceNumber}`, { lang: this.loId.lang });
}
async update(sequenceNumber: number, questionData: QuestionData): Promise<QuestionResponse> {
return this.put<QuestionResponse>(`/${sequenceNumber}`, questionData, { lang: this.loId.lang });
}
}

View file

@ -0,0 +1,51 @@
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),
});
}

View file

@ -0,0 +1,93 @@
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)) });
},
});
}