tachybase_todo/packages/resourcer/src/utils.ts

287 lines
7.7 KiB
TypeScript
Raw Normal View History

2020-10-24 15:34:43 +08:00
import _ from 'lodash';
import { pathToRegexp } from 'path-to-regexp';
import qs from 'qs';
2020-10-24 15:34:43 +08:00
import { ResourceType } from './resource';
export interface ParseRequest {
path: string;
method: string;
// 资源类型
type?: ResourceType;
}
export interface ParseOptions {
prefix?: string;
accessors?: {
list?: string;
create?: string;
get?: string;
update?: string;
delete?: string;
set?: string;
add?: string;
};
}
export interface ParsedParams {
2020-10-24 15:34:43 +08:00
actionName?: string;
resourceName?: string;
resourceKey?: string;
associatedName?: string;
associatedKey?: string;
// table: string;
// tableKey?: string | number;
// relatedTable?: string;
// relatedKey?: string | number;
// action?: ActionName,
}
export function getNameByParams(params: ParsedParams): string {
2020-10-24 15:34:43 +08:00
const { resourceName, associatedName } = params;
return associatedName ? `${associatedName}.${resourceName}` : resourceName;
}
export function parseRequest(request: ParseRequest, options: ParseOptions = {}): ParsedParams | false {
2020-10-24 15:34:43 +08:00
const accessors = {
// 常规 actions
list: 'list',
create: 'create',
get: 'get',
update: 'update',
delete: 'destroy',
// associate 操作
add: 'add',
set: 'set',
remove: 'remove',
...(options.accessors || {}),
};
const keys = [];
const regexp = pathToRegexp('/resourcer/{:associatedName.}?:resourceName{\\::actionName}', keys);
const matches = regexp.exec(request.path);
if (matches) {
const params = {};
keys.forEach((obj, index) => {
if (matches[index+1] === undefined) {
return;
}
params[obj.name] = matches[index+1];
});
return params;
}
const defaults = {
single: {
'/:resourceName': {
get: accessors.list,
post: accessors.create,
delete: accessors.delete
2020-10-24 15:34:43 +08:00
},
'/:resourceName/:resourceKey': {
get: accessors.get,
put: accessors.update,
patch: accessors.update,
delete: accessors.delete,
},
'/:associatedName/:associatedKey/:resourceName': {
get: accessors.list,
post: accessors.create,
delete: accessors.delete,
2020-10-24 15:34:43 +08:00
},
'/:associatedName/:associatedKey/:resourceName/:resourceKey': {
get: accessors.get,
post: accessors.create,
put: accessors.update,
patch: accessors.update,
delete: accessors.delete,
},
},
hasOne: {
'/:associatedName/:associatedKey/:resourceName': {
get: accessors.get,
post: accessors.update,
put: accessors.update,
patch: accessors.update,
delete: accessors.delete,
},
},
hasMany: {
'/:associatedName/:associatedKey/:resourceName': {
get: accessors.list,
post: accessors.create,
delete: accessors.delete,
2020-10-24 15:34:43 +08:00
},
'/:associatedName/:associatedKey/:resourceName/:resourceKey': {
get: accessors.get,
post: accessors.create,
put: accessors.update,
patch: accessors.update,
delete: accessors.delete,
},
},
belongsTo: {
'/:associatedName/:associatedKey/:resourceName': {
get: accessors.get,
delete: accessors.remove,
},
'/:associatedName/:associatedKey/:resourceName/:resourceKey': {
post: accessors.set,
},
},
belongsToMany: {
'/:associatedName/:associatedKey/:resourceName': {
get: accessors.list,
post: accessors.set,
},
'/:associatedName/:associatedKey/:resourceName/:resourceKey': {
get: accessors.get,
post: accessors.add,
put: accessors.update, // Many to Many 的 update 是针对 through
patch: accessors.update, // Many to Many 的 update 是针对 through
delete: accessors.remove,
},
},
};
const params: ParsedParams = {};
2020-10-24 15:34:43 +08:00
let prefix = (options.prefix||'').trim();
if (prefix && !prefix.startsWith('/')) {
prefix = `/${prefix}`;
}
const { type = 'single' } = request;
for (const path in defaults[type]) {
const keys = [];
const regexp = pathToRegexp(`${prefix}${path}`, keys, {});
const matches = regexp.exec(request.path);
if (!matches) {
continue;
}
keys.forEach((obj, index) => {
if (matches[index+1] === undefined) {
return;
}
params[obj.name] = matches[index+1];
});
params.actionName = _.get(defaults, [type, path, request.method.toLowerCase()]);
}
if (Object.keys(params).length === 0) {
return false;
}
if (params.resourceName) {
const [resourceName, actionName] = params.resourceName.split(':');
if (actionName) {
params.resourceName = resourceName;
params.actionName = actionName;
}
}
return params;
}
export function requireModule(module: any) {
if (typeof module === 'string') {
module = require(module);
}
if (typeof module !== 'object') {
return module;
}
return module.__esModule ? module.default : module;
}
export function parseQuery(input: string): any {
// 自带 query 处理的不太给力,需要用 qs 转一下
const query = qs.parse(input, {
// 原始 query string 中如果一个键连等号“=”都没有可以被认为是 null 类型
strictNullHandling: true
});
// filter 支持 json string
if (typeof query.filter === 'string') {
query.filter = JSON.parse(query.filter);
}
return query;
}
export function parseFields(fields: any) {
2020-10-24 15:34:43 +08:00
if (!fields) {
return {}
}
if (typeof fields === 'string') {
fields = fields.split(',').map(field => field.trim());
}
if (Array.isArray(fields)) {
const onlyFields = [];
const output: any = {};
fields.forEach(item => {
if (typeof item === 'string') {
onlyFields.push(item);
} else if (typeof item === 'object') {
if (item.only) {
onlyFields.push(...item.only.toString().split(','));
}
Object.assign(output, parseFields(item));
}
});
if (onlyFields.length) {
output.only = onlyFields;
2020-10-24 15:34:43 +08:00
}
return output;
2020-10-24 15:34:43 +08:00
}
if (fields.only && typeof fields.only === 'string') {
fields.only = fields.only.split(',').map(field => field.trim());
}
if (fields.except && typeof fields.except === 'string') {
fields.except = fields.except.split(',').map(field => field.trim());
}
if (fields.appends && typeof fields.appends === 'string') {
fields.appends = fields.appends.split(',').map(field => field.trim());
}
return fields;
}
export function mergeFields(defaults: any, inputs: any) {
let fields: any = {};
defaults = parseFields(defaults);
inputs = parseFields(inputs);
2020-10-24 15:34:43 +08:00
if (inputs.only) {
// 前端提供 only后端提供 only
if (defaults.only) {
fields.only = defaults.only.filter(field => inputs.only.includes(field))
}
// 前端提供 only后端提供 except输出 only 排除 except
else if (defaults.except) {
fields.only = inputs.only.filter(field => !defaults.except.includes(field))
}
// 前端提供 only后端没有提供 only 或 except
else {
fields.only = inputs.only;
}
} else if (inputs.except) {
// 前端提供 except后端提供 only只输出 only 里排除 except 的字段
if (defaults.only) {
fields.only = defaults.only.filter(field => !inputs.except.includes(field))
}
// 前端提供 except后端提供 except 或不提供,合并 except
else {
fields.except = _.uniq([...inputs.except, ...(defaults.except||[])]);
}
}
// 前端没提供 only 或 except
else {
fields = defaults;
}
// 如果前端提供了 appends
if (!_.isEmpty(inputs.appends)) {
fields.appends = _.uniq([...inputs.appends, ...(defaults.appends||[])]);
}
if (!fields.appends) {
fields.appends = [];
}
return fields;
}