first commit
This commit is contained in:
		
							
								
								
									
										25
									
								
								frontend/src/client/core/ApiError.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								frontend/src/client/core/ApiError.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,25 @@ | ||||
| import type { ApiRequestOptions } from "./ApiRequestOptions" | ||||
| import type { ApiResult } from "./ApiResult" | ||||
|  | ||||
| export class ApiError extends Error { | ||||
|   public readonly url: string | ||||
|   public readonly status: number | ||||
|   public readonly statusText: string | ||||
|   public readonly body: unknown | ||||
|   public readonly request: ApiRequestOptions | ||||
|  | ||||
|   constructor( | ||||
|     request: ApiRequestOptions, | ||||
|     response: ApiResult, | ||||
|     message: string, | ||||
|   ) { | ||||
|     super(message) | ||||
|  | ||||
|     this.name = "ApiError" | ||||
|     this.url = response.url | ||||
|     this.status = response.status | ||||
|     this.statusText = response.statusText | ||||
|     this.body = response.body | ||||
|     this.request = request | ||||
|   } | ||||
| } | ||||
							
								
								
									
										20
									
								
								frontend/src/client/core/ApiRequestOptions.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								frontend/src/client/core/ApiRequestOptions.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,20 @@ | ||||
| export type ApiRequestOptions = { | ||||
|   readonly method: | ||||
|     | "GET" | ||||
|     | "PUT" | ||||
|     | "POST" | ||||
|     | "DELETE" | ||||
|     | "OPTIONS" | ||||
|     | "HEAD" | ||||
|     | "PATCH" | ||||
|   readonly url: string | ||||
|   readonly path?: Record<string, unknown> | ||||
|   readonly cookies?: Record<string, unknown> | ||||
|   readonly headers?: Record<string, unknown> | ||||
|   readonly query?: Record<string, unknown> | ||||
|   readonly formData?: Record<string, unknown> | ||||
|   readonly body?: any | ||||
|   readonly mediaType?: string | ||||
|   readonly responseHeader?: string | ||||
|   readonly errors?: Record<number, string> | ||||
| } | ||||
							
								
								
									
										7
									
								
								frontend/src/client/core/ApiResult.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								frontend/src/client/core/ApiResult.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,7 @@ | ||||
| export type ApiResult<TData = any> = { | ||||
|   readonly body: TData | ||||
|   readonly ok: boolean | ||||
|   readonly status: number | ||||
|   readonly statusText: string | ||||
|   readonly url: string | ||||
| } | ||||
							
								
								
									
										126
									
								
								frontend/src/client/core/CancelablePromise.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										126
									
								
								frontend/src/client/core/CancelablePromise.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,126 @@ | ||||
| export class CancelError extends Error { | ||||
|   constructor(message: string) { | ||||
|     super(message) | ||||
|     this.name = "CancelError" | ||||
|   } | ||||
|  | ||||
|   public get isCancelled(): boolean { | ||||
|     return true | ||||
|   } | ||||
| } | ||||
|  | ||||
| export interface OnCancel { | ||||
|   readonly isResolved: boolean | ||||
|   readonly isRejected: boolean | ||||
|   readonly isCancelled: boolean | ||||
|  | ||||
|   (cancelHandler: () => void): void | ||||
| } | ||||
|  | ||||
| export class CancelablePromise<T> implements Promise<T> { | ||||
|   private _isResolved: boolean | ||||
|   private _isRejected: boolean | ||||
|   private _isCancelled: boolean | ||||
|   readonly cancelHandlers: (() => void)[] | ||||
|   readonly promise: Promise<T> | ||||
|   private _resolve?: (value: T | PromiseLike<T>) => void | ||||
|   private _reject?: (reason?: unknown) => void | ||||
|  | ||||
|   constructor( | ||||
|     executor: ( | ||||
|       resolve: (value: T | PromiseLike<T>) => void, | ||||
|       reject: (reason?: unknown) => void, | ||||
|       onCancel: OnCancel, | ||||
|     ) => void, | ||||
|   ) { | ||||
|     this._isResolved = false | ||||
|     this._isRejected = false | ||||
|     this._isCancelled = false | ||||
|     this.cancelHandlers = [] | ||||
|     this.promise = new Promise<T>((resolve, reject) => { | ||||
|       this._resolve = resolve | ||||
|       this._reject = reject | ||||
|  | ||||
|       const onResolve = (value: T | PromiseLike<T>): void => { | ||||
|         if (this._isResolved || this._isRejected || this._isCancelled) { | ||||
|           return | ||||
|         } | ||||
|         this._isResolved = true | ||||
|         if (this._resolve) this._resolve(value) | ||||
|       } | ||||
|  | ||||
|       const onReject = (reason?: unknown): void => { | ||||
|         if (this._isResolved || this._isRejected || this._isCancelled) { | ||||
|           return | ||||
|         } | ||||
|         this._isRejected = true | ||||
|         if (this._reject) this._reject(reason) | ||||
|       } | ||||
|  | ||||
|       const onCancel = (cancelHandler: () => void): void => { | ||||
|         if (this._isResolved || this._isRejected || this._isCancelled) { | ||||
|           return | ||||
|         } | ||||
|         this.cancelHandlers.push(cancelHandler) | ||||
|       } | ||||
|  | ||||
|       Object.defineProperty(onCancel, "isResolved", { | ||||
|         get: (): boolean => this._isResolved, | ||||
|       }) | ||||
|  | ||||
|       Object.defineProperty(onCancel, "isRejected", { | ||||
|         get: (): boolean => this._isRejected, | ||||
|       }) | ||||
|  | ||||
|       Object.defineProperty(onCancel, "isCancelled", { | ||||
|         get: (): boolean => this._isCancelled, | ||||
|       }) | ||||
|  | ||||
|       return executor(onResolve, onReject, onCancel as OnCancel) | ||||
|     }) | ||||
|   } | ||||
|  | ||||
|   get [Symbol.toStringTag]() { | ||||
|     return "Cancellable Promise" | ||||
|   } | ||||
|  | ||||
|   public then<TResult1 = T, TResult2 = never>( | ||||
|     onFulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | null, | ||||
|     onRejected?: ((reason: unknown) => TResult2 | PromiseLike<TResult2>) | null, | ||||
|   ): Promise<TResult1 | TResult2> { | ||||
|     return this.promise.then(onFulfilled, onRejected) | ||||
|   } | ||||
|  | ||||
|   public catch<TResult = never>( | ||||
|     onRejected?: ((reason: unknown) => TResult | PromiseLike<TResult>) | null, | ||||
|   ): Promise<T | TResult> { | ||||
|     return this.promise.catch(onRejected) | ||||
|   } | ||||
|  | ||||
|   public finally(onFinally?: (() => void) | null): Promise<T> { | ||||
|     return this.promise.finally(onFinally) | ||||
|   } | ||||
|  | ||||
|   public cancel(): void { | ||||
|     if (this._isResolved || this._isRejected || this._isCancelled) { | ||||
|       return | ||||
|     } | ||||
|     this._isCancelled = true | ||||
|     if (this.cancelHandlers.length) { | ||||
|       try { | ||||
|         for (const cancelHandler of this.cancelHandlers) { | ||||
|           cancelHandler() | ||||
|         } | ||||
|       } catch (error) { | ||||
|         console.warn("Cancellation threw an error", error) | ||||
|         return | ||||
|       } | ||||
|     } | ||||
|     this.cancelHandlers.length = 0 | ||||
|     if (this._reject) this._reject(new CancelError("Request aborted")) | ||||
|   } | ||||
|  | ||||
|   public get isCancelled(): boolean { | ||||
|     return this._isCancelled | ||||
|   } | ||||
| } | ||||
							
								
								
									
										57
									
								
								frontend/src/client/core/OpenAPI.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								frontend/src/client/core/OpenAPI.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,57 @@ | ||||
