tachybase_todo/packages/core/sdk/src/APIClient.ts
ChengLei Shao 0832a56868
feat: multiple apps (#1540)
* chore: skip yarn install in pm command

* feat: dump sub app by sub app name

* feat: dump & restore by sub app

* chore: enable application name to edit

* chore: field belongsTo uiSchema

* test: drop schema

* feat: uiSchema migrator

* fix: test

* fix: remove uiSchema

* fix: rerun migration

* chore: migrate fieldsHistory uiSchema

* fix: set uiSchema options

* chore: transaction params

* fix: sql error in mysql

* fix: sql compatibility

* feat: collection group api

* chore: restore & dump action template

* chore: tmp commit

* chore: collectionGroupAction

* feat: dumpableCollection api

* refactor: dump command

* fix: remove uiSchemaUid

* chore: get uiSchemaUid from tmp field

* feat: return dumped file url in dumper.dump

* feat: dump api

* refactor: collection groyoup

* chore: comment

* feat: restore command force option

* feat: dump with collection groups

* refactor: restore command

* feat: restore http api

* fix: test

* fix: test

* fix: restore test

* chore: volta pin

* fix: sub app load collection options

* fix: stop sub app

* feat: add stopped status to application to prevent duplicate application stop

* chore: tmp commit

* test: upgrade

* feat: pass upgrade event to sub app

* fix: app manager client

* fix: remove stopped status

* fix: emit beforeStop event

* feat: support dump & restore subApp through api

* chore: dumpable collections api

* refactor: getTableNameWithSchema

* fix: schema name

* feat:  cname

* refactor: collection 同步实现方式

* refactor: move collection group manager to database

* fix: test

* fix: remove uiSchema

* fix: uiSchema

* fix: remove settings

* chore: plugin enable & disable event

* feat: modal warning

* fix: users_jobs namespace

* fix: rolesUischemas namespace

* fix: am snippet

* feat: beforeSubAppInstall event

* fix: improve NOCOBASE_LOCALE_KEY & NOCOBASE_ROLE_KEY

---------

Co-authored-by: chenos <chenlinxh@gmail.com>
2023-03-10 19:16:00 +08:00

273 lines
6.3 KiB
TypeScript

import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios';
import qs from 'qs';
export interface ActionParams {
filterByTk?: any;
[key: string]: any;
}
type ResourceActionOptions<P = any> = {
resource?: string;
resourceOf?: any;
action?: string;
params?: P;
};
export interface IResource {
list?: (params?: ActionParams) => Promise<any>;
get?: (params?: ActionParams) => Promise<any>;
create?: (params?: ActionParams) => Promise<any>;
update?: (params?: ActionParams) => Promise<any>;
destroy?: (params?: ActionParams) => Promise<any>;
[key: string]: (params?: ActionParams) => Promise<any>;
}
export class Auth {
protected api: APIClient;
protected NOCOBASE_LOCALE_KEY = 'NOCOBASE_LOCALE';
protected NOCOBASE_ROLE_KEY = 'NOCOBASE_ROLE';
protected options = {
token: null,
locale: null,
role: null,
};
constructor(api: APIClient) {
this.api = api;
this.initKeys();
this.locale = this.getLocale();
this.role = this.getRole();
this.token = this.getToken();
this.api.axios.interceptors.request.use(this.middleware.bind(this));
}
initKeys() {
if (!window) {
return;
}
const match = window.location.pathname.match(/^\/apps\/([^/]*)\//);
if (!match) {
return;
}
const appName = match[1];
this.NOCOBASE_LOCALE_KEY = `${appName.toUpperCase()}_NOCOBASE_LOCALE`;
this.NOCOBASE_ROLE_KEY = `${appName.toUpperCase()}_NOCOBASE_ROLE`;
}
get locale() {
return this.getLocale();
}
get role() {
return this.getRole();
}
get token() {
return this.getToken();
}
set locale(value) {
this.setLocale(value);
}
set role(value) {
this.setRole(value);
}
set token(value) {
this.setToken(value);
}
middleware(config: AxiosRequestConfig) {
if (this.locale) {
config.headers['X-Locale'] = this.locale;
}
if (this.role) {
config.headers['X-Role'] = this.role;
}
if (this.token) {
config.headers['Authorization'] = `Bearer ${this.token}`;
}
return config;
}
getLocale() {
return this.api.storage.getItem(this.NOCOBASE_LOCALE_KEY);
}
setLocale(locale: string) {
this.options.locale = locale;
this.api.storage.setItem(this.NOCOBASE_LOCALE_KEY, locale || '');
}
getToken() {
return this.api.storage.getItem('NOCOBASE_TOKEN');
}
setToken(token: string) {
this.options.token = token;
this.api.storage.setItem('NOCOBASE_TOKEN', token || '');
if (!token) {
this.setRole(null);
// this.setLocale(null);
}
}
getRole() {
return this.api.storage.getItem(this.NOCOBASE_ROLE_KEY);
}
setRole(role: string) {
this.options.role = role;
this.api.storage.setItem(this.NOCOBASE_ROLE_KEY, role || '');
}
async signIn(values, authenticator: string = 'password'): Promise<AxiosResponse<any>> {
const response = await this.api.request({
method: 'post',
url: 'users:signin',
data: values,
params: {
authenticator,
},
});
const data = response?.data?.data;
this.setToken(data?.token);
return response;
}
async signOut() {
await this.api.request({
method: 'post',
url: 'users:signout',
});
this.setToken(null);
}
}
export abstract class Storage {
abstract clear(): void;
abstract getItem(key: string): string | null;
abstract removeItem(key: string): void;
abstract setItem(key: string, value: string): void;
}
export class MemoryStorage extends Storage {
items = new Map();
clear() {
this.items.clear();
}
getItem(key: string) {
return this.items.get(key);
}
setItem(key: string, value: string) {
return this.items.set(key, value);
}
removeItem(key: string) {
return this.items.delete(key);
}
}
interface ExtendedOptions {
authClass?: any;
storageClass?: any;
}
export class APIClient {
axios: AxiosInstance;
auth: Auth;
storage: Storage;
constructor(instance?: AxiosInstance | (AxiosRequestConfig & ExtendedOptions)) {
if (typeof instance === 'function') {
this.axios = instance;
} else {
const { authClass, storageClass, ...others } = instance || {};
this.axios = axios.create(others);
this.initStorage(storageClass);
if (authClass) {
this.auth = new authClass(this);
}
}
if (!this.storage) {
this.initStorage();
}
if (!this.auth) {
this.auth = new Auth(this);
}
this.interceptors();
}
private initStorage(storage?: any) {
if (storage) {
this.storage = new storage(this);
} else if (typeof localStorage !== 'undefined') {
this.storage = localStorage;
} else {
this.storage = new MemoryStorage();
}
}
interceptors() {
this.axios.interceptors.request.use((config) => {
config.paramsSerializer = (params) => {
return qs.stringify(params, {
strictNullHandling: true,
arrayFormat: 'brackets',
});
};
return config;
});
}
request<T = any, R = AxiosResponse<T>, D = any>(config: AxiosRequestConfig<D> | ResourceActionOptions): Promise<R> {
const { resource, resourceOf, action, params } = config as any;
if (resource) {
return this.resource(resource, resourceOf)[action](params);
}
return this.axios.request<T, R, D>(config);
}
resource(name: string, of?: any): IResource {
const target = {};
const handler = {
get: (_: any, actionName: string) => {
let url = name.split('.').join(`/${of || '_'}/`);
url += `:${actionName}`;
const config: AxiosRequestConfig = { url };
if (['get', 'list'].includes(actionName)) {
config['method'] = 'get';
} else {
config['method'] = 'post';
}
return async (params?: ActionParams, opts?: any) => {
const { values, filter, ...others } = params || {};
config['params'] = others;
if (filter) {
if (typeof filter === 'string') {
config['params']['filter'] = filter;
} else {
config['params']['filter'] = JSON.stringify(filter);
}
}
if (config.method !== 'get') {
config['data'] = values || {};
}
return await this.request({
...config,
...opts,
});
};
},
};
return new Proxy(target, handler);
}
}