mirror of
https://github.com/nocobase/nocobase
synced 2024-11-17 22:25:45 +00:00
dcdb21d398
* api/ui 改名为 server/client * 微调 * 继续完善 pages * Fix env file and file mode. (#1) * Fix: ignore .env file and environment variable names. * Fix: correct file mode. * fix: put environment variables together * fix: separate data and ui resourcer * feat: collection loader * feat: redirectTo * feat: fields & actions & views * feat: fields & actions * feat: app & pages & collections... * feat: collections & pages & permissions... * Doc: add readme (#2) * Doc: add README.md. * Util: add .editorconfig. * Fix: use glob ignore option instead of additional checking. (#3) * Fix: typo. (#4) * feat: permissions * feat: getCollection & getView actions * refactor: code cleanup Co-authored-by: Junyi <mytharcher@users.noreply.github.com>
257 lines
6.8 KiB
TypeScript
257 lines
6.8 KiB
TypeScript
import _ from 'lodash';
|
||
import { pathToRegexp } from 'path-to-regexp';
|
||
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 {
|
||
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 {
|
||
const { resourceName, associatedName } = params;
|
||
return associatedName ? `${associatedName}.${resourceName}` : resourceName;
|
||
}
|
||
|
||
export function parseRequest(request: ParseRequest, options: ParseOptions = {}): ParsedParams | false {
|
||
|
||
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,
|
||
},
|
||
'/:resourceName/:resourceKey': {
|
||
get: accessors.get,
|
||
put: accessors.update,
|
||
patch: accessors.update,
|
||
delete: accessors.delete,
|
||
},
|
||
'/:associatedName/:associatedKey/:resourceName': {
|
||
get: accessors.list,
|
||
post: accessors.create,
|
||
},
|
||
'/: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,
|
||
},
|
||
'/: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 = {};
|
||
|
||
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 parseFields(fields: any) {
|
||
if (!fields) {
|
||
return {}
|
||
}
|
||
if (typeof fields === 'string') {
|
||
fields = fields.split(',').map(field => field.trim());
|
||
}
|
||
if (Array.isArray(fields)) {
|
||
return {
|
||
only: fields,
|
||
appends: [],
|
||
}
|
||
}
|
||
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);
|
||
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;
|
||
}
|