fix(backend): Diverse bugfixes omtrent LearningPathPage
This commit is contained in:
		
							parent
							
								
									27b9cdf833
								
							
						
					
					
						commit
						12b9f31f5f
					
				
					 9 changed files with 34 additions and 33 deletions
				
			
		|  | @ -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> | ||||||
|  |  | ||||||
|  | @ -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; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  | @ -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"); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -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)); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -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; | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -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)), | ||||||
|     }); |     }); | ||||||
|  |  | ||||||
|  | @ -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, | ||||||
|  |  | ||||||
|  | @ -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> | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -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> | ||||||
|  |  | ||||||
		Reference in a new issue
	
	 Gerald Schmittinger
						Gerald Schmittinger