| import type { AxiosRequestConfig, AxiosResponse } from "axios" | ||||
| import type { ApiRequestOptions } from "./ApiRequestOptions" | ||||
| import type { TResult } from "./types" | ||||
|  | ||||
| type Headers = Record<string, string> | ||||
| type Middleware<T> = (value: T) => T | Promise<T> | ||||
| type Resolver<T> = (options: ApiRequestOptions) => Promise<T> | ||||
|  | ||||
| export class Interceptors<T> { | ||||
|   _fns: Middleware<T>[] | ||||
|  | ||||
|   constructor() { | ||||
|     this._fns = [] | ||||
|   } | ||||
|  | ||||
|   eject(fn: Middleware<T>) { | ||||
|     const index = this._fns.indexOf(fn) | ||||
|     if (index !== -1) { | ||||
|       this._fns = [...this._fns.slice(0, index), ...this._fns.slice(index + 1)] | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   use(fn: Middleware<T>) { | ||||
|     this._fns = [...this._fns, fn] | ||||
|   } | ||||
| } | ||||
|  | ||||
| export type OpenAPIConfig = { | ||||
|   BASE: string | ||||
|   CREDENTIALS: "include" | "omit" | "same-origin" | ||||
|   ENCODE_PATH?: ((path: string) => string) | undefined | ||||
|   HEADERS?: Headers | Resolver<Headers> | undefined | ||||
|   PASSWORD?: string | Resolver<string> | undefined | ||||
|   RESULT?: TResult | ||||
|   TOKEN?: string | Resolver<string> | undefined | ||||
|   USERNAME?: string | Resolver<string> | undefined | ||||
|   VERSION: string | ||||
|   WITH_CREDENTIALS: boolean | ||||
|   interceptors: { | ||||
|     request: Interceptors<AxiosRequestConfig> | ||||
|     response: Interceptors<AxiosResponse> | ||||
|   } | ||||
| } | ||||
|  | ||||
| export const OpenAPI: OpenAPIConfig = { | ||||
|   BASE: "", | ||||
|   CREDENTIALS: "include", | ||||
|   ENCODE_PATH: undefined, | ||||
|   HEADERS: undefined, | ||||
|   PASSWORD: undefined, | ||||
|   RESULT: "body", | ||||
|   TOKEN: undefined, | ||||
|   USERNAME: undefined, | ||||
|   VERSION: "0.1.0", | ||||
|   WITH_CREDENTIALS: false, | ||||
|   interceptors: { request: new Interceptors(), response: new Interceptors() }, | ||||
| } | ||||
							
								
								
									
										376
									
								
								frontend/src/client/core/request.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										376
									
								
								frontend/src/client/core/request.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,376 @@ | ||||
| import axios from "axios" | ||||
| import type { | ||||
|   AxiosError, | ||||
|   AxiosInstance, | ||||
|   AxiosRequestConfig, | ||||
|   AxiosResponse, | ||||
| } from "axios" | ||||
|  | ||||
| import { ApiError } from "./ApiError" | ||||
| import type { ApiRequestOptions } from "./ApiRequestOptions" | ||||
| import type { ApiResult } from "./ApiResult" | ||||
| import { CancelablePromise } from "./CancelablePromise" | ||||
| import type { OnCancel } from "./CancelablePromise" | ||||
| import type { OpenAPIConfig } from "./OpenAPI" | ||||
|  | ||||
| export const isString = (value: unknown): value is string => { | ||||
|   return typeof value === "string" | ||||
| } | ||||
|  | ||||
| export const isStringWithValue = (value: unknown): value is string => { | ||||
|   return isString(value) && value !== "" | ||||
| } | ||||
|  | ||||
| export const isBlob = (value: any): value is Blob => { | ||||
|   return value instanceof Blob | ||||
| } | ||||
|  | ||||
| export const isFormData = (value: unknown): value is FormData => { | ||||
|   return value instanceof FormData | ||||
| } | ||||
|  | ||||
| export const isSuccess = (status: number): boolean => { | ||||
|   return status >= 200 && status < 300 | ||||
| } | ||||
|  | ||||
| export const base64 = (str: string): string => { | ||||
|   try { | ||||
|     return btoa(str) | ||||
|   } catch (err) { | ||||
|     // @ts-ignore | ||||
|     return Buffer.from(str).toString("base64") | ||||
|   } | ||||
| } | ||||
|  | ||||
| export const getQueryString = (params: Record<string, unknown>): string => { | ||||
|   const qs: string[] = [] | ||||
|  | ||||
|   const append = (key: string, value: unknown) => { | ||||
|     qs.push(`${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`) | ||||
|   } | ||||
|  | ||||
|   const encodePair = (key: string, value: unknown) => { | ||||
|     if (value === undefined || value === null) { | ||||
|       return | ||||
|     } | ||||
|  | ||||
|     if (Array.isArray(value)) { | ||||
|       value.forEach((v) => encodePair(key, v)) | ||||
|     } else if (typeof value === "object") { | ||||
|       Object.entries(value).forEach(([k, v]) => encodePair(`${key}[${k}]`, v)) | ||||
|     } else { | ||||
|       append(key, value) | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   Object.entries(params).forEach(([key, value]) => encodePair(key, value)) | ||||
|  | ||||
|   return qs.length ? `?${qs.join("&")}` : "" | ||||
| } | ||||
|  | ||||
| const getUrl = (config: OpenAPIConfig, options: ApiRequestOptions): string => { | ||||
|   const encoder = config.ENCODE_PATH || encodeURI | ||||
|  | ||||
|   const path = options.url | ||||
|     .replace("{api-version}", config.VERSION) | ||||
|     .replace(/{(.*?)}/g, (substring: string, group: string) => { | ||||
|       if (options.path?.hasOwnProperty(group)) { | ||||
|         return encoder(String(options.path[group])) | ||||
|       } | ||||
|       return substring | ||||
|     }) | ||||
|  | ||||
|   const url = config.BASE + path | ||||
|   return options.query ? url + getQueryString(options.query) : url | ||||
| } | ||||
|  | ||||
| export const getFormData = ( | ||||
|   options: ApiRequestOptions, | ||||
| ): FormData | undefined => { | ||||
|   if (options.formData) { | ||||
|     const formData = new FormData() | ||||
|  | ||||
|     const process = (key: string, value: unknown) => { | ||||
|       if (isString(value) || isBlob(value)) { | ||||
|         formData.append(key, value) | ||||
|       } else { | ||||
|         formData.append(key, JSON.stringify(value)) | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     Object.entries(options.formData) | ||||
|       .filter(([, value]) => value !== undefined && value !== null) | ||||
|       .forEach(([key, value]) => { | ||||
|         if (Array.isArray(value)) { | ||||
|           value.forEach((v) => process(key, v)) | ||||
|         } else { | ||||
|           process(key, value) | ||||
|         } | ||||
|       }) | ||||
|  | ||||
|     return formData | ||||
|   } | ||||
|   return undefined | ||||
| } | ||||
|  | ||||
| type Resolver<T> = (options: ApiRequestOptions) => Promise<T> | ||||
|  | ||||
| export const resolve = async <T>( | ||||
|   options: ApiRequestOptions, | ||||
|   resolver?: T | Resolver<T>, | ||||
| ): Promise<T | undefined> => { | ||||
|   if (typeof resolver === "function") { | ||||
|     return (resolver as Resolver<T>)(options) | ||||
|   } | ||||
|   return resolver | ||||
| } | ||||
|  | ||||
| export const getHeaders = async ( | ||||
|   config: OpenAPIConfig, | ||||
|   options: ApiRequestOptions, | ||||
| ): Promise<Record<string, string>> => { | ||||
|   const [token, username, password, additionalHeaders] = await Promise.all([ | ||||
|     resolve(options, config.TOKEN), | ||||
|     resolve(options, config.USERNAME), | ||||
|     resolve(options, config.PASSWORD), | ||||
|     resolve(options, config.HEADERS), | ||||
|   ]) | ||||
|  | ||||
|   const headers = Object.entries({ | ||||
|     Accept: "application/json", | ||||
|     ...additionalHeaders, | ||||
|     ...options.headers, | ||||
|   }) | ||||
|     .filter(([, value]) => value !== undefined && value !== null) | ||||
|     .reduce( | ||||
|       (headers, [key, value]) => ({ | ||||
|         ...headers, | ||||
|         [key]: String(value), | ||||
|       }), | ||||
|       {} as Record<string, string>, | ||||
|     ) | ||||
|  | ||||
|   if (isStringWithValue(token)) { | ||||
|     headers.Authorization = `Bearer ${token}` | ||||
|   } | ||||
|  | ||||
|   if (isStringWithValue(username) && isStringWithValue(password)) { | ||||
|     const credentials = base64(`${username}:${password}`) | ||||
|     headers.Authorization = `Basic ${credentials}` | ||||
|   } | ||||
|  | ||||
|   if (options.body !== undefined) { | ||||
|     if (options.mediaType) { | ||||
|       headers["Content-Type"] = options.mediaType | ||||
|     } else if (isBlob(options.body)) { | ||||
|       headers["Content-Type"] = options.body.type || "application/octet-stream" | ||||
|     } else if (isString(options.body)) { | ||||
|       headers["Content-Type"] = "text/plain" | ||||
|     } else if (!isFormData(options.body)) { | ||||
|       headers["Content-Type"] = "application/json" | ||||
|     } | ||||
|   } else if (options.formData !== undefined) { | ||||
|     if (options.mediaType) { | ||||
|       headers["Content-Type"] = options.mediaType | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   return headers | ||||
| } | ||||
|  | ||||
| export const getRequestBody = (options: ApiRequestOptions): unknown => { | ||||
|   if (options.body) { | ||||
|     return options.body | ||||
|   } | ||||
|   return undefined | ||||
| } | ||||
|  | ||||
| export const sendRequest = async <T>( | ||||
|   config: OpenAPIConfig, | ||||
|   options: ApiRequestOptions, | ||||
|   url: string, | ||||
|   body: unknown, | ||||
|   formData: FormData | undefined, | ||||
|   headers: Record<string, string>, | ||||
|   onCancel: OnCancel, | ||||
|   axiosClient: AxiosInstance, | ||||
| ): Promise<AxiosResponse<T>> => { | ||||
|   const controller = new AbortController() | ||||
|  | ||||
|   let requestConfig: AxiosRequestConfig = { | ||||
|     data: body ?? formData, | ||||
|     headers, | ||||
|     method: options.method, | ||||
|     signal: controller.signal, | ||||
|     url, | ||||
|     withCredentials: config.WITH_CREDENTIALS, | ||||
|   } | ||||
|  | ||||
|   onCancel(() => controller.abort()) | ||||
|  | ||||
|   for (const fn of config.interceptors.request._fns) { | ||||
|     requestConfig = await fn(requestConfig) | ||||
|   } | ||||
|  | ||||
|   try { | ||||
|     return await axiosClient.request(requestConfig) | ||||
|   } catch (error) { | ||||
|     const axiosError = error as AxiosError<T> | ||||
|     if (axiosError.response) { | ||||
|       return axiosError.response | ||||
|     } | ||||
|     throw error | ||||
|   } | ||||
| } | ||||
|  | ||||
| export const getResponseHeader = ( | ||||
|   response: AxiosResponse<unknown>, | ||||
|   responseHeader?: string, | ||||
| ): string | undefined => { | ||||
|   if (responseHeader) { | ||||
|     const content = response.headers[responseHeader] | ||||
|     if (isString(content)) { | ||||
|       return content | ||||
|     } | ||||
|   } | ||||
|   return undefined | ||||
| } | ||||
|  | ||||
| export const getResponseBody = (response: AxiosResponse<unknown>): unknown => { | ||||
|   if (response.status !== 204) { | ||||
|     return response.data | ||||
|   } | ||||
|   return undefined | ||||
| } | ||||
|  | ||||
| export const catchErrorCodes = ( | ||||
|   options: ApiRequestOptions, | ||||
|   result: ApiResult, | ||||
| ): void => { | ||||
|   const errors: Record<number, string> = { | ||||
|     400: "Bad Request", | ||||
|     401: "Unauthorized", | ||||
|     402: "Payment Required", | ||||
|     403: "Forbidden", | ||||
|     404: "Not Found", | ||||
|     405: "Method Not Allowed", | ||||
|     406: "Not Acceptable", | ||||
|     407: "Proxy Authentication Required", | ||||
|     408: "Request Timeout", | ||||
|     409: "Conflict", | ||||
|     410: "Gone", | ||||
|     411: "Length Required", | ||||
|     412: "Precondition Failed", | ||||
|     413: "Payload Too Large", | ||||
|     414: "URI Too Long", | ||||
|     415: "Unsupported Media Type", | ||||
|     416: "Range Not Satisfiable", | ||||
|     417: "Expectation Failed", | ||||
|     418: "Im a teapot", | ||||
|     421: "Misdirected Request", | ||||
|     422: "Unprocessable Content", | ||||
|     423: "Locked", | ||||
|     424: "Failed Dependency", | ||||
|     425: "Too Early", | ||||
|     426: "Upgrade Required", | ||||
|     428: "Precondition Required", | ||||
|     429: "Too Many Requests", | ||||
|     431: "Request Header Fields Too Large", | ||||
|     451: "Unavailable For Legal Reasons", | ||||
|     500: "Internal Server Error", | ||||
|     501: "Not Implemented", | ||||
|     502: "Bad Gateway", | ||||
|     503: "Service Unavailable", | ||||
|     504: "Gateway Timeout", | ||||
|     505: "HTTP Version Not Supported", | ||||
|     506: "Variant Also Negotiates", | ||||
|     507: "Insufficient Storage", | ||||
|     508: "Loop Detected", | ||||
|     510: "Not Extended", | ||||
|     511: "Network Authentication Required", | ||||
|     ...options.errors, | ||||
|   } | ||||
|  | ||||
|   const error = errors[result.status] | ||||
|   if (error) { | ||||
|     throw new ApiError(options, result, error) | ||||
|   } | ||||
|  | ||||
|   if (!result.ok) { | ||||
|     const errorStatus = result.status ?? "unknown" | ||||
|     const errorStatusText = result.statusText ?? "unknown" | ||||
|     const errorBody = (() => { | ||||
|       try { | ||||
|         return JSON.stringify(result.body, null, 2) | ||||
|       } catch (e) { | ||||
|         return undefined | ||||
|       } | ||||
|     })() | ||||
|  | ||||
|     throw new ApiError( | ||||
|       options, | ||||
|       result, | ||||
|       `Generic Error: status: ${errorStatus}; status text: ${errorStatusText}; body: ${errorBody}`, | ||||
|     ) | ||||
|   } | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Request method | ||||
|  * @param config The OpenAPI configuration object | ||||
|  * @param options The request options from the service | ||||
|  * @param axiosClient The axios client instance to use | ||||
|  * @returns CancelablePromise<T> | ||||
|  * @throws ApiError | ||||
|  */ | ||||
| export const request = <T>( | ||||
|   config: OpenAPIConfig, | ||||
|   options: ApiRequestOptions, | ||||
|   axiosClient: AxiosInstance = axios, | ||||
| ): CancelablePromise<T> => { | ||||
|   return new CancelablePromise(async (resolve, reject, onCancel) => { | ||||
|     try { | ||||
|       const url = getUrl(config, options) | ||||
|       const formData = getFormData(options) | ||||
|       const body = getRequestBody(options) | ||||
|       const headers = await getHeaders(config, options) | ||||
|  | ||||
|       if (!onCancel.isCancelled) { | ||||
|         let response = await sendRequest<T>( | ||||
|           config, | ||||
|           options, | ||||
|           url, | ||||
|           body, | ||||
|           formData, | ||||
|           headers, | ||||
|           onCancel, | ||||
|           axiosClient, | ||||
|         ) | ||||
|  | ||||
|         for (const fn of config.interceptors.response._fns) { | ||||
|           response = await fn(response) | ||||
|         } | ||||
|  | ||||
|         const responseBody = getResponseBody(response) | ||||
|         const responseHeader = getResponseHeader( | ||||
|           response, | ||||
|           options.responseHeader, | ||||
|         ) | ||||
|  | ||||
|         const result: ApiResult = { | ||||
|           url, | ||||
|           ok: isSuccess(response.status), | ||||
|           status: response.status, | ||||
|           statusText: response.statusText, | ||||
|           body: responseHeader ?? responseBody, | ||||
|         } | ||||
|  | ||||
|         catchErrorCodes(options, result) | ||||
|  | ||||
|         resolve(result.body) | ||||
|       } | ||||
|     } catch (error) { | ||||
|       reject(error) | ||||
|     } | ||||
|   }) | ||||
| } | ||||
							
								
								
									
										14
									
								
								frontend/src/client/core/types.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								frontend/src/client/core/types.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | ||||
| import type { ApiResult } from "./ApiResult" | ||||
|  | ||||
| export type TResult = "body" | "raw" | ||||
|  | ||||
| export type TApiResponse<T extends TResult, TData> = Exclude< | ||||
|   T, | ||||
|   "raw" | ||||
| > extends never | ||||
|   ? ApiResult<TData> | ||||
|   : ApiResult<TData>["body"] | ||||
|  | ||||
| export type TConfig<T extends TResult> = { | ||||
|   _result?: T | ||||
| } | ||||
							
								
								
									
										8
									
								
								frontend/src/client/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								frontend/src/client/index.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | ||||
| export { ApiError } from "./core/ApiError" | ||||
| export { CancelablePromise, CancelError } from "./core/CancelablePromise" | ||||
| export { OpenAPI } from "./core/OpenAPI" | ||||
| export type { OpenAPIConfig } from "./core/OpenAPI" | ||||
|  | ||||
| export * from "./models" | ||||
| export * from "./schemas" | ||||
| export * from "./services" | ||||
							
								
								
									
										284
									
								
								frontend/src/client/models.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										284
									
								
								frontend/src/client/models.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,284 @@ | ||||
| export type Body_login_login_access_token = { | ||||
|   grant_type?: string | null | ||||
|   username: string | ||||
|   password: string | ||||
|   scope?: string | ||||
|   client_id?: string | null | ||||
|   client_secret?: string | null | ||||
| } | ||||
|  | ||||
| export type HTTPValidationError = { | ||||
|   detail?: Array<ValidationError> | ||||
| } | ||||
|  | ||||
| export type ItemCreate = { | ||||
|   title: string | ||||
|   description?: string | null | ||||
| } | ||||
|  | ||||
| export type ItemPublic = { | ||||
|   title: string | ||||
|   description?: string | null | ||||
|   id: string | ||||
|   owner_id: string | ||||
| } | ||||
|  | ||||
| export type ItemUpdate = { | ||||
|   title?: string | null | ||||
|   description?: string | null | ||||
| } | ||||
|  | ||||
| export type ItemsPublic = { | ||||
|   data: Array<ItemPublic> | ||||
|   count: number | ||||
| } | ||||
|  | ||||
| export type ClientMessagePublic = { | ||||
|   name: string, | ||||
|   phone: string, | ||||
|   email: string, | ||||
|   message: string, | ||||
|   id: string, | ||||
|   created_at: string | ||||
| } | ||||
|  | ||||
| export type ClientMessagesPublic = { | ||||
|   data: Array<ClientMessagePublic>, | ||||
|   count: number | ||||
| } | ||||
|  | ||||
| export type Message = { | ||||
|   message: string | ||||
| } | ||||
|  | ||||
| export type NewPassword = { | ||||
|   token: string | ||||
|   new_password: string | ||||
| } | ||||
|  | ||||
| export type Token = { | ||||
|   access_token: string | ||||
|   token_type?: string | ||||
| } | ||||
|  | ||||
| export type UpdatePassword = { | ||||
|   current_password: string | ||||
|   new_password: string | ||||
| } | ||||
|  | ||||
| export type UserCreate = { | ||||
|   email: string | ||||
|   is_active?: boolean | ||||
|   is_superuser?: boolean | ||||
|   full_name?: string | null | ||||
|   password: string | ||||
| } | ||||
|  | ||||
| export type UserPublic = { | ||||
|   email: string | ||||
|   is_active?: boolean | ||||
|   is_superuser?: boolean | ||||
|   full_name?: string | null | ||||
|   id: string | ||||
| } | ||||
|  | ||||
| export type UserRegister = { | ||||
|   email: string | ||||
|   password: string | ||||
|   full_name?: string | null | ||||
| } | ||||
|  | ||||
| export type UserUpdate = { | ||||
|   email?: string | null | ||||
|   is_active?: boolean | ||||
|   is_superuser?: boolean | ||||
|   full_name?: string | null | ||||
|   password?: string | null | ||||
| } | ||||
|  | ||||
| export type UserUpdateMe = { | ||||
|   full_name?: string | null | ||||
|   email?: string | null | ||||
| } | ||||
|  | ||||
| export type UsersPublic = { | ||||
|   data: Array<UserPublic> | ||||
|   count: number | ||||
| } | ||||
|  | ||||
| export type ValidationError = { | ||||
|   loc: Array<string | number> | ||||
|   msg: string | ||||
|   type: string | ||||
| } | ||||
|  | ||||
| export type WebSettingPublic = { | ||||
|   address: string, | ||||
|   google_map_api_key: string, | ||||
|   latitude: Number, | ||||
|   longitude: Number, | ||||
|   phone: string, | ||||
|   email: string, | ||||
|   facebook: string, | ||||
|   instagram: string, | ||||
|   youtube: string, | ||||
|   youtube_link: string, | ||||
|   whatsapp: string, | ||||
|   id: string | ||||
| } | ||||
|  | ||||
| export type WebSettingUpdate = { | ||||
|   address: string, | ||||
|   google_map_api_key: string, | ||||
|   latitude: Number, | ||||
|   longitude: Number, | ||||
|   phone: string, | ||||
|   email: string, | ||||
|   facebook: string, | ||||
|   instagram: string, | ||||
|   youtube: string, | ||||
|   youtube_link: string, | ||||
|   whatsapp: string, | ||||
| } | ||||
|  | ||||
|  | ||||
| export type AboutUssPublic = { | ||||
|   data: Array<AboutUsPublic> | ||||
|   count: number | ||||
| } | ||||
|  | ||||
| export type AboutUsPublic = { | ||||
|   index: number, | ||||
|   description: string, | ||||
|   image: string, | ||||
|   id: string | ||||
| } | ||||
|  | ||||
| export type AboutUsCreate = { | ||||
|   index: number, | ||||
|   description: string, | ||||
|   image: File, | ||||
| } | ||||
|  | ||||
| export type AboutUsUpdate = { | ||||
|   index: number, | ||||
|   description: string, | ||||
|   image?: File | undefined | null, | ||||
| } | ||||
|  | ||||
| export type CoursesPublic = { | ||||
|   data: Array<CoursePublic> | ||||
|   count: number | ||||
| } | ||||
|  | ||||
| export type CoursePublic = { | ||||
|   title: string, | ||||
|   sort_description: string, | ||||
|   long_description: string, | ||||
|   information: string, | ||||
|   contant: string, | ||||
|   remark: string, | ||||
|   id: string, | ||||
|   created_at: string | ||||
| } | ||||
|  | ||||
| export type CourseCreate = { | ||||
|   title: string, | ||||
|   sort_description: string, | ||||
|   long_description: string, | ||||
|   information: string, | ||||
|   contant: string, | ||||
|   remark: string, | ||||
| } | ||||
|  | ||||
| export type CourseDetailsPublic = { | ||||
|   title: string, | ||||
|   sort_description: string, | ||||
|   long_description: string, | ||||
|   information: string, | ||||
|   contant: string, | ||||
|   remark: string, | ||||
|   id: string, | ||||
|   created_at: string | ||||
|   images: Array<ImagePublic>, | ||||
|   info_images: Array<Info_imagePublic>, | ||||
|   schedule: Array<SchedulePublic> | ||||
| } | ||||
|  | ||||
| export type CourseUpdate = { | ||||
|   title: string, | ||||
|   sort_description: string, | ||||
|   long_description: string, | ||||
|   information: string, | ||||
|   contant: string, | ||||
|   remark: string, | ||||
| } | ||||
|  | ||||
| export type ImagesPublic = { | ||||
|   data: Array<ImagePublic>, | ||||
| } | ||||
|  | ||||
| export type ImagePublic = { | ||||
|   image: string, | ||||
|   course_id: string, | ||||
|   index: number, | ||||
|   id:string | ||||
| } | ||||
|  | ||||
| export type ImageUpdate = { | ||||
|   index: number, | ||||
| } | ||||
|  | ||||
| export type ImageCreate = { | ||||
|   image: File, | ||||
|   index: number, | ||||
|   course_id: string | ||||
| } | ||||
|  | ||||
| export type Info_imagesPublic = { | ||||
|   data: Array<Info_imagePublic>, | ||||
| } | ||||
|  | ||||
| export type Info_imagePublic = { | ||||
|   image: string, | ||||
|   course_id: string, | ||||
|   index: number | ||||
|   id: string | ||||
| } | ||||
|  | ||||
| export type Info_imageUpdate = { | ||||
|   index: number, | ||||
| } | ||||
|  | ||||
| export type Info_imagesCreate = { | ||||
|   image: File, | ||||
|   index: number, | ||||
|   course_id: string | ||||
| } | ||||
|  | ||||
| export type SchedulesPublic = { | ||||
|   data: Array<SchedulePublic>, | ||||
| } | ||||
|  | ||||
| export type SchedulePublic = { | ||||
|   title: string, | ||||
|   info1: string, | ||||
|   info2: string, | ||||
|   date: string, | ||||
|   course_id: string, | ||||
|   id:string | ||||
| } | ||||
| export type ScheduleCreate = { | ||||
|   title: string, | ||||
|   info1: string, | ||||
|   info2: string, | ||||
|   date: string, | ||||
|   course_id: string, | ||||
| } | ||||
|  | ||||
| export type ScheduleUpdate = { | ||||
|   title: string, | ||||
|   info1: string, | ||||
|   info2: string, | ||||
|   date: string, | ||||
| } | ||||
							
								
								
									
										444
									
								
								frontend/src/client/schemas.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										444
									
								
								frontend/src/client/schemas.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,444 @@ | ||||
| export const $Body_login_login_access_token = { | ||||
|   properties: { | ||||
|     grant_type: { | ||||
|       type: "any-of", | ||||
|       contains: [ | ||||
|         { | ||||
|           type: "string", | ||||
|           pattern: "password", | ||||
|         }, | ||||
|         { | ||||
|           type: "null", | ||||
|         }, | ||||
|       ], | ||||
|     }, | ||||
|     username: { | ||||
|       type: "string", | ||||
|       isRequired: true, | ||||
|     }, | ||||
|     password: { | ||||
|       type: "string", | ||||
|       isRequired: true, | ||||
|     }, | ||||
|     scope: { | ||||
|       type: "string", | ||||
|       default: "", | ||||
|     }, | ||||
|     client_id: { | ||||
|       type: "any-of", | ||||
|       contains: [ | ||||
|         { | ||||
|           type: "string", | ||||
|         }, | ||||
|         { | ||||
|           type: "null", | ||||
|         }, | ||||
|       ], | ||||
|     }, | ||||
|     client_secret: { | ||||
|       type: "any-of", | ||||
|       contains: [ | ||||
|         { | ||||
|           type: "string", | ||||
|         }, | ||||
|         { | ||||
|           type: "null", | ||||
|         }, | ||||
|       ], | ||||
|     }, | ||||
|   }, | ||||
| } as const | ||||
|  | ||||
| export const $HTTPValidationError = { | ||||
|   properties: { | ||||
|     detail: { | ||||
|       type: "array", | ||||
|       contains: { | ||||
|         type: "ValidationError", | ||||
|       }, | ||||
|     }, | ||||
|   }, | ||||
| } as const | ||||
|  | ||||
| export const $ItemCreate = { | ||||
|   properties: { | ||||
|     title: { | ||||
|       type: "string", | ||||
|       isRequired: true, | ||||
|       maxLength: 255, | ||||
|       minLength: 1, | ||||
|     }, | ||||
|     description: { | ||||
|       type: "any-of", | ||||
|       contains: [ | ||||
|         { | ||||
|           type: "string", | ||||
|           maxLength: 255, | ||||
|         }, | ||||
|         { | ||||
|           type: "null", | ||||
|         }, | ||||
|       ], | ||||
|     }, | ||||
|   }, | ||||
| } as const | ||||
|  | ||||
| export const $ItemPublic = { | ||||
|   properties: { | ||||
|     title: { | ||||
|       type: "string", | ||||
|       isRequired: true, | ||||
|       maxLength: 255, | ||||
|       minLength: 1, | ||||
|     }, | ||||
|     description: { | ||||
|       type: "any-of", | ||||
|       contains: [ | ||||
|         { | ||||
|           type: "string", | ||||
|           maxLength: 255, | ||||
|         }, | ||||
|         { | ||||
|           type: "null", | ||||
|         }, | ||||
|       ], | ||||
|     }, | ||||
|     id: { | ||||
|       type: "string", | ||||
|       isRequired: true, | ||||
|       format: "uuid", | ||||
|     }, | ||||
|     owner_id: { | ||||
|       type: "string", | ||||
|       isRequired: true, | ||||
|       format: "uuid", | ||||
|     }, | ||||
|   }, | ||||
| } as const | ||||
|  | ||||
| export const $ItemUpdate = { | ||||
|   properties: { | ||||
|     title: { | ||||
|       type: "any-of", | ||||
|       contains: [ | ||||
|         { | ||||
|           type: "string", | ||||
|           maxLength: 255, | ||||
|           minLength: 1, | ||||
|         }, | ||||
|         { | ||||
|           type: "null", | ||||
|         }, | ||||
|       ], | ||||
|     }, | ||||
|     description: { | ||||
|       type: "any-of", | ||||
|       contains: [ | ||||
|         { | ||||
|           type: "string", | ||||
|           maxLength: 255, | ||||
|         }, | ||||
|         { | ||||
|           type: "null", | ||||
|         }, | ||||
|       ], | ||||
|     }, | ||||
|   }, | ||||
| } as const | ||||
|  | ||||
| export const $ItemsPublic = { | ||||
|   properties: { | ||||
|     data: { | ||||
|       type: "array", | ||||
|       contains: { | ||||
|         type: "ItemPublic", | ||||
|       }, | ||||
|       isRequired: true, | ||||
|     }, | ||||
|     count: { | ||||
|       type: "number", | ||||
|       isRequired: true, | ||||
|     }, | ||||
|   }, | ||||
| } as const | ||||
|  | ||||
| export const $Message = { | ||||
|   properties: { | ||||
|     message: { | ||||
|       type: "string", | ||||
|       isRequired: true, | ||||
|     }, | ||||
|   }, | ||||
| } as const | ||||
|  | ||||
| export const $NewPassword = { | ||||
|   properties: { | ||||
|     token: { | ||||
|       type: "string", | ||||
|       isRequired: true, | ||||
|     }, | ||||
|     new_password: { | ||||
|       type: "string", | ||||
|       isRequired: true, | ||||
|       maxLength: 40, | ||||
|       minLength: 8, | ||||
|     }, | ||||
|   }, | ||||
| } as const | ||||
|  | ||||
| export const $Token = { | ||||
|   properties: { | ||||
|     access_token: { | ||||
|       type: "string", | ||||
|       isRequired: true, | ||||
|     }, | ||||
|     token_type: { | ||||
|       type: "string", | ||||
|       default: "bearer", | ||||
|     }, | ||||
|   }, | ||||
| } as const | ||||
|  | ||||
| export const $UpdatePassword = { | ||||
|   properties: { | ||||
|     current_password: { | ||||
|       type: "string", | ||||
|       isRequired: true, | ||||
|       maxLength: 40, | ||||
|       minLength: 8, | ||||
|     }, | ||||
|     new_password: { | ||||
|       type: "string", | ||||
|       isRequired: true, | ||||
|       maxLength: 40, | ||||
|       minLength: 8, | ||||
|     }, | ||||
|   }, | ||||
| } as const | ||||
|  | ||||
| export const $UserCreate = { | ||||
|   properties: { | ||||
|     email: { | ||||
|       type: "string", | ||||
|       isRequired: true, | ||||
|       format: "email", | ||||
|       maxLength: 255, | ||||
|     }, | ||||
|     is_active: { | ||||
|       type: "boolean", | ||||
|       default: true, | ||||
|     }, | ||||
|     is_superuser: { | ||||
|       type: "boolean", | ||||
|       default: false, | ||||
|     }, | ||||
|     full_name: { | ||||
|       type: "any-of", | ||||
|       contains: [ | ||||
|         { | ||||
|           type: "string", | ||||
|           maxLength: 255, | ||||
|         }, | ||||
|         { | ||||
|           type: "null", | ||||
|         }, | ||||
|       ], | ||||
|     }, | ||||
|     password: { | ||||
|       type: "string", | ||||
|       isRequired: true, | ||||
|       maxLength: 40, | ||||
|       minLength: 8, | ||||
|     }, | ||||
|   }, | ||||
| } as const | ||||
|  | ||||
| export const $UserPublic = { | ||||
|   properties: { | ||||
|     email: { | ||||
|       type: "string", | ||||
|       isRequired: true, | ||||
|       format: "email", | ||||
|       maxLength: 255, | ||||
|     }, | ||||
|     is_active: { | ||||
|       type: "boolean", | ||||
|       default: true, | ||||
|     }, | ||||
|     is_superuser: { | ||||
|       type: "boolean", | ||||
|       default: false, | ||||
|     }, | ||||
|     full_name: { | ||||
|       type: "any-of", | ||||
|       contains: [ | ||||
|         { | ||||
|           type: "string", | ||||
|           maxLength: 255, | ||||
|         }, | ||||
|         { | ||||
|           type: "null", | ||||
|         }, | ||||
|       ], | ||||
|     }, | ||||
|     id: { | ||||
|       type: "string", | ||||
|       isRequired: true, | ||||
|       format: "uuid", | ||||
|     }, | ||||
|   }, | ||||
| } as const | ||||
|  | ||||
| export const $UserRegister = { | ||||
|   properties: { | ||||
|     email: { | ||||
|       type: "string", | ||||
|       isRequired: true, | ||||
|       format: "email", | ||||
|       maxLength: 255, | ||||
|     }, | ||||
|     password: { | ||||
|       type: "string", | ||||
|       isRequired: true, | ||||
|       maxLength: 40, | ||||
|       minLength: 8, | ||||
|     }, | ||||
|     full_name: { | ||||
|       type: "any-of", | ||||
|       contains: [ | ||||
|         { | ||||
|           type: "string", | ||||
|           maxLength: 255, | ||||
|         }, | ||||
|         { | ||||
|           type: "null", | ||||
|         }, | ||||
|       ], | ||||
|     }, | ||||
|   }, | ||||
| } as const | ||||
|  | ||||
| export const $UserUpdate = { | ||||
|   properties: { | ||||
|     email: { | ||||
|       type: "any-of", | ||||
|       contains: [ | ||||
|         { | ||||
|           type: "string", | ||||
|           format: "email", | ||||
|           maxLength: 255, | ||||
|         }, | ||||
|         { | ||||
|           type: "null", | ||||
|         }, | ||||
|       ], | ||||
|     }, | ||||
|     is_active: { | ||||
|       type: "boolean", | ||||
|       default: true, | ||||
|     }, | ||||
|     is_superuser: { | ||||
|       type: "boolean", | ||||
|       default: false, | ||||
|     }, | ||||
|     full_name: { | ||||
|       type: "any-of", | ||||
|       contains: [ | ||||
|         { | ||||
|           type: "string", | ||||
|           maxLength: 255, | ||||
|         }, | ||||
|         { | ||||
|           type: "null", | ||||
|         }, | ||||
|       ], | ||||
|     }, | ||||
|     password: { | ||||
|       type: "any-of", | ||||
|       contains: [ | ||||
|         { | ||||
|           type: "string", | ||||
|           maxLength: 40, | ||||
|           minLength: 8, | ||||
|         }, | ||||
|         { | ||||
|           type: "null", | ||||
|         }, | ||||
|       ], | ||||
|     }, | ||||
|   }, | ||||
| } as const | ||||
|  | ||||
| export const $UserUpdateMe = { | ||||
|   properties: { | ||||
|     full_name: { | ||||
|       type: "any-of", | ||||
|       contains: [ | ||||
|         { | ||||
|           type: "string", | ||||
|           maxLength: 255, | ||||
|         }, | ||||
|         { | ||||
|           type: "null", | ||||
|         }, | ||||
|       ], | ||||
|     }, | ||||
|     email: { | ||||
|       type: "any-of", | ||||
|       contains: [ | ||||
|         { | ||||
|           type: "string", | ||||
|           format: "email", | ||||
|           maxLength: 255, | ||||
|         }, | ||||
|         { | ||||
|           type: "null", | ||||
|         }, | ||||
|       ], | ||||
|     }, | ||||
|   }, | ||||
| } as const | ||||
|  | ||||
| export const $UsersPublic = { | ||||
|   properties: { | ||||
|     data: { | ||||
|       type: "array", | ||||
|       contains: { | ||||
|         type: "UserPublic", | ||||
|       }, | ||||
|       isRequired: true, | ||||
|     }, | ||||
|     count: { | ||||
|       type: "number", | ||||
|       isRequired: true, | ||||
|     }, | ||||
|   }, | ||||
| } as const | ||||
|  | ||||
| export const $ValidationError = { | ||||
|   properties: { | ||||
|     loc: { | ||||
|       type: "array", | ||||
|       contains: { | ||||
|         type: "any-of", | ||||
|         contains: [ | ||||
|           { | ||||
|             type: "string", | ||||
|           }, | ||||
|           { | ||||
|             type: "number", | ||||
|           }, | ||||
|         ], | ||||
|       }, | ||||
|       isRequired: true, | ||||
|     }, | ||||
|     msg: { | ||||
|       type: "string", | ||||
|       isRequired: true, | ||||
|     }, | ||||
|     type: { | ||||
|       type: "string", | ||||
|       isRequired: true, | ||||
|     }, | ||||
|   }, | ||||
| } as const | ||||
							
								
								
									
										1045
									
								
								frontend/src/client/services.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1045
									
								
								frontend/src/client/services.ts
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
		Reference in New Issue
	
	Block a user