fix(backend): Diverse bugfixes omtrent LearningPathPage

This commit is contained in:
Gerald Schmittinger 2025-03-31 23:12:03 +02:00
parent 27b9cdf833
commit 12b9f31f5f
9 changed files with 34 additions and 33 deletions

View file

@ -1,6 +1,5 @@
<script setup lang="ts" generic="T"> <script setup lang="ts" generic="T">
import {RemoteResource} from "@/services/api-client/remote-resource.ts"; import {computed} from "vue";
import {computed, type MaybeRefOrGetter} from "vue";
import {useI18n} from "vue-i18n"; import {useI18n} from "vue-i18n";
import type {UseQueryReturnType} from "@tanstack/vue-query"; import type {UseQueryReturnType} from "@tanstack/vue-query";
@ -8,13 +7,12 @@
queryResult: UseQueryReturnType<T, Error> queryResult: UseQueryReturnType<T, Error>
}>() }>()
const { isLoading, isError, isSuccess, data, error } = props.queryResult;
const { t } = useI18n(); const { t } = useI18n();
const isLoading = computed(() => props.queryResult.isFetching);
const data = computed(() => props.queryResult.data);
const error = computed(() => props.queryResult.error);
const errorMessage = computed(() => { const errorMessage = computed(() => {
let errorWithMessage = (error.value as {message: string}) || null; let errorWithMessage = (error as {message: string}) || null;
return errorWithMessage?.message || JSON.stringify(errorWithMessage) return errorWithMessage?.message || JSON.stringify(errorWithMessage)
}); });
</script> </script>
@ -23,14 +21,14 @@
<div class="loading-div" v-if="isLoading"> <div class="loading-div" v-if="isLoading">
<v-progress-circular indeterminate></v-progress-circular> <v-progress-circular indeterminate></v-progress-circular>
</div> </div>
<div v-if="error"> <div v-if="isError">
<v-empty-state <v-empty-state
icon="mdi-alert-circle-outline" icon="mdi-alert-circle-outline"
:text="errorMessage" :text="errorMessage"
:title="t('error_title')" :title="t('error_title')"
></v-empty-state> ></v-empty-state>
</div> </div>
<slot v-if="data" :data="data!"></slot> <slot v-if="isSuccess && data" :data="data"></slot>
</template> </template>
<style scoped> <style scoped>

View file

@ -1,41 +1,47 @@
import { apiConfig } from "@/config.ts";
import apiClient from "@/services/api-client/api-client.ts"; import apiClient from "@/services/api-client/api-client.ts";
import type {AxiosResponse, ResponseType} from "axios"; import type {AxiosResponse, ResponseType} from "axios";
import {HttpErrorResponseException} from "@/exception/http-error-response-exception.ts"; import {HttpErrorResponseException} from "@/exception/http-error-response-exception.ts";
export abstract class BaseController { export abstract class BaseController {
protected baseUrl: string; protected basePath: string;
protected constructor(basePath: string) { protected constructor(basePath: string) {
this.baseUrl = `${apiConfig.baseUrl}/${basePath}`; this.basePath = basePath;
} }
private assertSuccessResponse(response: AxiosResponse<unknown, unknown>) { private assertSuccessResponse(response: AxiosResponse<unknown, unknown>) {
if (response.status / 200 !== 2) { if (response.status / 100 !== 2) {
throw new HttpErrorResponseException(response); throw new HttpErrorResponseException(response);
} }
} }
private absolutePathFor(path: string) {
return "/" + this.basePath + path;
}
protected async get<T>(path: string, queryParams?: Record<string, any>, responseType?: ResponseType): Promise<T> { protected async get<T>(path: string, queryParams?: Record<string, any>, responseType?: ResponseType): Promise<T> {
let response = await apiClient.get<T>(path, {params: queryParams, responseType}); let response = await apiClient.get<T>(
this.absolutePathFor(path),
{params: queryParams, responseType}
);
this.assertSuccessResponse(response); this.assertSuccessResponse(response);
return response.data; return response.data;
} }
protected async post<T>(path: string, body: unknown): Promise<T> { protected async post<T>(path: string, body: unknown): Promise<T> {
let response = await apiClient.post<T>(path, body); let response = await apiClient.post<T>(this.absolutePathFor(path), body);
this.assertSuccessResponse(response); this.assertSuccessResponse(response);
return response.data; return response.data;
} }
protected async delete<T>(path: string): Promise<T> { protected async delete<T>(path: string): Promise<T> {
let response = await apiClient.delete<T>(path) let response = await apiClient.delete<T>(this.absolutePathFor(path))
this.assertSuccessResponse(response); this.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): Promise<T> {
let response = await apiClient.put<T>(path, body); let response = await apiClient.put<T>(this.absolutePathFor(path), body);
this.assertSuccessResponse(response); this.assertSuccessResponse(response);
return response.data; return response.data;
} }

View file

@ -8,10 +8,10 @@ export class LearningObjectController extends BaseController {
} }
async getMetadata(hruid: string, language: Language, version: number): Promise<LearningObject> { async getMetadata(hruid: string, language: Language, version: number): Promise<LearningObject> {
return this.get<LearningObject>(`/learningObject/${hruid}`, {language, version}); return this.get<LearningObject>(`/${hruid}`, {language, version});
} }
async getHTML(hruid: string, language: Language, version: number): Promise<Document> { async getHTML(hruid: string, language: Language, version: number): Promise<Document> {
return this.get<Document>(`/learningObject/${hruid}/html`, {language, version}, "document"); return this.get<Document>(`/${hruid}/html`, {language, version}, "document");
} }
} }

