Add api module
This commit is contained in:
parent
b8e39699bb
commit
90e227c794
|
|
@ -0,0 +1,88 @@
|
|||
import Query from 'qs'
|
||||
import * as R from 'ramda'
|
||||
import { sprintf } from 'sprintf-js'
|
||||
import request from './request'
|
||||
import HttpMethod from './HttpMethod'
|
||||
|
||||
class ApiMethodFactory {
|
||||
private readonly apiPrefix: string
|
||||
|
||||
constructor(apiPrefix: string) {
|
||||
this.apiPrefix = apiPrefix
|
||||
}
|
||||
|
||||
private makePath = <T>(data: T, pathKeys: string[]) => (template: string): string => {
|
||||
const prefixedTemplate = `${this.apiPrefix}${template}`
|
||||
|
||||
if (R.isEmpty(pathKeys)) {
|
||||
return prefixedTemplate
|
||||
}
|
||||
|
||||
const pathData = R.pick(pathKeys, data)
|
||||
|
||||
if (R.isEmpty(pathData)) {
|
||||
throw Error('api: empty path data')
|
||||
}
|
||||
|
||||
return sprintf(prefixedTemplate, pathData)
|
||||
}
|
||||
|
||||
private makeEndpoint = <T>(
|
||||
template: string,
|
||||
data: T,
|
||||
pathKeys: string[],
|
||||
queryKeys: string[],
|
||||
): string => {
|
||||
const make = R.compose(
|
||||
this.addQuery(data, queryKeys),
|
||||
this.makePath(data, pathKeys),
|
||||
)
|
||||
|
||||
return make(template)
|
||||
}
|
||||
|
||||
private addQuery = <T>(data: T, queryKeys: string[]) => (path: string): string => {
|
||||
if (R.isEmpty(queryKeys)) {
|
||||
return path
|
||||
}
|
||||
|
||||
const queryData = R.pick(queryKeys, data)
|
||||
|
||||
if (R.isEmpty(queryData)) {
|
||||
throw Error('api: empty query data')
|
||||
}
|
||||
|
||||
const query = Query.stringify(queryData)
|
||||
|
||||
return `${path}?${query}`
|
||||
}
|
||||
|
||||
make = <R, T = null>(
|
||||
template: string,
|
||||
method: HttpMethod = HttpMethod.GET, {
|
||||
path: pathKeys = [],
|
||||
query: queryKeys = [],
|
||||
}: { path?: string[], query?: string[] } = {},
|
||||
) => async (data: Nullable<T> = null): Promise<R> => {
|
||||
const getBody = R.pipe(
|
||||
R.ifElse(
|
||||
R.isNil,
|
||||
R.always(null),
|
||||
R.omit(R.concat(pathKeys, queryKeys)),
|
||||
),
|
||||
R.when(R.isEmpty, R.always(null)),
|
||||
R.unless(R.isNil, JSON.stringify),
|
||||
)
|
||||
|
||||
const body = getBody(data)
|
||||
const endpoint = this.makeEndpoint(template, data, pathKeys, queryKeys)
|
||||
|
||||
return await request({
|
||||
method: method,
|
||||
url: endpoint,
|
||||
data: body,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export default ApiMethodFactory
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
enum HttpMethod {
|
||||
GET = 'GET',
|
||||
POST = 'POST',
|
||||
PUT = 'PUT',
|
||||
PATCH = 'PATCH',
|
||||
DELETE = 'DELETE'
|
||||
}
|
||||
|
||||
export default HttpMethod
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
export interface ApiError {
|
||||
errorCode: number
|
||||
errorMessage: Nullable<string>
|
||||
}
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
export { default as HttpMethod } from './HttpMethod'
|
||||
export { default as ApiMethodFactory } from './ApiMethodFactory'
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
import axios, { AxiosRequestConfig } from 'axios'
|
||||
|
||||
import logger from 'lib/logger'
|
||||
|
||||
const retrieve = async (
|
||||
props: AxiosRequestConfig,
|
||||
hasRetriedAfterAuthentication = false,
|
||||
): Promise<any> => {
|
||||
try {
|
||||
const { data } = await axios(props)
|
||||
|
||||
return data
|
||||
} catch (err) {
|
||||
if (err?.hasAuthenticated && !hasRetriedAfterAuthentication) {
|
||||
return retrieve(props, true)
|
||||
}
|
||||
|
||||
throw new Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
const request = (props: AxiosRequestConfig, { throwOnError = true } = {}) => {
|
||||
logger.debug(props, `throwOnError: ${throwOnError}`)
|
||||
|
||||
return retrieve(props)
|
||||
}
|
||||
|
||||
export default request
|
||||
|
|
@ -0,0 +1 @@
|
|||
export { thread } from './thread'
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
import * as R from 'ramda'
|
||||
|
||||
type Transformer = (value: any) => any
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const pipe = R.pipe as unknown as (...fs: Transformer[]) => (value: any) => any
|
||||
|
||||
export const thread = (value: any, ...fs: Transformer[]) => {
|
||||
const transform = pipe(...fs)
|
||||
|
||||
return transform(value)
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
type Nullable<T> = T | null | undefined
|
||||
|
||||
type UnknownRecord = Record<string, unknown>
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
import loglevel from 'loglevel'
|
||||
|
||||
const logger = loglevel.getLogger('default')
|
||||
|
||||
logger.setLevel(process.env.NODE_ENV === 'production' ? 'WARN' : 'DEBUG')
|
||||
|
||||
export const pipelog = (...args: unknown[]) => (value: unknown) => {
|
||||
logger.debug(...args, value)
|
||||
|
||||
return value
|
||||
}
|
||||
|
||||
export default logger
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"name": "lib",
|
||||
"version": "0.1.0",
|
||||
"dependencies": {
|
||||
"@types/qs": "^6.9.7",
|
||||
"@types/ramda": "^0.27.44",
|
||||
"@types/sprintf-js": "^1.1.2",
|
||||
"axios": "^0.21.1",
|
||||
"loglevel": "^1.7.1",
|
||||
"qs": "^6.10.1",
|
||||
"ramda": "^0.27.1",
|
||||
"sprintf-js": "^1.1.2"
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue