* refactor: plugin manager page * fix: bug * feat: addByNpm api * fix: improve the addByNpm * feat: improve applicationPlugins:list api * fix: re-download npm package when restart app * fix: plugin delete api * feat: plugin detail api * feat: zipUrl add api * fix: upload api bug * fix: plugin detail info * feat: upgrade api * fix: upload api * feat: handle plugin load error * feat: support authToken * feat: muti lang * fix: build error * fix: self review * Update plugin-manager.ts * fix: bug * fix: bug * fix: bug * fix: bug * fix: bug * fix: bugs * fix: detail click and remove isOfficial * fix: upgrade no refresh * fix: file size and type check * fix: bug * fix: upgrade error * fix: bug * fix: bug * fix: plugin card layout * fix: handling exceptional cases * fix: tgz file support * fix: macos compress file * fix: bug * fix: bug * fix: bug * fix: bug * fix: add upgrade npm type * fix: bugs * fix: bug * fix: change plugins static expose url * fix: api prefix * fix: bug * fix: add nginx `/static/plugin/` path * fix: bugs and pr docker build no dts * fix: bug * fix: build tools bug * fix: improve code * fix: build bug * feat: improve plugin info * fix: ui bug * fix: plugin document bug * feat: improve code * feat: improve code * feat: process dev deps check * feat: improve code * feat: process.env.IS_DEV_CMD * fix: do not delete the plugin package * feat: plugin symlink * fix: tsx watch --ignore=./storage/plugins/** * fix: test error * fix: improve code * fix: improve code * fix: emitStartedEvent * fix: improve code * fix: type error * fix: test error * test: console.log * fix: createStoragePluginSymLink * fix: clientStaticMiddleware rename to clientStaticUtils * feat: build tools support plugins folder * fix: 350px * fix: error * feat: client dev support plugin folder * fix: clear cli options * fix: typeError: Converting circular structure to JSON * fix: plugin name * chore: restart application after command * feat: upgrade error & docs * Update v14-changelog.md * Update v14-changelog.md * Update v14-changelog.md * fix: gateway test * refactor(plugin-workflow): add ready state for gracefully tearing down * Revert "chore: restart application after command" This reverts commit 5015274f8e4e06e506e15754b672330330e8c7f8. * chore: stop application whe restart * T 1218 change plugin folder (#2629) * feat: change folder name * feat: change `pm create` command * feat: revert plugin name change * fix: delete samples * feat: change plugins folder * fix: pm create * feat: update docs * fix: link package error * fix: docs * fix: create command * fix: pm add error * fix: create add build * fix: pm creatre + add * feat: add tar command * fix: docs * fix: bug * fix: docs --------- Co-authored-by: chenos <chenlinxh@gmail.com> * feat: docs * Update your-fisrt-plugin.md * Update your-fisrt-plugin.md * chore: application reload * chore: test * fix: pm add error * chore: preset install skip exists plugin * fix: createIfNotExists --------- Co-authored-by: chenos <chenlinxh@gmail.com> Co-authored-by: chareice <chareice@live.com> Co-authored-by: Zhou <zhou.working@gmail.com> Co-authored-by: mytharcher <mytharcher@gmail.com>
235 lines
6.6 KiB
TypeScript
235 lines
6.6 KiB
TypeScript
import APIDocPlugin from '../server';
|
|
import baseSwagger from './base-swagger';
|
|
import { SchemaTypeMapping } from './constants';
|
|
import { createDefaultActionSwagger, getInterfaceCollection } from './helpers';
|
|
import { getPluginsSwagger, getSwaggerDocument, loadSwagger } from './loader';
|
|
import { merge } from './merge';
|
|
import collectionToSwaggerObject from './collections';
|
|
|
|
export class SwaggerManager {
|
|
private plugin: APIDocPlugin;
|
|
|
|
constructor(plugin: APIDocPlugin) {
|
|
this.plugin = plugin;
|
|
}
|
|
|
|
private get app() {
|
|
return this.plugin.app;
|
|
}
|
|
|
|
private get db() {
|
|
return this.plugin.db;
|
|
}
|
|
|
|
async generateSwagger(options: { plugins?: string[] } = {}) {
|
|
const base = await this.getBaseSwagger();
|
|
const core = options.plugins ? {} : await loadSwagger('@nocobase/server');
|
|
const plugins = await this.loadSwaggers(options.plugins);
|
|
return merge(merge(core, plugins), base);
|
|
}
|
|
|
|
async getSwagger() {
|
|
return this.generateSwagger();
|
|
}
|
|
|
|
async collection2Swagger(collectionName: string, withAssociation = true) {
|
|
const collection = this.db.getCollection(collectionName);
|
|
return await (async () => {
|
|
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
return collectionToSwaggerObject(collection, {
|
|
withAssociation,
|
|
});
|
|
})();
|
|
}
|
|
|
|
async getCollectionsSwagger(name?: string) {
|
|
const base = await this.getBaseSwagger();
|
|
let others = {};
|
|
if (name) {
|
|
const collectionSwagger = await this.collection2Swagger(name);
|
|
others = merge(others, collectionSwagger);
|
|
} else {
|
|
const collections = await this.db.getRepository('collections').find({
|
|
filter: {
|
|
'name.$ne': ['roles', 'users'],
|
|
'hidden.$isFalsy': true,
|
|
},
|
|
});
|
|
|
|
for (const collection of collections) {
|
|
if (collection.name === 'roles') {
|
|
continue;
|
|
}
|
|
|
|
try {
|
|
others = merge(others, await this.collection2Swagger(collection.name, false));
|
|
} catch (e) {
|
|
this.app.log.error(e);
|
|
}
|
|
}
|
|
}
|
|
return merge(base, others);
|
|
}
|
|
|
|
async getPluginsSwagger(pluginName?: string) {
|
|
return this.generateSwagger({
|
|
plugins: pluginName ? [pluginName] : [],
|
|
});
|
|
}
|
|
|
|
async getCoreSwagger() {
|
|
return merge(await this.getBaseSwagger(), await loadSwagger('@nocobase/server'));
|
|
}
|
|
|
|
async getUrls() {
|
|
const plugins = await getPluginsSwagger(this.db)
|
|
.then((res) => {
|
|
return Object.keys(res).map((name) => {
|
|
const schema = res[name];
|
|
return {
|
|
name: schema.info?.title || name,
|
|
url: `/api/swagger:get?ns=${encodeURIComponent(`plugins/${name}`)}`,
|
|
};
|
|
});
|
|
})
|
|
.catch(() => []);
|
|
const collections = await this.db.getRepository('collections').find({
|
|
filter: {
|
|
'name.$ne': ['roles', 'users'],
|
|
'hidden.$isFalsy': true,
|
|
},
|
|
});
|
|
return [
|
|
{
|
|
name: 'NocoBase API',
|
|
url: '/api/swagger:get',
|
|
},
|
|
{
|
|
name: 'NocoBase API - Core',
|
|
url: '/api/swagger:get?ns=core',
|
|
},
|
|
{
|
|
name: 'NocoBase API - All plugins',
|
|
url: '/api/swagger:get?ns=plugins',
|
|
},
|
|
{
|
|
name: 'NocoBase API - Custom collections',
|
|
url: '/api/swagger:get?ns=collections',
|
|
},
|
|
...plugins,
|
|
...collections.map((collection) => {
|
|
return {
|
|
name: `Collection API - ${collection.title}`,
|
|
url: `/api/swagger:get?ns=${encodeURIComponent('collections/' + collection.name)}`,
|
|
};
|
|
}),
|
|
];
|
|
}
|
|
|
|
private async getBaseSwagger() {
|
|
return merge(baseSwagger, {
|
|
info: {
|
|
version: await this.app.version.get(),
|
|
},
|
|
servers: [
|
|
{
|
|
url: (this.app.resourcer.options.prefix || '/').replace(/^[^/]/, '/$1'),
|
|
},
|
|
],
|
|
});
|
|
}
|
|
|
|
private generateSchemas() {
|
|
const schemas = {};
|
|
this.app.i18n;
|
|
this.db.collections.forEach((collection) => {
|
|
const properties = {};
|
|
const model = {
|
|
type: 'object',
|
|
properties,
|
|
};
|
|
|
|
collection.forEachField((field) => {
|
|
const { type, target } = field;
|
|
const property: Record<string, any> = {};
|
|
if (type === 'hasMany' || type === 'belongsToMany') {
|
|
property['type'] = 'array';
|
|
property['items'] = {
|
|
$ref: `#/components/schemas/${target}`,
|
|
};
|
|
} else if (type === 'belongsTo' || type === 'hasOne') {
|
|
property['type'] = 'object';
|
|
property['$ref'] = `#/components/schemas/${target}`;
|
|
} else {
|
|
property.type = SchemaTypeMapping[type] || type;
|
|
if (property.type === 'array' && !property.items) {
|
|
property.items = {
|
|
type: 'object',
|
|
};
|
|
}
|
|
property.example = field.get('defaultValue');
|
|
}
|
|
properties[field.name] = property;
|
|
});
|
|
schemas[collection.name] = model;
|
|
});
|
|
return schemas;
|
|
}
|
|
|
|
private generateCollectionBuiltInInterface() {
|
|
const paths = {};
|
|
const IC = getInterfaceCollection(this.app.resourcer.options);
|
|
this.db.collections.forEach((collection) => {
|
|
const { name } = collection;
|
|
let actions;
|
|
|
|
if (this.app.resourcer.isDefined(name)) {
|
|
actions = this.app.resourcer.getResource(name).actions;
|
|
} else {
|
|
actions = {
|
|
has: () => true,
|
|
};
|
|
}
|
|
|
|
const collectionMethods = {};
|
|
Object.entries(IC).forEach(([path, actionNames]) => {
|
|
actionNames.forEach((actionName) => {
|
|
const actionMethods = createDefaultActionSwagger({
|
|
collection,
|
|
});
|
|
const { method = 'post', ...swaggerArgs } = actionMethods[actionName];
|
|
const parameters = [];
|
|
['associatedName', 'associatedIndex', 'resourceIndex'].forEach((name) => {
|
|
if (path.includes(`{${name}}`)) {
|
|
parameters.push({
|
|
name,
|
|
required: true,
|
|
schema: {
|
|
type: 'string',
|
|
},
|
|
});
|
|
}
|
|
});
|
|
|
|
if (actions.has(actionName)) {
|
|
collectionMethods[path.replace('{resourceName}', `${name}:${actionName}`)] = {
|
|
[method]: {
|
|
tags: [name],
|
|
description: `${name}:${actionName}`,
|
|
parameters,
|
|
...swaggerArgs,
|
|
},
|
|
};
|
|
}
|
|
});
|
|
});
|
|
Object.assign(paths, collectionMethods);
|
|
});
|
|
return paths;
|
|
}
|
|
|
|
private loadSwaggers(plugins: string[]) {
|
|
return getSwaggerDocument(this.db, plugins);
|
|
}
|
|
}
|