View file

@ -2,6 +2,7 @@ import {BaseController} from "@/controllers/base-controller.ts";
import {LearningPath} from "@/data-objects/learning-path.ts"; import {LearningPath} from "@/data-objects/learning-path.ts";
import type {LearningPathDTO} from "@/data-objects/learning-path.ts"; import type {LearningPathDTO} from "@/data-objects/learning-path.ts";
import type {Language} from "@/data-objects/language.ts"; import type {Language} from "@/data-objects/language.ts";
import {single} from "@/utils/response-assertions.ts";
export class LearningPathController extends BaseController { export class LearningPathController extends BaseController {
constructor() { constructor() {
@ -18,6 +19,6 @@ export class LearningPathController extends BaseController {
forGroup: options?.forGroup, forGroup: options?.forGroup,
forStudent: options?.forStudent forStudent: options?.forStudent
}); });
return dtos.map(dto => LearningPath.fromDTO(dto)) return LearningPath.fromDTO(single(dtos));
} }
} }

View file

@ -1,7 +1,9 @@
import type {AxiosResponse} from "axios"; import type {AxiosResponse} from "axios";
export class HttpErrorResponseException extends Error { export class HttpErrorResponseException extends Error {
public statusCode: number;
constructor(public response: AxiosResponse<unknown, unknown>) { constructor(public response: AxiosResponse<unknown, unknown>) {
super(response.statusText); super((response.data as {message: string})?.message || JSON.stringify(response.data));
this.statusCode = response.status;
} }
} }

View file

@ -47,10 +47,10 @@ export function useLearningObjectListForPathQuery(
let learningObjects = []; let learningObjects = [];
for (let node of toValue(learningPath).nodesAsList) { for (let node of toValue(learningPath).nodesAsList) {
learningObjects.push( learningObjects.push(
learningObjectController.getHTML(node.learningobjectHruid, node.language, node.version) learningObjectController.getMetadata(node.learningobjectHruid, node.language, node.version)
); );
} }
return learningObjects; return Promise.all(learningObjects);
}, },
enabled: () => Boolean(toValue(learningPath)), enabled: () => Boolean(toValue(learningPath)),
}); });

View file

