Добавление функции враппера для api объектов, доработка http объекта (#36)

This commit is contained in:
Nikolay
2020-12-28 14:05:21 +03:00
committed by GitHub
parent 793fa0583f
commit 9cb663aa65
7 changed files with 108 additions and 163 deletions

View File

@ -55,6 +55,7 @@
"class-methods-use-this": 0,
"react/prefer-stateless-function": 0,
"react/jsx-fragments": 0,
"react/jsx-key": "warn",
"react/no-array-index-key": 0,
"react/destructuring-assignment": 0,
"no-console": [
@ -95,6 +96,7 @@
"@typescript-eslint/no-namespace": "off",
"@typescript-eslint/explicit-module-boundary-types": "off",
"@typescript-eslint/no-unused-vars": ["error"],
"@typescript-eslint/no-explicit-any": "off",
"array-bracket-spacing": ["warn", "never"],
"block-spacing": ["warn", "never"],
"brace-style": ["warn", "1tbs", {"allowSingleLine": true}],

View File

@ -1,70 +0,0 @@
import {makeStorageApi} from './StorageApi';
type Region = {
name: string;
subject_number: number;
gibdd_codes: Array<number>;
};
type ResponseRegions = {
regions: Array<Region>;
}
const api = makeStorageApi<ResponseRegions>({
key: 'russian_regions',
hook: '26502372-6bc4-4cdf-bbcc-41b3b71cb386',
description: 'Регионы России',
service_name: 'geo_services',
});
export const regionsApi = {
request: async (): Promise<Region[]> => {
const {value: {regions}} = await api.request();
return regions;
},
find: async (name: string): Promise<Undefinable<Region>> => {
const regions = await regionsApi.request();
return regions.find(region => region.name === name);
},
create: async (newRegion: Region): Promise<Region> => {
const regions = await regionsApi.request();
const findedRegion = regions.find(region => region.name === newRegion.name);
if (findedRegion) {
throw new Error(`Город с именем "${newRegion.name}" уже существует`);
}
await api.update({
regions: [
...regions,
newRegion,
],
});
return newRegion;
},
update: async (updatedRegion: Region): Promise<Region> => {
const regions = await regionsApi.request();
const findedIndex = regions.findIndex(region => region.name === updatedRegion.name);
if (findedIndex === -1) {
throw new Error(`Город с именем "${updatedRegion.name}" не найден`);
}
await api.update({
regions: regions.map((region, index) => {
if (findedIndex === index) {
return updatedRegion;
}
return region;
}),
});
return updatedRegion;
},
remove: async (name: string): Promise<string> => {
const regions = await regionsApi.request();
const findedIndex = regions.findIndex(region => region.name === name);
if (findedIndex === -1) {
throw new Error(`Город с именем "${name}" не найден`);
}
await api.update({
regions: regions.filter(region => region.name === name),
});
return name;
}
};

View File

@ -1,44 +0,0 @@
import {http} from '../infrastructure/Http';
type QueryRequest = {
hook: string;
}
type ResponseData<T> = {
key: string;
value: T;
description: string;
service_name: string;
author: string;
};
type RequestData<T> = Omit<ResponseData<T>, 'author'>;
type ApiConfig<T> = Omit<ResponseData<T>, 'value' | 'author'> & {
hook: string;
};
type Api<T> = {
request: () => Promise<ResponseData<T>>;
update: (updateValue: T) => Promise<ResponseData<T>>;
};
const ROOT_URL = 'https://api.storage.vigdorov.ru/store';
export const makeStorageApi = <T>({key, hook, ...body}: ApiConfig<T>): Api<T> => {
const config = {params: {hook}};
return {
request: async (): Promise<ResponseData<T>> => {
const {data} = await http.get<QueryRequest, ResponseData<T>>(`${ROOT_URL}/${key}`, config);
return data;
},
update: async (updateValue: T): Promise<ResponseData<T>> => {
const {data} = await http.put<QueryRequest, RequestData<T>, ResponseData<T>>(ROOT_URL, {
...body,
key,
value: updateValue,
}, config);
return data;
},
};
};

View File

@ -0,0 +1,29 @@
import {http} from '../infrastructure/Http';
import {makeApi} from '../utils/makeApi';
type User = {
id: number;
avatar: string;
email: string;
first_name: string;
last_name: string;
};
type UserReponse = {
data: Array<User>;
page: number;
per_page: number;
support: {
text: string;
url: string;
}
total: number;
total_pages: number;
};
export const usersApi = makeApi({
request: async () => {
const {data} = await http.get<void, UserReponse>('https://reqres.in/api/users');
return data;
},
});

View File

@ -1,53 +1,51 @@
import axios, {AxiosRequestConfig, AxiosResponse} from 'axios';
import axios, {AxiosRequestConfig} from 'axios';
type RequestConfig<Q> = Omit<AxiosRequestConfig, 'params'> & {
params: Q;
enum Method {
Get = 'get',
Delete = 'delete',
Head = 'head',
Options = 'options',
Post = 'post',
Put = 'put',
Patch = 'patch',
}
type RequestConfig<Q, B> = Omit<AxiosRequestConfig, 'params' | 'data'> & {
params?: Q;
data?: B;
};
const requestMiddleware = async <Q, B, R>(config: RequestConfig<Q, B>): Promise<R> => {
const axiosResponse = await axios.request<R>(config);
// Добавить обработку ошибок
return axiosResponse.data;
};
const request = <Q, B, R>(config: RequestConfig<Q, B>) => requestMiddleware<Q, B, R>(config);
const requestWithoutBody = (method: Method) => <Q, R>(url: string, query?: Q) => {
return request<Q, never, R>({
method,
url,
params: query,
});
};
const requestWithBody = (method: Method) => <Q, B, R>(url: string, query?: Q, body?: B) => {
return request<Q, B, R>({
method,
url,
params: query,
data: body,
});
};
export const http = {
get: <Query, Response>(
url: string,
config: RequestConfig<Query>
): Promise<AxiosResponse<Response>> => {
return axios.get<Response>(url, config);
},
delete: <Query, Response>(
url: string,
config: RequestConfig<Query>
): Promise<AxiosResponse<Response>> => {
return axios.delete<Response>(url, config);
},
head: <Query, Response>(
url: string,
config: RequestConfig<Query>
): Promise<AxiosResponse<Response>> => {
return axios.head<Response>(url, config);
},
options: <Query, Response>(
url: string,
config: RequestConfig<Query>
): Promise<AxiosResponse<Response>> => {
return axios.options<Response>(url, config);
},
post: <Query, Body, Response>(
url: string,
body: Body,
config: RequestConfig<Query>
): Promise<AxiosResponse<Response>> => {
return axios.post<Response>(url, body, config);
},
put: <Query, Body, Response>(
url: string,
body: Body,
config: RequestConfig<Query>
): Promise<AxiosResponse<Response>> => {
return axios.post<Response>(url, body, config);
},
patch: <Query, Body, Response>(
url: string,
body: Body,
config: RequestConfig<Query>
): Promise<AxiosResponse<Response>> => {
return axios.post<Response>(url, body, config);
},
get: requestWithoutBody(Method.Get),
delete: requestWithoutBody(Method.Delete),
head: requestWithoutBody(Method.Head),
options: requestWithoutBody(Method.Options),
post: requestWithBody(Method.Post),
put: requestWithBody(Method.Put),
patch: requestWithBody(Method.Patch),
};

20
src/core/utils/makeApi.ts Normal file
View File

@ -0,0 +1,20 @@
import {fromPromise} from '@most/core';
import {Stream} from '@most/types';
import {objectEntries} from './objectEntries';
type PromiseApi = Record<string, (...args: any[]) => Promise<unknown>>;
type StreamApi<T extends PromiseApi> = {
[K in keyof T]: (...params: Parameters<T[K]>) => (
T[K] extends (...args: any[]) => Promise<infer R> ? Stream<R> : never
);
};
export const makeApi = <T extends PromiseApi>(apiObj: T) => {
return objectEntries(apiObj).reduce((streamObj, [apiKey, apiMethod]) => {
return {
...streamObj,
[apiKey]: (...args: Parameters<typeof apiMethod>) => fromPromise(apiMethod(...args)),
};
}, {} as StreamApi<T>);
};

View File

@ -1,8 +1,18 @@
import React, {memo} from 'react';
import {usersApi} from '../../../../core/api/usersTestApi';
import {useStream} from '../../../../core/utils/useStream';
const userList$ = usersApi.request();
const Page: React.FC = () => {
const users = useStream(userList$, []);
return (
<div>tags</div>
<div>
tags
{users.map(user => (
<div key={user.id}>{user.first_name}, {user.last_name}</div>
))}
</div>
);
};