import { Scope } from "../../types"; export type body = Record; export type fetchOption = Record; export type Params = Record; export interface PaginationResponse { pagination: { more: boolean; items_per_page: number; }; results: T[]; count: number; } export type FetchParams = Record; export interface TransportExceptionInterface { name: string; } export interface ValidationExceptionInterface extends TransportExceptionInterface { name: "ValidationException"; error: object; violations: string[]; titles: string[]; propertyPaths: string[]; } export interface ValidationErrorResponse extends TransportExceptionInterface { violations: { title: string; propertyPath: string; }[]; } export interface AccessExceptionInterface extends TransportExceptionInterface { name: "AccessException"; violations: string[]; } export interface NotFoundExceptionInterface extends TransportExceptionInterface { name: "NotFoundException"; } export interface ServerExceptionInterface extends TransportExceptionInterface { name: "ServerException"; message: string; code: number; body: string; } export interface ConflictHttpExceptionInterface extends TransportExceptionInterface { name: "ConflictHttpException"; violations: string[]; } /** * Generic api method that can be adapted to any fetch request * * This method is suitable make a single fetch. When performing a GET to fetch a list of elements, always consider pagination * and use of the @link{fetchResults} method. */ export const makeFetch = ( method: "POST" | "GET" | "PUT" | "PATCH" | "DELETE", url: string, body?: body | Input | null, options?: FetchParams, ): Promise => { let opts = { method: method, headers: { "Content-Type": "application/json;charset=utf-8", }, }; if (body !== null && typeof body !== "undefined") { Object.assign(opts, { body: JSON.stringify(body) }); } if (typeof options !== "undefined") { opts = Object.assign(opts, options); } return fetch(url, opts).then((response) => { if (response.status === 204) { return Promise.resolve(); } if (response.ok) { return response.json(); } if (response.status === 422) { return response.json().then((response) => { throw ValidationException(response); }); } if (response.status === 403) { throw AccessException(response); } if (response.status === 409) { throw ConflictHttpException(response); } throw { name: "Exception", sta: response.status, txt: response.statusText, err: new Error(), violations: response.body, }; }); }; /** * Fetch results with certain parameters */ function _fetchAction( page: number, uri: string, params?: FetchParams, ): Promise> { const item_per_page = 50; const searchParams = new URLSearchParams(); searchParams.append("item_per_page", item_per_page.toString()); searchParams.append("page", page.toString()); if (params !== undefined) { Object.keys(params).forEach((key) => { const v = params[key]; if (typeof v === "string") { searchParams.append(key, v); } else if (typeof v === "number") { searchParams.append(key, v.toString()); } else if (v === null) { searchParams.append(key, ""); } }); } const url = uri + "?" + searchParams.toString(); return fetch(url, { method: "GET", headers: { "Content-Type": "application/json;charset=utf-8", }, }) .then((response) => { if (response.ok) { return response.json(); } if (response.status === 404) { throw NotFoundException(response); } if (response.status === 422) { return response.json().then((response) => { throw ValidationException(response); }); } if (response.status === 403) { throw AccessException(response); } if (response.status >= 500) { return response.text().then((body) => { throw ServerException(response.status, body); }); } throw new Error("other network error"); }) .catch( ( reason: | NotFoundExceptionInterface | ServerExceptionInterface | ValidationExceptionInterface | TransportExceptionInterface, ) => { console.error(reason); throw reason; }, ); } export const fetchResults = async ( uri: string, params?: FetchParams, ): Promise => { const promises: Promise[] = []; let page = 1; const firstData: PaginationResponse = (await _fetchAction( page, uri, params, )) as PaginationResponse; promises.push(Promise.resolve(firstData.results)); if (firstData.pagination.more) { do { page = ++page; promises.push( _fetchAction(page, uri, params).then((r) => Promise.resolve(r.results), ), ); } while (page * firstData.pagination.items_per_page < firstData.count); } return Promise.all(promises).then((values) => values.flat()); }; export const fetchScopes = (): Promise => { return fetchResults("/api/1.0/main/scope.json"); }; /** * Error objects to be thrown */ const ValidationException = ( response: ValidationErrorResponse, ): ValidationExceptionInterface => { const error = {} as ValidationExceptionInterface; error.name = "ValidationException"; error.violations = response.violations.map( (violation) => `${violation.title}: ${violation.propertyPath}`, ); error.titles = response.violations.map((violation) => violation.title); error.propertyPaths = response.violations.map( (violation) => violation.propertyPath, ); return error; }; // eslint-disable-next-line @typescript-eslint/no-unused-vars const AccessException = (response: Response): AccessExceptionInterface => { const error = {} as AccessExceptionInterface; error.name = "AccessException"; error.violations = ["You are not allowed to perform this action"]; return error; }; // eslint-disable-next-line @typescript-eslint/no-unused-vars const NotFoundException = (response: Response): NotFoundExceptionInterface => { const error = {} as NotFoundExceptionInterface; error.name = "NotFoundException"; return error; }; const ServerException = ( code: number, body: string, ): ServerExceptionInterface => { const error = {} as ServerExceptionInterface; error.name = "ServerException"; error.code = code; error.body = body; return error; }; const ConflictHttpException = ( // eslint-disable-next-line @typescript-eslint/no-unused-vars response: Response, ): ConflictHttpExceptionInterface => { const error = {} as ConflictHttpExceptionInterface; error.name = "ConflictHttpException"; error.violations = [ "Sorry, but someone else has already changed this entity. Please refresh the page and apply the changes again", ]; return error; };