@ -12,7 +12,6 @@ import UserClasses from "@/views/classes/UserClasses.vue";
import UserAssignments from "@/views/classes/UserAssignments.vue"; import UserAssignments from "@/views/classes/UserAssignments.vue";
import authState from "@/services/auth/auth-service.ts"; import authState from "@/services/auth/auth-service.ts";
import LearningPathPage from "@/views/learning-paths/LearningPathPage.vue"; import LearningPathPage from "@/views/learning-paths/LearningPathPage.vue";
import path from "path";
import LearningPathSearchPage from "@/views/learning-paths/LearningPathSearchPage.vue"; import LearningPathSearchPage from "@/views/learning-paths/LearningPathSearchPage.vue";
import UserHomePage from "@/views/homepage/UserHomePage.vue"; import UserHomePage from "@/views/homepage/UserHomePage.vue";
import SingleTheme from "@/views/SingleTheme.vue"; import SingleTheme from "@/views/SingleTheme.vue";
@ -109,16 +108,15 @@ const router = createRouter({
}, },
{ {
path: "/learningPath", path: "/learningPath",
component: MenuBar,
children: [ children: [
{ {
path: "/search", path: "search",
name: "LearningPathSearchPage", name: "LearningPathSearchPage",
component: LearningPathSearchPage, component: LearningPathSearchPage,
meta: { requiresAuth: true } meta: { requiresAuth: true }
}, },
{ {
path: "/:hruid/:language", path: ":hruid/:language",
name: "LearningPath", name: "LearningPath",
component: LearningPathPage, component: LearningPathPage,
props: true, props: true,

View file

@ -6,7 +6,7 @@ import UsingQueryResult from "@/components/UsingQueryResult.vue";
const props = defineProps<{hruid: string, language: Language, version: number}>() const props = defineProps<{hruid: string, language: Language, version: number}>()
const learningObjectHtmlQueryResult: UseQueryReturnType<Document, Error> = useLearningObjectHTMLQuery(props.hruid, props.language, props.version); const learningObjectHtmlQueryResult: UseQueryReturnType<Document, Error> = useLearningObjectHTMLQuery(() => props.hruid, () => props.language, () => props.version);
</script> </script>

View file

@ -27,10 +27,10 @@
const learningPathQueryResult = useGetLearningPathQuery(props.hruid, props.language, typedQuery.value); const learningPathQueryResult = useGetLearningPathQuery(props.hruid, props.language, typedQuery.value);
const learningObjectListQueryResult = useLearningObjectListForPathQuery(learningPathQueryResult.data.value); const learningObjectListQueryResult = useLearningObjectListForPathQuery(learningPathQueryResult.data);
const nodesList: ComputedRef<LearningPathNode[] | null> = computed(() => const nodesList: ComputedRef<LearningPathNode[] | null> = computed(() =>
(!learningPathQueryResult.isPending && !learningPathQueryResult.isError) ? learningPathQueryResult.data.value?.nodesAsList : null learningPathQueryResult.isSuccess ? learningPathQueryResult.data.value?.nodesAsList : null
); );
const currentNode = computed(() => { const currentNode = computed(() => {
@ -107,7 +107,6 @@
</script> </script>
<template> <template>
<v-main>
<using-query-result <using-query-result
:query-result="learningPathQueryResult" :query-result="learningPathQueryResult"
v-slot="learningPath: {data: LearningPath}" v-slot="learningPath: {data: LearningPath}"
@ -125,7 +124,6 @@
</template> </template>
</v-list-item> </v-list-item>
<v-divider></v-divider> <v-divider></v-divider>
<div v-if="props.learningObjectHruid"> <div v-if="props.learningObjectHruid">
<using-query-result <using-query-result
:query-result="learningObjectListQueryResult" :query-result="learningObjectListQueryResult"
@ -160,7 +158,6 @@
<learning-path-search-field></learning-path-search-field> <learning-path-search-field></learning-path-search-field>
</div> </div>
</div> </div>
<learning-object-view <learning-object-view
:hruid="currentNode.learningobjectHruid" :hruid="currentNode.learningobjectHruid"
:language="currentNode.language" :language="currentNode.language"
@ -186,7 +183,6 @@
</v-btn> </v-btn>
</div> </div>
</using-query-result> </using-query-result>
</v-main>
</template> </template>
<style scoped> <style scoped>