Skip to content

Commit d773986

Browse files
authored
Merge pull request #1208 from merico-dev/table-1200-settings-form-api-client
refactor: share api client between dashboard & settings-form
2 parents 07e40d9 + a0845e3 commit d773986

File tree

19 files changed

+268
-208
lines changed

19 files changed

+268
-208
lines changed
Lines changed: 24 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -1,121 +1,47 @@
1-
import axios, { AxiosRequestConfig, Method } from 'axios';
21
import { DataSourceType } from '~/model';
32
import { AnyObject, IDashboardConfig } from '..';
4-
import { cryptSign } from './utils';
3+
import { DefaultApiClient, IAPIClient } from '../shared';
4+
import { Method } from 'axios';
55

6+
export { FacadeApiClient, DefaultApiClient } from '../shared';
7+
export type { IAPIClient, IAPIClientRequestOptions } from '../shared';
68
export type TQueryPayload = {
79
type: DataSourceType;
810
key: string;
911
query: string;
1012
env?: AnyObject;
1113
};
1214

13-
export interface IAPIClientRequestOptions {
14-
string?: boolean;
15-
params?: AnyObject;
16-
headers?: AnyObject;
17-
}
18-
19-
export interface IAPIClient {
20-
getRequest: <T = $TSFixMe>(
21-
method: Method,
22-
signal?: AbortSignal,
23-
) => (url: string, data: AnyObject, options?: IAPIClientRequestOptions) => Promise<T>;
15+
export interface IDashboardAPIClient extends IAPIClient {
2416
query: <T = $TSFixMe>(signal?: AbortSignal) => (data: TQueryPayload, options?: AnyObject) => Promise<T>;
2517
}
2618

27-
export class DefaultApiClient implements IAPIClient {
28-
baseURL: string;
29-
app_id: string;
30-
app_secret: string;
31-
makeQueryENV: (() => AnyObject) | null;
32-
33-
constructor() {
34-
this.baseURL = 'http://localhost:31200';
35-
this.app_id = '';
36-
this.app_secret = '';
37-
this.makeQueryENV = null;
38-
}
39-
40-
getAuthentication(params: Record<string, unknown>) {
41-
if (!this.app_id || !this.app_secret) {
42-
return undefined;
43-
}
44-
const nonce_str = new Date().getTime().toString();
45-
return {
46-
app_id: this.app_id,
47-
nonce_str,
48-
sign: cryptSign(
49-
{
50-
app_id: this.app_id,
51-
nonce_str,
52-
...params,
53-
},
54-
this.app_secret,
55-
),
56-
};
57-
}
58-
59-
getRequest(method: Method, signal?: AbortSignal) {
60-
return (url: string, data: AnyObject, options: IAPIClientRequestOptions = {}) => {
61-
const headers = this.buildHeader(options);
62-
const conf = this.buildAxiosConfig(method, url, data, options, headers, signal);
19+
export class DashboardApiClient extends DefaultApiClient implements IDashboardAPIClient {
20+
makeQueryENV?: (() => AnyObject) | null = null;
6321

64-
return axios(conf)
65-
.then((res) => {
66-
return res.data;
67-
})
68-
.catch((err: Error) => {
69-
return Promise.reject(err);
70-
});
22+
query<T>(signal: AbortSignal | undefined): (data: TQueryPayload, options?: AnyObject) => Promise<T> {
23+
return async (data: TQueryPayload, options: AnyObject = {}) => {
24+
if (!data.env) {
25+
data.env = this.makeQueryENV?.() ?? { error: 'failed to run makeQueryENV' };
26+
}
27+
return this.getRequest<T>('POST', signal)('/query', data, options);
7128
};
7229
}
30+
}
7331

74-
buildAxiosConfig(
75-
method: Method,
76-
url: string,
77-
data: AnyObject,
78-
options: IAPIClientRequestOptions,
79-
headers: AnyObject,
80-
signal: AbortSignal | undefined,
81-
) {
82-
const conf: AxiosRequestConfig = {
83-
baseURL: this.baseURL,
84-
method,
85-
url,
86-
params: method === 'GET' ? data : options.params,
87-
headers,
88-
signal,
89-
};
32+
export class DashboardApiFacadeClient implements IDashboardAPIClient {
33+
constructor(public implementation: IDashboardAPIClient) {}
9034

91-
if (['POST', 'PUT'].includes(method)) {
92-
conf.data = options.string ? JSON.stringify(data) : data;
93-
conf.data.authentication = this.getAuthentication(conf.data);
94-
}
95-
return conf;
35+
query<T>(signal?: AbortSignal) {
36+
return this.implementation.query<T>(signal);
9637
}
9738

98-
buildHeader(options: IAPIClientRequestOptions): AnyObject {
99-
const token = window.localStorage.getItem('token');
100-
return {
101-
'X-Requested-With': 'XMLHttpRequest',
102-
'Content-Type': options.string ? 'application/x-www-form-urlencoded' : 'application/json',
103-
authorization: token ? `bearer ${token}` : '',
104-
...options.headers,
105-
};
106-
}
107-
108-
query(signal?: AbortSignal) {
109-
return async (data: TQueryPayload, options: AnyObject = {}) => {
110-
if (!data.env) {
111-
data.env = this.makeQueryENV?.() ?? { error: 'failed to run makeQueryENV' };
112-
}
113-
return this.getRequest('POST', signal)('/query', data, options);
114-
};
39+
getRequest<T>(method: Method, signal?: AbortSignal) {
40+
return this.implementation.getRequest<T>(method, signal);
11541
}
11642
}
11743

118-
const Default = new DefaultApiClient();
44+
const Default = new DashboardApiClient();
11945

12046
export function configureAPIClient(config: IDashboardConfig) {
12147
if (Default.baseURL !== config.apiBaseURL) {
@@ -127,29 +53,15 @@ export function configureAPIClient(config: IDashboardConfig) {
12753
if (config.app_secret) {
12854
Default.app_secret = config.app_secret;
12955
}
56+
13057
if (config.makeQueryENV) {
13158
Default.makeQueryENV = config.makeQueryENV;
13259
}
13360
}
13461

135-
export class FacadeApiClient implements IAPIClient {
136-
implementation: IAPIClient = Default;
137-
138-
getRequest<T>(
139-
method: Method,
140-
signal?: AbortSignal,
141-
): (url: string, data: AnyObject, options?: IAPIClientRequestOptions) => Promise<T> {
142-
return this.implementation.getRequest(method, signal);
143-
}
144-
145-
query<T>(signal?: AbortSignal): (data: TQueryPayload, options?: AnyObject) => Promise<T> {
146-
return this.implementation.query(signal);
147-
}
148-
}
149-
15062
/**
15163
* @example facadeApiClient.implementation = new MyAPIClient();
15264
*/
153-
export const facadeApiClient = new FacadeApiClient();
65+
export const facadeApiClient = new DashboardApiFacadeClient(Default);
15466

155-
export const APIClient: IAPIClient = facadeApiClient;
67+
export const APIClient: IDashboardAPIClient = facadeApiClient;

dashboard/src/shared

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../../shared/src

settings-form/src/account/index.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@ export * from './add-account';
22
export * from './account-list';
33
export * from './delete-account';
44
export * from './login';
5+
export type { IStyles } from './styles';
Lines changed: 13 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -1,105 +1,25 @@
1-
import axios, { Method } from 'axios';
2-
import CryptoJS from 'crypto-js';
3-
import _ from 'lodash';
1+
import { DefaultApiClient, FacadeApiClient, IAPIClient } from '../shared';
42

5-
function marshall(params: Record<string, $TSFixMe>) {
6-
params = params || {};
7-
const keys = Object.keys(params).sort();
8-
const kvs = [];
9-
for (let i = 0; i < keys.length; i++) {
10-
const k = keys[i];
11-
if (k != 'authentication' && params[k]) {
12-
kvs.push(keys[i] + '=' + (typeof params[k] == 'object' ? JSON.stringify(params[k]) : params[k]));
13-
} else {
14-
const authKeys = Object.keys(params[k]).sort();
15-
for (let j = 0; j < authKeys.length; j++) {
16-
const ak = authKeys[j];
17-
if (ak != 'sign' && params[k][ak]) {
18-
kvs.push(
19-
authKeys[j] + '=' + (typeof params[k][ak] == 'object' ? JSON.stringify(params[k][ak]) : params[k][ak]),
20-
);
21-
}
22-
}
23-
}
24-
}
25-
return kvs.sort().join('&');
26-
}
27-
28-
function cryptSign(params: Record<string, $TSFixMe>, appsecret: string) {
29-
let temp = marshall(params);
30-
temp += '&key=' + appsecret;
31-
const sign = CryptoJS.MD5(temp).toString();
32-
return sign.toUpperCase();
33-
}
34-
35-
export const APIClient = {
36-
baseURL: 'http://localhost:31200',
37-
app_id: '',
38-
app_secret: '',
39-
getAuthentication(params: Record<string, $TSFixMe>) {
40-
if (!this.app_id || !this.app_secret) {
41-
return undefined;
42-
}
43-
const nonce_str = new Date().getTime().toString();
44-
return {
45-
app_id: this.app_id,
46-
nonce_str,
47-
sign: cryptSign(
48-
{
49-
app_id: this.app_id,
50-
nonce_str,
51-
...params,
52-
},
53-
this.app_secret,
54-
),
55-
};
56-
},
57-
getRequest(method: Method, signal?: AbortSignal) {
58-
return (url: string, data: $TSFixMe, options: $TSFixMe = {}) => {
59-
const token = window.localStorage.getItem('token');
60-
const headers = {
61-
'X-Requested-With': 'XMLHttpRequest',
62-
'Content-Type': options.string ? 'application/x-www-form-urlencoded' : 'application/json',
63-
authorization: token ? `bearer ${token}` : '',
64-
...options.headers,
65-
};
3+
export { FacadeApiClient, DefaultApiClient } from '../shared';
4+
export type { IAPIClient, IAPIClientRequestOptions } from '../shared';
665

67-
const conf: $TSFixMe = {
68-
baseURL: this.baseURL,
69-
method,
70-
url,
71-
params: method === 'GET' ? data : options.params,
72-
headers: headers,
73-
signal,
74-
};
6+
const Default = new DefaultApiClient();
757

76-
if (['POST', 'PUT'].includes(method)) {
77-
conf.data = options.string ? JSON.stringify(data) : data;
78-
conf.data.authentication = this.getAuthentication(conf.data);
79-
}
8+
/**
9+
* @example facadeApiClient.implementation = new MyAPIClient();
10+
*/
11+
export const facadeApiClient = new FacadeApiClient(Default);
8012

81-
return axios(conf)
82-
.then((res: $TSFixMe) => {
83-
return res.data;
84-
})
85-
.catch((err: $TSFixMe) => {
86-
if (_.has(err, 'response.data.detail.message')) {
87-
return Promise.reject(new Error(err.response.data.detail.message));
88-
}
89-
return Promise.reject(err);
90-
});
91-
};
92-
},
93-
};
13+
export const APIClient: IAPIClient = facadeApiClient;
9414

9515
export function configureAPIClient(config: ISettingsFormConfig) {
96-
if (APIClient.baseURL !== config.apiBaseURL) {
97-
APIClient.baseURL = config.apiBaseURL;
16+
if (Default.baseURL !== config.apiBaseURL) {
17+
Default.baseURL = config.apiBaseURL;
9818
}
9919
if (config.app_id) {
100-
APIClient.app_id = config.app_id;
20+
Default.app_id = config.app_id;
10121
}
10222
if (config.app_secret) {
103-
APIClient.app_secret = config.app_secret;
23+
Default.app_secret = config.app_secret;
10424
}
10525
}

settings-form/src/index.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,14 @@ export * from './datasource';
88
export * from './account';
99
export * from './api-key';
1010
export * from './sql_snippet';
11+
export {
12+
FacadeApiClient,
13+
DefaultApiClient,
14+
facadeApiClient,
15+
APIClient,
16+
configureAPIClient,
17+
} from './api-caller/request';
18+
export type { IAPIClient, IAPIClientRequestOptions } from './api-caller/request';
1119

1220
export * from './api-caller/account.typed';
1321
export * from './api-caller/api-key.typed';

settings-form/src/shared

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../../shared/src

shared/.eslintrc.json

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"extends": ["../.eslintrc.json"],
3+
"ignorePatterns": ["!**/*"],
4+
"overrides": [
5+
{
6+
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
7+
"rules": {}
8+
},
9+
{
10+
"files": ["*.ts", "*.tsx"],
11+
"rules": {}
12+
},
13+
{
14+
"files": ["*.js", "*.jsx"],
15+
"rules": {}
16+
}
17+
]
18+
}

shared/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# shared
2+
3+
This library was generated with [Nx](https://nx.dev).

shared/project.json

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"name": "shared",
3+
"$schema": "../node_modules/nx/schemas/project-schema.json",
4+
"sourceRoot": "shared/src",
5+
"projectType": "library",
6+
"targets": {
7+
"lint": {
8+
"executor": "@nx/linter:eslint",
9+
"outputs": ["{options.outputFile}"],
10+
"options": {
11+
"lintFilePatterns": ["shared/**/*.ts"]
12+
}
13+
}
14+
},
15+
"tags": []
16+
}

0 commit comments

Comments
 (0)