feat(frontend): LearningObjectService en LearningPathService geïmplementeerd.

This commit is contained in:
Gerald Schmittinger 2025-03-23 08:56:34 +01:00
parent 8b0fc4263f
commit 3c3fddb7d0
24 changed files with 375 additions and 84 deletions

View file

@ -0,0 +1,10 @@
import axios from "axios";
import { apiConfig } from "@/config.ts";
const apiClient = axios.create({
baseURL: apiConfig.baseUrl,
headers: {
"Content-Type": "application/json",
},
});
export default apiClient;

View file

@ -0,0 +1,10 @@
import type {AxiosResponse} from "axios";
export class HttpErrorStatusException extends Error {
public readonly statusCode: number;
constructor(response: AxiosResponse<any, any>) {
super(`${response.statusText} (${response.status})`);
this.statusCode = response.status;
}
}

View file

@ -0,0 +1,10 @@
import {type Params, RestEndpoint} from "@/services/api-client/endpoints/rest-endpoint.ts";
import {RemoteResource} from "@/services/api-client/remote-resource.ts";
export class DeleteEndpoint<PP extends Params, QP extends Params, R> extends RestEndpoint<PP, QP, undefined, R> {
readonly method = "GET";
public delete(pathParams: PP, queryParams: QP): RemoteResource<R> {
return super.request(pathParams, queryParams, undefined);
}
}

View file

@ -0,0 +1,10 @@
import {type Params, RestEndpoint} from "@/services/api-client/endpoints/rest-endpoint.ts";
import {RemoteResource} from "@/services/api-client/remote-resource.ts";
export class GetEndpoint<PP extends Params, QP extends Params, R> extends RestEndpoint<PP, QP, undefined, R> {
readonly method = "GET";
public get(pathParams: PP, queryParams: QP): RemoteResource<R> {
return super.request(pathParams, queryParams, undefined);
}
}

View file

@ -0,0 +1,10 @@
import {type Params, RestEndpoint} from "@/services/api-client/endpoints/rest-endpoint.ts";
import {RemoteResource} from "@/services/api-client/remote-resource.ts";
export class PostEndpoint<PP extends Params, QP extends Params, B, R> extends RestEndpoint<PP, QP, B, R> {
readonly method = "POST";
public post(pathParams: PP, queryParams: QP, body: B): RemoteResource<R> {
return super.request(pathParams, queryParams, body);
}
}

View file

@ -0,0 +1,30 @@
import {RemoteResource} from "@/services/api-client/remote-resource.ts";
import apiClient from "@/services/api-client/api-client.ts";
import {HttpErrorStatusException} from "@/services/api-client/api-exceptions";
export abstract class RestEndpoint<PP extends Params, QP extends Params, B, R> {
public abstract readonly method: "GET" | "POST" | "PUT" | "DELETE";
constructor(public readonly url: string) {
}
protected request(pathParams: PP, queryParams: QP, body: B): RemoteResource<R> {
let urlFilledIn = this.url;
urlFilledIn.replace(/:(\w+)([/$])/g, (_, key, after) =>
(key in pathParams ? encodeURIComponent(pathParams[key]) : `:${key}`) + after
);
return new RemoteResource(async () => {
const response = await apiClient.request<R>({
url: urlFilledIn,
method: this.method,
params: queryParams,
data: body,
});
if (response.status / 100 !== 2) {
throw new HttpErrorStatusException(response);
}
return response.data;
});
}
}
export type Params = {[key: string]: string | number | boolean};

View file

@ -0,0 +1,70 @@
export class RemoteResource<T> {
static NOT_LOADED: NotLoadedState = {type: "notLoaded"};
static LOADING: LoadingState = {type: "loading"};
private state: NotLoadedState | LoadingState | ErrorState | SuccessState<T> = RemoteResource.NOT_LOADED;
constructor(private readonly requestFn: () => Promise<T>) {
}
public async request(): Promise<T | undefined> {
this.state = RemoteResource.LOADING;
try {
let resource = await this.requestFn();
this.state = {
type: "success",
data: resource
};
return resource;
} catch (e: any) {
this.state = {
type: "error",
errorCode: e.statusCode,
message: e.message,
error: e
};
}
}
public startRequestInBackground(): RemoteResource<T> {
this.request().then();
return this;
}
public get data(): T | undefined {
if (this.state.type === "success") {
return this.state.data;
}
}
public map<U>(mappingFn: (content: T) => U): RemoteResource<U> {
return new RemoteResource<U>(async () => {
await this.request();
if (this.state.type === "success") {
return mappingFn(this.state.data);
} else if (this.state.type === "error") {
throw this.state.error;
} else {
throw new Error("Fetched resource, but afterwards, it was neither in a success nor in an error state. " +
"This should never happen.");
}
});
}
}
type NotLoadedState = {
type: "notLoaded"
};
type LoadingState = {
type: "loading"
};
type ErrorState = {
type: "error",
errorCode?: number,
message?: string,
error: any
};
type SuccessState<T> = {
type: "success",
data: T
}