0832a56868
* 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>
273 lines
6.3 KiB
TypeScript
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);
|
|
}
|
|
}
|