feat: api documentation plugin (#2255)
* feat: api doc plugin * fix: merge * chore: upgrade swagger ui to latest * feat: get paths from recourser * feat: configure security * feat: add models * feat: reimplement resource action * feat: support render schemas correctly * feat: support load swagger documentation * refactor: implement `SwaggerManager` * fix: re import * feat: update info * refactor: do not use the cache strategy for the time being * feat: support collection builtin actions * fix: incorrect tag * feat: support different swagger json for different plugins * feat: support load server package * feat: support visit from plugin center * feat: add schemas for mapConfiguration * feat: update * fix: update tags * feat: support only render plugin that has swagger content * refactor: use swagger-ui-react instead of swagger-ui-dist * fix: clean * fix: reset * refactor: update plugin place * fix: revert * fix: remove version * fix: type error * feat: swagger doc * refactor: improve apis * feat: add doc * feat: support destination cache * fix: avoid authorization override * fix: auth bug * feat: update documentation * fix: typo * feat: support json * fix: key * fix: update yarn.lock * feat: update swagger doc * feat: swagger doc * docs: add auth swagger files (#2341) * docs: add auth swagger files * fix: yarn.lock * fix: skip core * feat: swagger doc * docs: improve auth docs * fix(theme-editor): avoid crashing * feat(theme-editor): improve api doc * docs: add localization-management swagger * docs(plugin-workflow): add api doc (#2379) * fix: remove files * fix: aaa * fix: dist * fix: load swagger * feat: acl api doc (#2494) * chore: acl api doc * feat: ui schema api doc * feat: multi apps api doc * chore: ui schema doc * feat: collection api doc * chore: association api doc * chore: single association doc * feat: move action doc * chore: list parameters * feat: update swagger doc * chore: collectionIndex to first * fix: test error * fix: ref * chore: doc tags * chore: template * chore: doc with association options * chore: single association doc * chore: relation type * chore: filter single association params * chore: m2m api doc * chore: params * fix: 0.12.0-alpha.5 * fix: update yarn.lock * chore: data wrap --------- Co-authored-by: chenos <chenlinxh@gmail.com> Co-authored-by: YANG QIA <2013xile@gmail.com> Co-authored-by: Rain <958414905@qq.com> Co-authored-by: Junyi <mytharcher@users.noreply.github.com> Co-authored-by: ChengLei Shao <chareice@live.com>
This commit is contained in:
parent
9154e531c2
commit
1e0bedca86
@ -15,3 +15,4 @@ packages/core/client/src/locale/*
|
||||
**/.dumi/tmp-production
|
||||
packages/core/cli/templates/plugin/src/client/*.tpl
|
||||
packages/app/client/src/.plugins
|
||||
packages/**/locale/**
|
||||
|
1
packages/app/client/src/plugins/api-doc.ts
Normal file
1
packages/app/client/src/plugins/api-doc.ts
Normal file
@ -0,0 +1 @@
|
||||
export { default } from '@nocobase/plugin-api-doc/client';
|
@ -1,11 +1,11 @@
|
||||
import {
|
||||
ArrayFieldRepository,
|
||||
BelongsToManyRepository,
|
||||
HasManyRepository,
|
||||
MultipleRelationRepository,
|
||||
} from '@nocobase/database';
|
||||
import { Context } from '..';
|
||||
import { getRepositoryFromParams } from '../utils';
|
||||
import {
|
||||
BelongsToManyRepository,
|
||||
MultipleRelationRepository,
|
||||
HasManyRepository,
|
||||
ArrayFieldRepository,
|
||||
} from '@nocobase/database';
|
||||
|
||||
export async function add(ctx: Context, next) {
|
||||
const repository = getRepositoryFromParams(ctx);
|
||||
@ -19,8 +19,9 @@ export async function add(ctx: Context, next) {
|
||||
) {
|
||||
return await next();
|
||||
}
|
||||
const filterByTk = ctx.action.params.filterByTk || ctx.action.params.filterByTks || ctx.action.params.values;
|
||||
|
||||
await (<HasManyRepository | BelongsToManyRepository>repository).add(ctx.action.params.values);
|
||||
await (<HasManyRepository | BelongsToManyRepository>repository).add(filterByTk);
|
||||
|
||||
ctx.status = 200;
|
||||
|
||||
|
@ -28,7 +28,9 @@ export function RelationRepositoryActionBuilder(method: 'remove' | 'set') {
|
||||
return async function (ctx: Context, next) {
|
||||
const repository = getRepositoryFromParams(ctx);
|
||||
|
||||
await repository[method](ctx.action.params.values);
|
||||
const filterByTk = ctx.action.params.filterByTk || ctx.action.params.filterByTks || ctx.action.params.values;
|
||||
|
||||
await repository[method](filterByTk);
|
||||
|
||||
ctx.status = 200;
|
||||
await next();
|
||||
|
@ -1,6 +1,6 @@
|
||||
import lodash from 'lodash';
|
||||
import { Transactionable } from 'sequelize/types';
|
||||
import { Collection } from '../collection';
|
||||
import lodash from 'lodash';
|
||||
import { transactionWrapperBuilder } from '../decorators/transaction-decorator';
|
||||
import { ArrayField } from '../fields';
|
||||
|
||||
@ -9,7 +9,11 @@ const transaction = transactionWrapperBuilder(function () {
|
||||
});
|
||||
|
||||
export class ArrayFieldRepository {
|
||||
constructor(protected collection: Collection, protected fieldName: string, protected targetValue: string | number) {
|
||||
constructor(
|
||||
protected collection: Collection,
|
||||
protected fieldName: string,
|
||||
protected targetValue: string | number,
|
||||
) {
|
||||
const field = collection.getField(fieldName);
|
||||
if (!(field instanceof ArrayField)) {
|
||||
throw new Error('Field must be of type Array');
|
||||
|
@ -133,23 +133,10 @@ export class BelongsToManyRepository extends MultipleRelationRepository {
|
||||
call: 'add' | 'set',
|
||||
options: TargetKey | TargetKey[] | PrimaryKeyWithThroughValues | PrimaryKeyWithThroughValues[] | AssociatedOptions,
|
||||
) {
|
||||
let handleKeys: TargetKey[] | PrimaryKeyWithThroughValues[];
|
||||
const handleKeys: TargetKey[] | PrimaryKeyWithThroughValues[] = this.convertTks(options) as any;
|
||||
|
||||
const transaction = await this.getTransaction(options, false);
|
||||
|
||||
if (lodash.isPlainObject(options)) {
|
||||
options = (<AssociatedOptions>options).tk || [];
|
||||
}
|
||||
|
||||
if (lodash.isString(options) || lodash.isNumber(options)) {
|
||||
handleKeys = [<TargetKey>options];
|
||||
} // if it is type primaryKeyWithThroughValues
|
||||
else if (lodash.isArray(options) && options.length == 2 && lodash.isPlainObject(options[0][1])) {
|
||||
handleKeys = [<PrimaryKeyWithThroughValues>options];
|
||||
} else {
|
||||
handleKeys = <TargetKey[] | PrimaryKeyWithThroughValues[]>options;
|
||||
}
|
||||
|
||||
const sourceModel = await this.getSourceModel(transaction);
|
||||
|
||||
const setObj = (<any>handleKeys).reduce((carry, item) => {
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { omit } from 'lodash';
|
||||
import { HasMany, Op } from 'sequelize';
|
||||
import { AggregateOptions, DestroyOptions, FindOptions, TargetKey, TK } from '../repository';
|
||||
import { AggregateOptions, DestroyOptions, FindOptions, TK, TargetKey } from '../repository';
|
||||
import { AssociatedOptions, MultipleRelationRepository } from './multiple-relation-repository';
|
||||
import { transaction } from './relation-repository';
|
||||
export class HasManyRepository extends MultipleRelationRepository {
|
||||
@ -89,20 +89,9 @@ export class HasManyRepository extends MultipleRelationRepository {
|
||||
return true;
|
||||
}
|
||||
|
||||
handleKeyOfAdd(options) {
|
||||
let handleKeys;
|
||||
|
||||
if (typeof options !== 'object' && !Array.isArray(options)) {
|
||||
handleKeys = [options];
|
||||
} else {
|
||||
handleKeys = options['pk'];
|
||||
}
|
||||
return handleKeys;
|
||||
}
|
||||
|
||||
@transaction((args, transaction) => {
|
||||
return {
|
||||
pk: args[0],
|
||||
tk: args[0],
|
||||
transaction,
|
||||
};
|
||||
})
|
||||
@ -111,23 +100,22 @@ export class HasManyRepository extends MultipleRelationRepository {
|
||||
|
||||
const sourceModel = await this.getSourceModel(transaction);
|
||||
|
||||
await sourceModel[this.accessors().set](this.handleKeyOfAdd(options), {
|
||||
await sourceModel[this.accessors().set](this.convertTks(options), {
|
||||
transaction,
|
||||
});
|
||||
}
|
||||
|
||||
@transaction((args, transaction) => {
|
||||
return {
|
||||
pk: args[0],
|
||||
tk: args[0],
|
||||
transaction,
|
||||
};
|
||||
})
|
||||
async add(options: TargetKey | TargetKey[] | AssociatedOptions): Promise<void> {
|
||||
const transaction = await this.getTransaction(options);
|
||||
|
||||
const sourceModel = await this.getSourceModel(transaction);
|
||||
|
||||
await sourceModel[this.accessors().add](this.handleKeyOfAdd(options), {
|
||||
await sourceModel[this.accessors().add](this.convertTks(options), {
|
||||
transaction,
|
||||
});
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { HasOne, MultiAssociationAccessors, Sequelize, Transaction, Transactionable } from 'sequelize';
|
||||
import lodash from 'lodash';
|
||||
import { HasOne, MultiAssociationAccessors, Sequelize, Transaction, Transactionable } from 'sequelize';
|
||||
import injectTargetCollection from '../decorators/target-collection-decorator';
|
||||
import {
|
||||
CommonFindOptions,
|
||||
@ -8,8 +8,8 @@ import {
|
||||
Filter,
|
||||
FindOneOptions,
|
||||
FindOptions,
|
||||
TargetKey,
|
||||
TK,
|
||||
TargetKey,
|
||||
UpdateOptions,
|
||||
} from '../repository';
|
||||
import { updateModelByValues } from '../update-associations';
|
||||
@ -113,14 +113,8 @@ export abstract class MultipleRelationRepository extends RelationRepository {
|
||||
})
|
||||
async remove(options: TargetKey | TargetKey[] | AssociatedOptions): Promise<void> {
|
||||
const transaction = await this.getTransaction(options);
|
||||
let handleKeys = options['tk'];
|
||||
|
||||
if (!Array.isArray(handleKeys)) {
|
||||
handleKeys = [handleKeys];
|
||||
}
|
||||
|
||||
const sourceModel = await this.getSourceModel(transaction);
|
||||
await sourceModel[this.accessors().removeMultiple](handleKeys, {
|
||||
await sourceModel[this.accessors().removeMultiple](this.convertTks(options), {
|
||||
transaction,
|
||||
});
|
||||
return;
|
||||
|
@ -44,6 +44,22 @@ export abstract class RelationRepository {
|
||||
return this.db.getCollection(this.targetModel.name);
|
||||
}
|
||||
|
||||
convertTk(options: any) {
|
||||
let tk = options;
|
||||
if (typeof options === 'object' && options['tk']) {
|
||||
tk = options['tk'];
|
||||
}
|
||||
return tk;
|
||||
}
|
||||
|
||||
convertTks(options: any) {
|
||||
let tk = this.convertTk(options);
|
||||
if (typeof tk === 'string') {
|
||||
tk = tk.split(',');
|
||||
}
|
||||
return lodash.castArray(tk);
|
||||
}
|
||||
|
||||
targetKey() {
|
||||
return this.associationField.targetKey;
|
||||
}
|
||||
|
@ -38,11 +38,10 @@ export abstract class SingleRelationRepository extends RelationRepository {
|
||||
})
|
||||
async set(options: TargetKey | SetOption): Promise<void> {
|
||||
const transaction = await this.getTransaction(options);
|
||||
const handleKey = lodash.isPlainObject(options) ? (<SetOption>options).tk : options;
|
||||
|
||||
const sourceModel = await this.getSourceModel(transaction);
|
||||
|
||||
return await sourceModel[this.accessors().set](handleKey, {
|
||||
return await sourceModel[this.accessors().set](this.convertTk(options), {
|
||||
transaction,
|
||||
});
|
||||
}
|
||||
|
1565
packages/core/server/src/swagger/index.json
Normal file
1565
packages/core/server/src/swagger/index.json
Normal file
File diff suppressed because it is too large
Load Diff
@ -2,8 +2,9 @@ import deepmerge from 'deepmerge';
|
||||
|
||||
const overwriteMerge = (destinationArray, sourceArray, options) => sourceArray;
|
||||
|
||||
export function merge(obj1: any, obj2: any) {
|
||||
export function merge(obj1: any, obj2: any, opts?: any) {
|
||||
return deepmerge(obj1, obj2, {
|
||||
arrayMerge: overwriteMerge,
|
||||
...opts,
|
||||
});
|
||||
}
|
||||
|
373
packages/plugins/acl/src/swagger/index.ts
Normal file
373
packages/plugins/acl/src/swagger/index.ts
Normal file
@ -0,0 +1,373 @@
|
||||
export default {
|
||||
openapi: '3.0.2',
|
||||
info: {
|
||||
title: 'NocoBase API - ACL plugin',
|
||||
},
|
||||
paths: {
|
||||
'/roles:list': {
|
||||
get: {
|
||||
tags: ['roles'],
|
||||
description: '',
|
||||
parameters: [],
|
||||
responses: {
|
||||
200: {
|
||||
description: 'ok',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'array',
|
||||
items: {
|
||||
$ref: '#/components/schemas/role',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
'/roles:get': {
|
||||
get: {
|
||||
tags: ['roles'],
|
||||
description: '',
|
||||
parameters: [
|
||||
{
|
||||
name: 'filterByTk',
|
||||
in: 'query',
|
||||
description: 'role name',
|
||||
required: true,
|
||||
schema: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
],
|
||||
responses: {
|
||||
200: {
|
||||
description: 'ok',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
$ref: '#/components/schemas/role',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
'/roles:create': {
|
||||
post: {
|
||||
tags: ['roles'],
|
||||
description: '',
|
||||
requestBody: {
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
$ref: '#/components/schemas/role',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
responses: {
|
||||
'200': {
|
||||
description: 'OK',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
$ref: '#/components/schemas/role',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
'/roles:update': {
|
||||
post: {
|
||||
tags: ['roles'],
|
||||
description: '',
|
||||
parameters: [
|
||||
{
|
||||
name: 'filterByTk',
|
||||
in: 'query',
|
||||
description: 'role name',
|
||||
required: true,
|
||||
schema: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
],
|
||||
requestBody: {
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
$ref: '#/components/schemas/role',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
responses: {
|
||||
200: {
|
||||
description: 'ok',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
$ref: '#/components/schemas/role',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
'/roles:destroy': {
|
||||
post: {
|
||||
tags: ['roles'],
|
||||
description: '',
|
||||
parameters: [
|
||||
{
|
||||
name: 'filterByTk',
|
||||
in: 'query',
|
||||
description: 'role name',
|
||||
required: true,
|
||||
schema: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
],
|
||||
responses: {
|
||||
'200': {
|
||||
description: 'OK',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
'/roles:check': {
|
||||
get: {
|
||||
tags: ['roles'],
|
||||
description: 'return current user role',
|
||||
responses: {
|
||||
200: {
|
||||
description: 'ok',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
$ref: '#/components/schemas/role',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
'/roles:setDefaultRole': {
|
||||
post: {
|
||||
tags: ['roles'],
|
||||
description: 'set default role for new user',
|
||||
requestBody: {
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
roleName: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
responses: {
|
||||
'200': {
|
||||
description: 'OK',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
'/roles/{roleName}/collections:list': {
|
||||
get: {
|
||||
tags: ['roles.collections'],
|
||||
description: 'list permissions of collections for role by roleName',
|
||||
parameters: [
|
||||
{
|
||||
name: 'filterByTk',
|
||||
in: 'query',
|
||||
description: 'role name',
|
||||
required: true,
|
||||
schema: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
],
|
||||
responses: {
|
||||
'200': {
|
||||
description: 'OK',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
type: {
|
||||
type: 'string',
|
||||
description: 'collection',
|
||||
},
|
||||
name: {
|
||||
type: 'string',
|
||||
description: 'collection name',
|
||||
},
|
||||
collectionName: {
|
||||
type: 'string',
|
||||
description: 'collection name',
|
||||
},
|
||||
title: {
|
||||
type: 'string',
|
||||
description: 'collection title',
|
||||
},
|
||||
roleName: {
|
||||
type: 'string',
|
||||
description: 'role name',
|
||||
},
|
||||
usingConfig: {
|
||||
type: 'string',
|
||||
enum: ['resourceAction', 'strategy'],
|
||||
description: 'resourceAction: 单独配置, strategy: 全局策略',
|
||||
},
|
||||
exists: {
|
||||
type: 'boolean',
|
||||
description: '是否存在单独配置的权限',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
'/availableActions:list': {
|
||||
get: {
|
||||
tags: ['availableActions'],
|
||||
description: 'available actions of resource in current system',
|
||||
parameters: [],
|
||||
responses: {
|
||||
'200': {
|
||||
description: 'OK',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
name: {
|
||||
type: 'string',
|
||||
description: 'Action名称',
|
||||
},
|
||||
displayName: {
|
||||
type: 'string',
|
||||
description: 'Action显示名称',
|
||||
},
|
||||
allowConfigureFields: {
|
||||
type: 'boolean',
|
||||
description: '是否允许配置字段',
|
||||
},
|
||||
onNewRecord: {
|
||||
type: 'string',
|
||||
description: '是否是新记录的Action',
|
||||
},
|
||||
type: {
|
||||
type: 'string',
|
||||
description: 'new-data 或者 old-data',
|
||||
},
|
||||
aliases: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'string',
|
||||
},
|
||||
description: '别名',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
components: {
|
||||
schemas: {
|
||||
role: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
title: {
|
||||
type: 'string',
|
||||
description: '角色名称',
|
||||
},
|
||||
name: {
|
||||
type: 'string',
|
||||
description: '角色标识',
|
||||
},
|
||||
description: {
|
||||
type: 'string',
|
||||
description: '角色描述',
|
||||
},
|
||||
hidden: {
|
||||
type: 'boolean',
|
||||
description: '是否隐藏',
|
||||
},
|
||||
default: {
|
||||
type: 'boolean',
|
||||
description: '是否默认',
|
||||
},
|
||||
allowConfigure: {
|
||||
type: 'boolean',
|
||||
description: '是否允许配置',
|
||||
},
|
||||
allowNewMenu: {
|
||||
type: 'boolean',
|
||||
description: '是否允许新建菜单',
|
||||
},
|
||||
snippets: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'string',
|
||||
},
|
||||
description: '接口权限',
|
||||
},
|
||||
strategy: {
|
||||
type: 'array',
|
||||
description: '数据表权限策略',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
actions: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'string',
|
||||
},
|
||||
description: '操作',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
createdAt: {
|
||||
type: 'string',
|
||||
format: 'date-time',
|
||||
description: '创建时间',
|
||||
},
|
||||
updatedAt: {
|
||||
type: 'string',
|
||||
format: 'date-time',
|
||||
description: '更新时间',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
2
packages/plugins/api-doc/.npmignore
Normal file
2
packages/plugins/api-doc/.npmignore
Normal file
@ -0,0 +1,2 @@
|
||||
/node_modules
|
||||
/src
|
661
packages/plugins/api-doc/LICENSE
Normal file
661
packages/plugins/api-doc/LICENSE
Normal file
@ -0,0 +1,661 @@
|
||||
GNU AFFERO GENERAL PUBLIC LICENSE
|
||||
Version 3, 19 November 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU Affero General Public License is a free, copyleft license for
|
||||
software and other kinds of works, specifically designed to ensure
|
||||
cooperation with the community in the case of network server software.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
our General Public Licenses are intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
Developers that use our General Public Licenses protect your rights
|
||||
with two steps: (1) assert copyright on the software, and (2) offer
|
||||
you this License which gives you legal permission to copy, distribute
|
||||
and/or modify the software.
|
||||
|
||||
A secondary benefit of defending all users' freedom is that
|
||||
improvements made in alternate versions of the program, if they
|
||||
receive widespread use, become available for other developers to
|
||||
incorporate. Many developers of free software are heartened and
|
||||
encouraged by the resulting cooperation. However, in the case of
|
||||
software used on network servers, this result may fail to come about.
|
||||
The GNU General Public License permits making a modified version and
|
||||
letting the public access it on a server without ever releasing its
|
||||
source code to the public.
|
||||
|
||||
The GNU Affero General Public License is designed specifically to
|
||||
ensure that, in such cases, the modified source code becomes available
|
||||
to the community. It requires the operator of a network server to
|
||||
provide the source code of the modified version running there to the
|
||||
users of that server. Therefore, public use of a modified version, on
|
||||
a publicly accessible server, gives the public access to the source
|
||||
code of the modified version.
|
||||
|
||||
An older license, called the Affero General Public License and
|
||||
published by Affero, was designed to accomplish similar goals. This is
|
||||
a different license, not a version of the Affero GPL, but Affero has
|
||||
released a new version of the Affero GPL which permits relicensing under
|
||||
this license.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU Affero General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Remote Network Interaction; Use with the GNU General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, if you modify the
|
||||
Program, your modified version must prominently offer all users
|
||||
interacting with it remotely through a computer network (if your version
|
||||
supports such interaction) an opportunity to receive the Corresponding
|
||||
Source of your version by providing access to the Corresponding Source
|
||||
from a network server at no charge, through some standard or customary
|
||||
means of facilitating copying of software. This Corresponding Source
|
||||
shall include the Corresponding Source for any work covered by version 3
|
||||
of the GNU General Public License that is incorporated pursuant to the
|
||||
following paragraph.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the work with which it is combined will remain governed by version
|
||||
3 of the GNU General Public License.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU Affero General Public License from time to time. Such new versions
|
||||
will be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU Affero General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU Affero General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU Affero General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If your software can interact with users remotely through a computer
|
||||
network, you should also make sure that it provides a way for users to
|
||||
get its source. For example, if your program is a web application, its
|
||||
interface could display a "Source" link that leads users to an archive
|
||||
of the code. There are many ways you could offer source, and different
|
||||
solutions will be better for different programs; see section 13 for the
|
||||
specific requirements.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU AGPL, see
|
||||
<https://www.gnu.org/licenses/>.
|
80
packages/plugins/api-doc/README.md
Normal file
80
packages/plugins/api-doc/README.md
Normal file
@ -0,0 +1,80 @@
|
||||
# api-doc
|
||||
|
||||
English | [中文](./README.zh-CN.md)
|
||||
|
||||
|
||||
## Introduction
|
||||
|
||||
This plugin is based on `swagger` to write documentation.
|
||||
|
||||
## How to access the documentation
|
||||
|
||||
1. The access address in the plugin center is `{domain}/admin/settings/api-doc/documentation`
|
||||
2. The access address outside the plugin center is `{domain}/api-documentation`
|
||||
|
||||
## How to write swagger documentation
|
||||
|
||||
> The method in the plugin is the same
|
||||
|
||||
1. `src/swagger.{ts,json}`
|
||||
2. `src/swagger/index.{ts,json}`
|
||||
|
||||
The file paths above can all be traversed to write documentation. Just export your written documentation by default. An example is shown below:
|
||||
|
||||
```ts
|
||||
export default {
|
||||
info: {
|
||||
title: 'NocoBase API - Api-doc plugin',
|
||||
},
|
||||
tags: [],
|
||||
paths: {},
|
||||
components: {
|
||||
schemas: {}
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
Usually, you only need to write **info.title**, **tags**, **paths**, and **components**. Other information such as **server** and **info** are merged into our **base-swagger**.
|
||||
|
||||
Base swagger includes the following code:
|
||||
|
||||
```ts
|
||||
// base swagger
|
||||
export default {
|
||||
openapi: '3.0.3',
|
||||
info: {
|
||||
title: 'NocoBase API documentation',
|
||||
description: '',
|
||||
contact: {
|
||||
url: 'https://github.com/nocobase/nocobase/issues',
|
||||
},
|
||||
license: {
|
||||
name: 'Core packages are Apache 2.0 & Plugins packages are AGPL 3.0 licensed.',
|
||||
url: 'https://github.com/nocobase/nocobase#license',
|
||||
},
|
||||
},
|
||||
externalDocs: {
|
||||
description: 'Find out more about NocoBase',
|
||||
url: 'https://docs.nocobase.com/',
|
||||
},
|
||||
components: {
|
||||
securitySchemes: {
|
||||
'api-key': {
|
||||
type: 'http',
|
||||
scheme: 'bearer',
|
||||
},
|
||||
},
|
||||
},
|
||||
security: [
|
||||
{
|
||||
'api-key': [],
|
||||
},
|
||||
],
|
||||
};
|
||||
```
|
||||
|
||||
> Note that configurations that can only be obtained at runtime, such as the server and version fields, are not filled in the base-swagger.
|
||||
|
||||
You can also override these defaults. When writing the swagger documentation for your plugin, you should consider whether your plugin's documentation can be accessed independently.
|
||||
|
||||
For detailed swagger writing rules, please refer to the [official documentation](https://swagger.io/docs/specification/about/).
|
79
packages/plugins/api-doc/README.zh-CN.md
Normal file
79
packages/plugins/api-doc/README.zh-CN.md
Normal file
@ -0,0 +1,79 @@
|
||||
# api-doc
|
||||
|
||||
[English](./README.md) | 中文
|
||||
|
||||
## 简介
|
||||
|
||||
该插件基于 `swagger` 编写文档。
|
||||
|
||||
## 如何访问文档
|
||||
|
||||
1. 在插件中心访问地址是 `{domain}/admin/settings/api-doc/documentation`
|
||||
2. 在插件中心外访问地址是 `{domain}/api-documentation`
|
||||
|
||||
## 如何编写 swagger 文档
|
||||
|
||||
> 插件内方式一样
|
||||
|
||||
1. `src/swagger.{ts,json}`
|
||||
2. `src/swagger/index.{ts,json}`
|
||||
|
||||
上面的文件路径均可遍写文档,只需最后将您编写的文档默认导出即可,例子如下:
|
||||
|
||||
```ts
|
||||
export default {
|
||||
info: {
|
||||
title: 'NocoBase API - Auth plugin',
|
||||
},
|
||||
tags: [],
|
||||
paths: {},
|
||||
components: {
|
||||
schemas: {}
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
通常你只需要编写 **info.title**, **tags**, **paths**, **components** 即可其他内容如 **server**, **info** 的其他信息都合并我们的 **base-swagger**.
|
||||
|
||||
base swagger 包含如下代码
|
||||
```ts
|
||||
// base swagger
|
||||
export default {
|
||||
openapi: '3.0.3',
|
||||
info: {
|
||||
title: 'NocoBase API documentation',
|
||||
description: '',
|
||||
contact: {
|
||||
url: 'https://github.com/nocobase/nocobase/issues',
|
||||
},
|
||||
license: {
|
||||
name: 'Core packages are Apache 2.0 & Plugins packages are AGPL 3.0 licensed.',
|
||||
url: 'https://github.com/nocobase/nocobase#license',
|
||||
},
|
||||
},
|
||||
externalDocs: {
|
||||
description: 'Find out more about NocoBase',
|
||||
url: 'https://docs.nocobase.com/',
|
||||
},
|
||||
components: {
|
||||
securitySchemes: {
|
||||
'api-key': {
|
||||
type: 'http',
|
||||
scheme: 'bearer',
|
||||
},
|
||||
},
|
||||
},
|
||||
security: [
|
||||
{
|
||||
'api-key': [],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
> 注意:涉及到运行时才能获取的配置,并没有填写在 base-swagger 里,如 server, version 字段
|
||||
|
||||
这些默认的配置你同样也可以覆盖它,在你编写插件的 `swagger` 文档时,你需要视你的插件文档是独立的文档,可以被单独访问。
|
||||
|
||||
详细的 `swagger` 编写规则请参考[官方文档](https://swagger.io/docs/specification/about/)
|
2
packages/plugins/api-doc/client.d.ts
vendored
Normal file
2
packages/plugins/api-doc/client.d.ts
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
export * from './dist/client';
|
||||
export { default } from './dist/client';
|
1
packages/plugins/api-doc/client.js
Normal file
1
packages/plugins/api-doc/client.js
Normal file
@ -0,0 +1 @@
|
||||
module.exports = require('./dist/client/index.js');
|
23
packages/plugins/api-doc/package.json
Normal file
23
packages/plugins/api-doc/package.json
Normal file
@ -0,0 +1,23 @@
|
||||
{
|
||||
"name": "@nocobase/plugin-api-doc",
|
||||
"version": "0.12.0-alpha.5",
|
||||
"displayName": "API documentation",
|
||||
"displayName.zh-CN": "API 文档",
|
||||
"description": "",
|
||||
"license": "AGPL-3.0",
|
||||
"main": "./dist/server/index.js",
|
||||
"types": "./dist/server/index.d.ts",
|
||||
"devDependencies": {
|
||||
"swagger-ui-dist": "^5.3.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@nocobase/client": "0.x",
|
||||
"@nocobase/database": "0.x",
|
||||
"@nocobase/server": "0.x"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/nocobase/nocobase.git",
|
||||
"directory": "packages/plugins/api-doc"
|
||||
}
|
||||
}
|
3
packages/plugins/api-doc/server.d.ts
vendored
Normal file
3
packages/plugins/api-doc/server.d.ts
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
// @ts-nocheck
|
||||
export * from './dist/server';
|
||||
export { default } from './dist/server';
|
1
packages/plugins/api-doc/server.js
Normal file
1
packages/plugins/api-doc/server.js
Normal file
@ -0,0 +1 @@
|
||||
module.exports = require('./dist/server/index.js');
|
104
packages/plugins/api-doc/src/client/Document.tsx
Normal file
104
packages/plugins/api-doc/src/client/Document.tsx
Normal file
@ -0,0 +1,104 @@
|
||||
import { css, useAPIClient, useRequest } from '@nocobase/client';
|
||||
import { Select, Space, Spin, Typography } from 'antd';
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import { SwaggerUIBundle } from 'swagger-ui-dist';
|
||||
import 'swagger-ui-dist/swagger-ui.css';
|
||||
import { useTranslation } from '../locale';
|
||||
|
||||
const DESTINATION_URL_KEY = 'API_DOC:DESTINATION_URL_KEY';
|
||||
const getUrl = () => localStorage.getItem(DESTINATION_URL_KEY);
|
||||
|
||||
const Documentation = () => {
|
||||
const apiClient = useAPIClient();
|
||||
const { t } = useTranslation();
|
||||
const swaggerUIRef = useRef();
|
||||
|
||||
const { data: urls } = useRequest<{ data: { name: string; url: string }[] }>({ url: 'swagger:getUrls' });
|
||||
const requestInterceptor = (req) => {
|
||||
if (!req.headers['Authorization']) {
|
||||
req.headers['Authorization'] = `Bearer ${apiClient.auth.getToken()}`;
|
||||
}
|
||||
return req;
|
||||
};
|
||||
|
||||
const [destination, onDestinationChange] = useState<string>(getUrl());
|
||||
|
||||
useEffect(() => {
|
||||
if (destination) {
|
||||
localStorage.setItem(DESTINATION_URL_KEY, destination);
|
||||
}
|
||||
}, [destination]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!urls?.data?.length) return;
|
||||
|
||||
if (!destination || !urls.data.find((item) => item.url === getUrl())) {
|
||||
onDestinationChange(urls.data[0].url);
|
||||
}
|
||||
}, [destination, urls]);
|
||||
|
||||
useEffect(() => {
|
||||
SwaggerUIBundle({
|
||||
requestInterceptor,
|
||||
url: destination,
|
||||
domNode: swaggerUIRef.current,
|
||||
});
|
||||
}, [destination]);
|
||||
|
||||
if (!destination) {
|
||||
return <Spin />;
|
||||
}
|
||||
return (
|
||||
<Space
|
||||
direction="vertical"
|
||||
style={{
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className={css`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
max-width: 1460px;
|
||||
width: 100%;
|
||||
padding: 16px 20px;
|
||||
`}
|
||||
>
|
||||
<Typography.Text
|
||||
style={{
|
||||
whiteSpace: 'nowrap',
|
||||
}}
|
||||
strong
|
||||
>
|
||||
{t('Select a definition')}
|
||||
</Typography.Text>
|
||||
<Select
|
||||
showSearch
|
||||
value={destination}
|
||||
options={urls?.data}
|
||||
style={{
|
||||
width: '100%',
|
||||
}}
|
||||
fieldNames={{
|
||||
label: 'name',
|
||||
value: 'url',
|
||||
}}
|
||||
onChange={onDestinationChange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div ref={swaggerUIRef}></div>
|
||||
{/* <SwaggerUI url={destination} requestInterceptor={requestInterceptor} persistAuthorization deepLinking /> */}
|
||||
</Space>
|
||||
);
|
||||
};
|
||||
|
||||
export default Documentation;
|
74
packages/plugins/api-doc/src/client/index.tsx
Normal file
74
packages/plugins/api-doc/src/client/index.tsx
Normal file
@ -0,0 +1,74 @@
|
||||
import { RightOutlined } from '@ant-design/icons';
|
||||
import { Plugin, SettingsCenterProvider } from '@nocobase/client';
|
||||
import { Button, Tooltip } from 'antd';
|
||||
import React from 'react';
|
||||
import { useTranslation } from '../locale';
|
||||
import Documentation from './Document';
|
||||
|
||||
import { createStyles } from 'antd-style';
|
||||
|
||||
const DOCUMENTATION_PATH = '/api-documentation';
|
||||
|
||||
export const useStyles = createStyles(({ css, token }) => {
|
||||
return css`
|
||||
position: relative;
|
||||
background: ${token.colorBgContainer};
|
||||
padding: ${token.paddingMD}px;
|
||||
.open-tab {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
}
|
||||
`;
|
||||
});
|
||||
|
||||
const SCDocumentation = () => {
|
||||
const { styles } = useStyles();
|
||||
return (
|
||||
<div className={styles}>
|
||||
<div className="open-tab">
|
||||
<Tooltip title="open in new tab">
|
||||
<a href={DOCUMENTATION_PATH} target="_blank" rel="noreferrer">
|
||||
<Button size="small" icon={<RightOutlined />} />
|
||||
</a>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<Documentation />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const APIDocumentationProvider = React.memo((props) => {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<SettingsCenterProvider
|
||||
settings={{
|
||||
['api-doc']: {
|
||||
title: t('API documentation'),
|
||||
icon: 'BookOutlined',
|
||||
tabs: {
|
||||
documentation: {
|
||||
title: t('Documentation'),
|
||||
component: SCDocumentation,
|
||||
},
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
{props.children}
|
||||
</SettingsCenterProvider>
|
||||
);
|
||||
});
|
||||
APIDocumentationProvider.displayName = 'APIDocumentationProvider';
|
||||
|
||||
export class APIDocumentationPlugin extends Plugin {
|
||||
async load() {
|
||||
this.app.use(APIDocumentationProvider);
|
||||
this.app.router.add('api-documentation', {
|
||||
path: DOCUMENTATION_PATH,
|
||||
Component: Documentation,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default APIDocumentationPlugin;
|
1
packages/plugins/api-doc/src/index.ts
Normal file
1
packages/plugins/api-doc/src/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export { default } from './server';
|
1
packages/plugins/api-doc/src/locale/en-US.ts
Normal file
1
packages/plugins/api-doc/src/locale/en-US.ts
Normal file
@ -0,0 +1 @@
|
||||
export default {};
|
16
packages/plugins/api-doc/src/locale/index.ts
Normal file
16
packages/plugins/api-doc/src/locale/index.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import { i18n } from '@nocobase/client';
|
||||
import { useTranslation as useT } from 'react-i18next';
|
||||
|
||||
export const NAMESPACE = 'api-doc';
|
||||
|
||||
export function lang(key: string) {
|
||||
return i18n.t(key, { ns: NAMESPACE });
|
||||
}
|
||||
|
||||
export function generateNTemplate(key: string) {
|
||||
return `{{t('${key}', { ns: '${NAMESPACE}' })}}`;
|
||||
}
|
||||
|
||||
export function useTranslation() {
|
||||
return useT(NAMESPACE);
|
||||
}
|
5
packages/plugins/api-doc/src/locale/zh-CN.ts
Normal file
5
packages/plugins/api-doc/src/locale/zh-CN.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export default {
|
||||
'API documentation': 'Api 文档',
|
||||
'Documentation': '文档',
|
||||
'Select a definition': '选择端点',
|
||||
};
|
1
packages/plugins/api-doc/src/server/index.ts
Normal file
1
packages/plugins/api-doc/src/server/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export { default } from './server';
|
48
packages/plugins/api-doc/src/server/server.ts
Normal file
48
packages/plugins/api-doc/src/server/server.ts
Normal file
@ -0,0 +1,48 @@
|
||||
import { Context } from '@nocobase/actions';
|
||||
import { Plugin } from '@nocobase/server';
|
||||
import { SwaggerManager } from './swagger';
|
||||
|
||||
export default class APIDoc extends Plugin {
|
||||
swagger: SwaggerManager;
|
||||
constructor(app, options) {
|
||||
super(app, options);
|
||||
this.swagger = new SwaggerManager(this);
|
||||
}
|
||||
beforeLoad() {}
|
||||
async load() {
|
||||
this.app.resource({
|
||||
name: 'swagger',
|
||||
type: 'single',
|
||||
actions: {
|
||||
getUrls: async (ctx: Context, next) => {
|
||||
// ctx.withoutDataWrapping = true;
|
||||
ctx.body = await this.swagger.getUrls();
|
||||
await next();
|
||||
},
|
||||
get: async (ctx: Context, next) => {
|
||||
ctx.withoutDataWrapping = true;
|
||||
const { ns } = ctx.action.params;
|
||||
if (!ns) {
|
||||
ctx.body = await this.swagger.getSwagger();
|
||||
return;
|
||||
}
|
||||
const [type, index] = ns.split('/');
|
||||
if (type === 'core') {
|
||||
ctx.body = await this.swagger.getCoreSwagger();
|
||||
} else if (type === 'plugins') {
|
||||
ctx.body = await this.swagger.getPluginsSwagger(index);
|
||||
} else if (type === 'collections') {
|
||||
ctx.body = await this.swagger.getCollectionsSwagger(index);
|
||||
}
|
||||
await next();
|
||||
},
|
||||
},
|
||||
only: ['get', 'getUrls'],
|
||||
});
|
||||
this.app.acl.allow('swagger', ['get', 'getUrls'], 'loggedIn');
|
||||
this.app.acl.registerSnippet({
|
||||
name: ['pm', this.name, 'documentation'].join('.'),
|
||||
actions: ['swagger:*'],
|
||||
});
|
||||
}
|
||||
}
|
50
packages/plugins/api-doc/src/server/swagger/base-swagger.ts
Normal file
50
packages/plugins/api-doc/src/server/swagger/base-swagger.ts
Normal file
@ -0,0 +1,50 @@
|
||||
export default {
|
||||
openapi: '3.0.3',
|
||||
info: {
|
||||
title: 'NocoBase API documentation',
|
||||
description: '',
|
||||
contact: {
|
||||
url: 'https://github.com/nocobase/nocobase/issues',
|
||||
},
|
||||
license: {
|
||||
name: 'Core packages are Apache 2.0 & Plugins packages are AGPL 3.0 licensed.',
|
||||
url: 'https://github.com/nocobase/nocobase#license',
|
||||
},
|
||||
},
|
||||
externalDocs: {
|
||||
description: 'Find out more about NocoBase',
|
||||
url: 'https://docs.nocobase.com/api/http',
|
||||
},
|
||||
components: {
|
||||
securitySchemes: {
|
||||
'api-key': {
|
||||
type: 'http',
|
||||
scheme: 'bearer',
|
||||
},
|
||||
},
|
||||
schemas: {
|
||||
error: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
errors: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
message: {
|
||||
description: '错误信息',
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
security: [
|
||||
{
|
||||
'api-key': [],
|
||||
},
|
||||
],
|
||||
};
|
@ -0,0 +1,37 @@
|
||||
import { Field } from '@nocobase/database';
|
||||
|
||||
const fieldTypeMap = {
|
||||
bigint: {
|
||||
type: 'integer',
|
||||
format: 'int64',
|
||||
},
|
||||
datetime: {
|
||||
type: 'string',
|
||||
format: 'date-time',
|
||||
},
|
||||
string: {
|
||||
type: 'string',
|
||||
},
|
||||
text: {
|
||||
type: 'string',
|
||||
},
|
||||
|
||||
jsontype: {
|
||||
type: 'string',
|
||||
},
|
||||
};
|
||||
|
||||
function getTypeByField(field: Field) {
|
||||
const fieldType = field.dataType.toString().toLowerCase();
|
||||
const fieldAttributes = fieldTypeMap[fieldType];
|
||||
|
||||
if (!fieldAttributes) {
|
||||
return {
|
||||
type: 'string',
|
||||
};
|
||||
}
|
||||
|
||||
return fieldAttributes;
|
||||
}
|
||||
|
||||
export { fieldTypeMap, getTypeByField };
|
@ -0,0 +1,10 @@
|
||||
import { Collection } from '@nocobase/database';
|
||||
import schemas from './schemas';
|
||||
import parameters from './parameters';
|
||||
|
||||
export default (collection: Collection, options) => {
|
||||
return {
|
||||
...schemas(collection, options),
|
||||
...parameters(collection),
|
||||
};
|
||||
};
|
@ -0,0 +1,173 @@
|
||||
import { Collection } from '@nocobase/database';
|
||||
import { getTypeByField } from './field-type-map';
|
||||
|
||||
export default (collection: Collection) => {
|
||||
const primaryKey = collection.model.primaryKeyAttribute;
|
||||
|
||||
const parameters = {};
|
||||
|
||||
if (primaryKey) {
|
||||
Object.assign(parameters, {
|
||||
collectionIndex: {
|
||||
required: true,
|
||||
name: 'collectionIndex',
|
||||
in: 'path',
|
||||
description: 'collection index',
|
||||
schema: getTypeByField(collection.fields.get(primaryKey)),
|
||||
},
|
||||
filterByTk: {
|
||||
name: 'filterByTk',
|
||||
in: 'query',
|
||||
description: 'filter by TK(default by ID)',
|
||||
schema: getTypeByField(collection.fields.get(primaryKey)),
|
||||
},
|
||||
|
||||
filterByTks: {
|
||||
name: 'filterByTk',
|
||||
in: 'query',
|
||||
description: 'filter by TKs(default by ID), example: `1,2,3`',
|
||||
schema: {
|
||||
type: 'array',
|
||||
items: getTypeByField(collection.fields.get(primaryKey)),
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
Object.assign(parameters, {
|
||||
filter: {
|
||||
name: 'filter',
|
||||
in: 'query',
|
||||
description: 'filter items',
|
||||
schema: {
|
||||
type: 'object',
|
||||
},
|
||||
},
|
||||
sort: {
|
||||
name: 'sort',
|
||||
in: 'query',
|
||||
description: 'sort items by fields, example: `-field1,-field2,field3`',
|
||||
schema: {
|
||||
oneOf: [
|
||||
{
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'string',
|
||||
},
|
||||
example: ['-id', 'createdAt'],
|
||||
},
|
||||
{
|
||||
type: 'string',
|
||||
example: '-id,createdAt',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
fields: {
|
||||
name: 'fields',
|
||||
in: 'query',
|
||||
description: 'select fields, example: `field1,field2`',
|
||||
schema: {
|
||||
oneOf: [
|
||||
{
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'string',
|
||||
},
|
||||
example: ['id', 'createdAt'],
|
||||
},
|
||||
{
|
||||
type: 'string',
|
||||
example: 'id,createdAt',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
|
||||
except: {
|
||||
name: 'except',
|
||||
in: 'query',
|
||||
description: 'except fields in results, example: `field1,field2`',
|
||||
schema: {
|
||||
oneOf: [
|
||||
{
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'string',
|
||||
},
|
||||
example: ['id', 'createdAt'],
|
||||
},
|
||||
{
|
||||
type: 'string',
|
||||
example: 'id,createdAt',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
|
||||
appends: {
|
||||
name: 'appends',
|
||||
in: 'query',
|
||||
description: 'append associations in results, example: `assoc1,assoc2`',
|
||||
schema: {
|
||||
oneOf: [
|
||||
{
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'string',
|
||||
},
|
||||
example: ['id', 'createdAt'],
|
||||
},
|
||||
{
|
||||
type: 'string',
|
||||
example: 'id,createdAt',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
whitelist: {
|
||||
name: 'whitelist',
|
||||
in: 'query',
|
||||
description: 'whitelist for fields changes, example: `field1,field2`',
|
||||
schema: {
|
||||
oneOf: [
|
||||
{
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'string',
|
||||
},
|
||||
example: ['id', 'createdAt'],
|
||||
},
|
||||
{
|
||||
type: 'string',
|
||||
example: 'id,createdAt',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
blacklist: {
|
||||
name: 'blacklist',
|
||||
in: 'query',
|
||||
description: 'blacklist for fields changes, example: `field1,field2`',
|
||||
schema: {
|
||||
oneOf: [
|
||||
{
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'string',
|
||||
},
|
||||
example: ['id', 'createdAt'],
|
||||
},
|
||||
{
|
||||
type: 'string',
|
||||
example: 'id,createdAt',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
parameters,
|
||||
};
|
||||
};
|
@ -0,0 +1,101 @@
|
||||
import { Collection, Field, RelationField } from '@nocobase/database';
|
||||
import { getTypeByField } from './field-type-map';
|
||||
import { associationFields } from '../paths/associations';
|
||||
|
||||
function getCollectionReadOnlyFields(collection: Collection) {
|
||||
const readOnlyFields = [];
|
||||
|
||||
// autoIncrement field is readOnly
|
||||
const primaryKey = collection.model.primaryKeyAttribute;
|
||||
|
||||
if (primaryKey) {
|
||||
const primaryField = collection.fields.get(primaryKey);
|
||||
if (primaryField && primaryField.options?.autoIncrement) {
|
||||
readOnlyFields.push(primaryKey);
|
||||
}
|
||||
}
|
||||
|
||||
for (const [fieldName, field] of collection.fields) {
|
||||
if (field.type == 'sort') {
|
||||
readOnlyFields.push(fieldName);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (field.type == 'context') {
|
||||
readOnlyFields.push(fieldName);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (
|
||||
[
|
||||
'createdAt',
|
||||
'updatedAt',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
'sort',
|
||||
'created_by_id',
|
||||
'createdById',
|
||||
'updatedById',
|
||||
'updated_by_id',
|
||||
].includes(fieldName)
|
||||
) {
|
||||
readOnlyFields.push(fieldName);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return readOnlyFields;
|
||||
}
|
||||
|
||||
function collectionToSchema(collection: Collection) {
|
||||
return {
|
||||
[collection.name]: {
|
||||
type: 'object',
|
||||
properties: Array.from(collection.fields)
|
||||
.filter(([key, value]) => {
|
||||
return !(value instanceof RelationField);
|
||||
})
|
||||
.reduce((obj, [key, value]) => {
|
||||
obj[key] = {
|
||||
...getFieldTypeAttributes(value),
|
||||
};
|
||||
return obj;
|
||||
}, {}),
|
||||
},
|
||||
[`${collection.name}.form`]: {
|
||||
allOf: [
|
||||
{
|
||||
$ref: `#/components/schemas/${collection.name}`,
|
||||
},
|
||||
{
|
||||
type: 'object',
|
||||
properties: getCollectionReadOnlyFields(collection).reduce((obj, key) => {
|
||||
obj[key] = {
|
||||
readOnly: true,
|
||||
};
|
||||
return obj;
|
||||
}, {}),
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export default (collection: Collection, options) => {
|
||||
const associations = associationFields(collection);
|
||||
const associationsTarget = associations.map((field) => collection.db.getCollection(field.target));
|
||||
|
||||
const schemas = collectionToSchema(collection);
|
||||
if (options.withAssociation) {
|
||||
for (const target of associationsTarget) {
|
||||
Object.assign(schemas, collectionToSchema(target));
|
||||
}
|
||||
}
|
||||
return {
|
||||
schemas,
|
||||
};
|
||||
};
|
||||
|
||||
function getFieldTypeAttributes(field: Field) {
|
||||
return getTypeByField(field);
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
import { Collection } from '@nocobase/database';
|
||||
import paths from './paths';
|
||||
import components from './components';
|
||||
import tags from './tags';
|
||||
|
||||
function collectionToSwaggerObject(collection: Collection, options) {
|
||||
return {
|
||||
paths: paths(collection, options),
|
||||
components: components(collection, options),
|
||||
tags: tags(collection, options),
|
||||
};
|
||||
}
|
||||
|
||||
export default collectionToSwaggerObject;
|
@ -0,0 +1,33 @@
|
||||
import {
|
||||
BelongsToField,
|
||||
BelongsToManyField,
|
||||
Collection,
|
||||
HasManyField,
|
||||
HasOneField,
|
||||
RelationField,
|
||||
} from '@nocobase/database';
|
||||
import multipleAssociation from './multiple-association';
|
||||
import singleAssociation from './single-association';
|
||||
|
||||
export default (collection: Collection) => {
|
||||
return associationFields(collection)
|
||||
.map((field) => {
|
||||
if (field.type === 'belongsToMany' || field.type === 'hasMany') {
|
||||
return multipleAssociation(collection, field as BelongsToManyField | HasManyField);
|
||||
}
|
||||
|
||||
return singleAssociation(collection, field as BelongsToField | HasOneField);
|
||||
})
|
||||
.reduce((obj, item) => {
|
||||
return {
|
||||
...obj,
|
||||
...item,
|
||||
};
|
||||
}, {});
|
||||
};
|
||||
|
||||
export function associationFields(collection: Collection): Array<RelationField> {
|
||||
return Array.from(collection.fields.values())
|
||||
.filter((field) => field instanceof RelationField)
|
||||
.filter((field) => field.name !== 'createdBy' && field.name !== 'updatedBy');
|
||||
}
|
@ -0,0 +1,117 @@
|
||||
import { BelongsToManyField, Collection, HasManyField } from '@nocobase/database';
|
||||
import {
|
||||
CreateActionTemplate,
|
||||
DestroyActionTemplate,
|
||||
GetActionTemplate,
|
||||
ListActionTemplate,
|
||||
MoveActionTemplate,
|
||||
UpdateActionTemplate,
|
||||
} from '../collection';
|
||||
import { hasSortField } from '../index';
|
||||
|
||||
export function appendCollectionIndexParams(apiDef: object) {
|
||||
for (const action of Object.keys(apiDef)) {
|
||||
const parameters = apiDef[action]['parameters'];
|
||||
|
||||
if (!parameters) {
|
||||
apiDef[action]['parameters'] = [];
|
||||
}
|
||||
|
||||
apiDef[action]['parameters'].unshift({
|
||||
$ref: '#/components/parameters/collectionIndex',
|
||||
});
|
||||
}
|
||||
return apiDef;
|
||||
}
|
||||
|
||||
export default (collection: Collection, relationField: HasManyField | BelongsToManyField) => {
|
||||
const options = {
|
||||
collection,
|
||||
relationField,
|
||||
};
|
||||
|
||||
const targetCollection = collection.db.getCollection(relationField.target);
|
||||
|
||||
const paths = {
|
||||
[`/${collection.name}/{collectionIndex}/${relationField.name}:list`]: appendCollectionIndexParams(
|
||||
ListActionTemplate(options),
|
||||
),
|
||||
[`/${collection.name}/{collectionIndex}/${relationField.name}:get`]: appendCollectionIndexParams(
|
||||
GetActionTemplate(options),
|
||||
),
|
||||
[`/${collection.name}/{collectionIndex}/${relationField.name}:create`]: appendCollectionIndexParams(
|
||||
CreateActionTemplate(options),
|
||||
),
|
||||
[`/${collection.name}/{collectionIndex}/${relationField.name}:update`]: appendCollectionIndexParams(
|
||||
UpdateActionTemplate(options),
|
||||
),
|
||||
[`/${collection.name}/{collectionIndex}/${relationField.name}:destroy`]: appendCollectionIndexParams(
|
||||
DestroyActionTemplate(options),
|
||||
),
|
||||
|
||||
[`/${collection.name}/{collectionIndex}/${relationField.name}:set`]: appendCollectionIndexParams({
|
||||
post: {
|
||||
tags: [`${collection.name}.${relationField.name}`],
|
||||
summary: 'Set or reset associations',
|
||||
parameters: [
|
||||
{
|
||||
$ref: '#/components/parameters/filterByTk',
|
||||
},
|
||||
{
|
||||
$ref: '#/components/parameters/filterByTks',
|
||||
},
|
||||
],
|
||||
responses: {
|
||||
'200': {
|
||||
description: 'OK',
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
|
||||
[`/${collection.name}/{collectionIndex}/${relationField.name}:remove`]: appendCollectionIndexParams({
|
||||
post: {
|
||||
tags: [`${collection.name}.${relationField.name}`],
|
||||
summary: 'Detach record',
|
||||
parameters: [
|
||||
{
|
||||
$ref: '#/components/parameters/filterByTk',
|
||||
},
|
||||
{
|
||||
$ref: '#/components/parameters/filterByTks',
|
||||
},
|
||||
],
|
||||
responses: {
|
||||
'200': {
|
||||
description: 'OK',
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
|
||||
[`/${collection.name}/{collectionIndex}/${relationField.name}:toggle`]: appendCollectionIndexParams({
|
||||
post: {
|
||||
tags: [`${collection.name}.${relationField.name}`],
|
||||
summary: 'Attach or detach record',
|
||||
parameters: [
|
||||
{
|
||||
$ref: '#/components/parameters/filterByTk',
|
||||
},
|
||||
],
|
||||
responses: {
|
||||
'200': {
|
||||
description: 'OK',
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
};
|
||||
|
||||
if (hasSortField(collection)) {
|
||||
paths[`/${collection.name}/{collectionIndex}/${relationField.name}:move`] = appendCollectionIndexParams(
|
||||
MoveActionTemplate(options),
|
||||
);
|
||||
}
|
||||
|
||||
return paths;
|
||||
};
|
@ -0,0 +1,92 @@
|
||||
import { BelongsToField, Collection, HasOneField } from '@nocobase/database';
|
||||
import { appendCollectionIndexParams } from './multiple-association';
|
||||
import { CreateActionTemplate, DestroyActionTemplate, GetActionTemplate, UpdateActionTemplate } from '../collection';
|
||||
|
||||
function removeFilterByTkParams(apiDoc: object) {
|
||||
for (const action of Object.values(apiDoc)) {
|
||||
if (action.parameters) {
|
||||
action.parameters = action.parameters.filter((param: any) => {
|
||||
if (param.$ref) {
|
||||
return param.$ref !== '#/components/parameters/filterByTk';
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return apiDoc;
|
||||
}
|
||||
|
||||
const parametersShouldRemove = [
|
||||
'#/components/parameters/filterByTk',
|
||||
'#/components/parameters/filter',
|
||||
'#/components/parameters/sort',
|
||||
];
|
||||
|
||||
function filterSingleAssociationParams(apiDoc: object) {
|
||||
for (const action of Object.values(apiDoc)) {
|
||||
if (action.parameters) {
|
||||
action.parameters = action.parameters.filter((param: any) => {
|
||||
if (param.$ref) {
|
||||
return !parametersShouldRemove.includes(param.$ref);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return apiDoc;
|
||||
}
|
||||
|
||||
export default (collection: Collection, relationField: HasOneField | BelongsToField) => {
|
||||
const options = {
|
||||
collection,
|
||||
relationField,
|
||||
};
|
||||
|
||||
return {
|
||||
[`/${collection.name}/{collectionIndex}/${relationField.name}:get`]: removeFilterByTkParams(
|
||||
filterSingleAssociationParams(appendCollectionIndexParams(GetActionTemplate(options))),
|
||||
),
|
||||
[`/${collection.name}/{collectionIndex}/${relationField.name}:set`]: appendCollectionIndexParams({
|
||||
post: {
|
||||
tags: [`${collection.name}.${relationField.name}`],
|
||||
summary: 'Associate a record',
|
||||
parameters: [
|
||||
{
|
||||
name: 'tk',
|
||||
in: 'query',
|
||||
description: 'targetKey',
|
||||
schema: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
],
|
||||
responses: {
|
||||
'200': {
|
||||
description: 'OK',
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
|
||||
[`/${collection.name}/{collectionIndex}/${relationField.name}:remove`]: appendCollectionIndexParams({
|
||||
post: {
|
||||
tags: [`${collection.name}.${relationField.name}`],
|
||||
summary: 'Disassociate the relationship record',
|
||||
responses: {
|
||||
'200': {
|
||||
description: 'OK',
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
[`/${collection.name}/{collectionIndex}/${relationField.name}:update`]: removeFilterByTkParams(
|
||||
filterSingleAssociationParams(appendCollectionIndexParams(UpdateActionTemplate(options))),
|
||||
),
|
||||
[`/${collection.name}/{collectionIndex}/${relationField.name}:create`]: removeFilterByTkParams(
|
||||
filterSingleAssociationParams(appendCollectionIndexParams(CreateActionTemplate(options))),
|
||||
),
|
||||
[`/${collection.name}/{collectionIndex}/${relationField.name}:destroy`]: removeFilterByTkParams(
|
||||
filterSingleAssociationParams(appendCollectionIndexParams(DestroyActionTemplate(options))),
|
||||
),
|
||||
};
|
||||
};
|
@ -0,0 +1,340 @@
|
||||
import { Collection, RelationField } from '@nocobase/database';
|
||||
import { hasSortField } from './index';
|
||||
|
||||
type TemplateOptions = {
|
||||
collection: Collection;
|
||||
relationField?: RelationField;
|
||||
};
|
||||
|
||||
export function relationTypeToString(field: RelationField) {
|
||||
return {
|
||||
belongsTo: 'Many to one',
|
||||
hasOne: 'One to one',
|
||||
hasMany: 'One to many',
|
||||
belongsToMany: 'Many to many',
|
||||
}[field.type];
|
||||
}
|
||||
|
||||
export function ListActionTemplate({ collection, relationField }: TemplateOptions) {
|
||||
return {
|
||||
get: {
|
||||
tags: [relationField ? `${collection.name}.${relationField.name}` : collection.name],
|
||||
summary: relationField
|
||||
? `Return a list of ${relationTypeToString(relationField)} relationship`
|
||||
: `Returns a list of the collection`,
|
||||
parameters: [
|
||||
{
|
||||
name: 'page',
|
||||
in: 'query',
|
||||
description: 'page number',
|
||||
required: false,
|
||||
schema: {
|
||||
type: 'integer',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'pageSize',
|
||||
in: 'query',
|
||||
description: 'page size',
|
||||
required: false,
|
||||
schema: {
|
||||
type: 'integer',
|
||||
},
|
||||
},
|
||||
{
|
||||
$ref: '#/components/parameters/filter',
|
||||
},
|
||||
{
|
||||
$ref: '#/components/parameters/sort',
|
||||
},
|
||||
{
|
||||
$ref: '#/components/parameters/fields',
|
||||
},
|
||||
{
|
||||
$ref: '#/components/parameters/appends',
|
||||
},
|
||||
{
|
||||
$ref: '#/components/parameters/except',
|
||||
},
|
||||
],
|
||||
responses: {
|
||||
'200': {
|
||||
description: 'OK',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
data: {
|
||||
type: 'array',
|
||||
items: {
|
||||
$ref: `#/components/schemas/${collection.name}`,
|
||||
},
|
||||
},
|
||||
meta: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
count: {
|
||||
type: 'integer',
|
||||
description: 'total count',
|
||||
},
|
||||
page: {
|
||||
type: 'integer',
|
||||
description: 'current page',
|
||||
},
|
||||
pageSize: {
|
||||
type: 'integer',
|
||||
description: 'items count per page',
|
||||
},
|
||||
totalPage: {
|
||||
type: 'integer',
|
||||
description: 'total page',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function GetActionTemplate(options: TemplateOptions) {
|
||||
const { collection, relationField } = options;
|
||||
|
||||
return {
|
||||
get: {
|
||||
tags: [relationField ? `${collection.name}.${relationField.name}` : collection.name],
|
||||
summary: `Return a record${relationField ? ` of ${relationTypeToString(relationField)}` : ''}`,
|
||||
parameters: [
|
||||
{
|
||||
$ref: '#/components/parameters/filterByTk',
|
||||
},
|
||||
{
|
||||
$ref: '#/components/parameters/filter',
|
||||
},
|
||||
{
|
||||
$ref: '#/components/parameters/sort',
|
||||
},
|
||||
{
|
||||
$ref: '#/components/parameters/fields',
|
||||
},
|
||||
{
|
||||
$ref: '#/components/parameters/appends',
|
||||
},
|
||||
{
|
||||
$ref: '#/components/parameters/except',
|
||||
},
|
||||
],
|
||||
responses: {
|
||||
'200': {
|
||||
description: 'OK',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
data: {
|
||||
$ref: `#/components/schemas/${collection.name}`,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function CreateActionTemplate(options: TemplateOptions) {
|
||||
const { collection, relationField } = options;
|
||||
return {
|
||||
post: {
|
||||
tags: [relationField ? `${collection.name}.${relationField.name}` : collection.name],
|
||||
summary: relationField ? `Create and associate a record` : `Create record`,
|
||||
parameters: [
|
||||
{
|
||||
$ref: '#/components/parameters/whitelist',
|
||||
},
|
||||
{
|
||||
$ref: '#/components/parameters/blacklist',
|
||||
},
|
||||
],
|
||||
requestBody: {
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
$ref: `#/components/schemas/${collection.name}.form`,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
responses: {
|
||||
'200': {
|
||||
description: 'OK',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
data: {
|
||||
$ref: `#/components/schemas/${collection.name}`,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function UpdateActionTemplate(options: TemplateOptions) {
|
||||
const { collection, relationField } = options;
|
||||
return {
|
||||
post: {
|
||||
tags: [relationField ? `${collection.name}.${relationField.name}` : collection.name],
|
||||
summary: relationField ? `Update the relationship record` : `Update record`,
|
||||
parameters: [
|
||||
{
|
||||
$ref: '#/components/parameters/filterByTk',
|
||||
},
|
||||
{
|
||||
$ref: '#/components/parameters/filter',
|
||||
},
|
||||
{
|
||||
$ref: '#/components/parameters/whitelist',
|
||||
},
|
||||
{
|
||||
$ref: '#/components/parameters/blacklist',
|
||||
},
|
||||
],
|
||||
requestBody: {
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
$ref: `#/components/schemas/${collection.name}.form`,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
responses: {
|
||||
'200': {
|
||||
description: 'OK',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
data: {
|
||||
$ref: `#/components/schemas/${collection.name}`,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function DestroyActionTemplate(options: TemplateOptions) {
|
||||
const { collection, relationField } = options;
|
||||
return {
|
||||
post: {
|
||||
tags: [relationField ? `${collection.name}.${relationField.name}` : collection.name],
|
||||
summary: relationField ? `Destroy and disassociate the relationship record` : `Delete record`,
|
||||
parameters: [
|
||||
{
|
||||
$ref: '#/components/parameters/filterByTk',
|
||||
},
|
||||
{
|
||||
$ref: '#/components/parameters/filter',
|
||||
},
|
||||
],
|
||||
responses: {
|
||||
'200': {
|
||||
description: 'OK',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function MoveActionTemplate(options: TemplateOptions) {
|
||||
const { collection, relationField } = options;
|
||||
|
||||
return {
|
||||
post: {
|
||||
tags: [relationField ? `${collection.name}.${relationField.name}` : collection.name],
|
||||
summary: relationField ? `Move the relationship record` : `Move record`,
|
||||
parameters: [
|
||||
{
|
||||
name: 'sourceId',
|
||||
in: 'query',
|
||||
description: 'source id',
|
||||
schema: { type: 'string' },
|
||||
},
|
||||
{
|
||||
name: 'targetId',
|
||||
in: 'query',
|
||||
description: 'move target id',
|
||||
schema: { type: 'string' },
|
||||
},
|
||||
|
||||
{
|
||||
name: 'method',
|
||||
in: 'query',
|
||||
description: 'move method, insertAfter or insertBefore',
|
||||
schema: { type: 'string' },
|
||||
},
|
||||
{
|
||||
name: 'sortField',
|
||||
in: 'query',
|
||||
description: 'sort field name, default is sort',
|
||||
schema: { type: 'string' },
|
||||
},
|
||||
{
|
||||
name: 'targetScope',
|
||||
in: 'query',
|
||||
description: 'move target scope',
|
||||
schema: { type: 'string' },
|
||||
},
|
||||
{
|
||||
name: 'sticky',
|
||||
in: 'query',
|
||||
description: 'sticky to top',
|
||||
schema: { type: 'boolean' },
|
||||
},
|
||||
],
|
||||
responses: {
|
||||
'200': {
|
||||
description: 'OK',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export default (collection: Collection) => {
|
||||
const options = { collection };
|
||||
const apiDoc: any = {
|
||||
[`/${collection.name}:list`]: ListActionTemplate(options),
|
||||
[`/${collection.name}:get`]: GetActionTemplate(options),
|
||||
[`/${collection.name}:create`]: CreateActionTemplate(options),
|
||||
[`/${collection.name}:update`]: UpdateActionTemplate(options),
|
||||
[`/${collection.name}:destroy`]: DestroyActionTemplate(options),
|
||||
};
|
||||
|
||||
if (hasSortField(collection)) {
|
||||
apiDoc[`/${collection.name}:move`] = MoveActionTemplate(options);
|
||||
}
|
||||
|
||||
return apiDoc;
|
||||
};
|
@ -0,0 +1,23 @@
|
||||
import { Collection } from '@nocobase/database';
|
||||
import list from './collection';
|
||||
import associations from './associations';
|
||||
|
||||
export default (collection: Collection, options) => {
|
||||
const paths = list(collection);
|
||||
|
||||
if (options.withAssociation) {
|
||||
Object.assign(paths, associations(collection));
|
||||
}
|
||||
|
||||
return paths;
|
||||
};
|
||||
|
||||
export function hasSortField(collection: Collection) {
|
||||
for (const field of collection.fields.values()) {
|
||||
if (field.type === 'sort') {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
import { Collection } from '@nocobase/database';
|
||||
import { associationFields } from '../paths/associations';
|
||||
import { relationTypeToString } from '../paths/collection';
|
||||
|
||||
export default (collection: Collection, options) => {
|
||||
const associations = associationFields(collection);
|
||||
|
||||
const tags = [
|
||||
{
|
||||
name: collection.name,
|
||||
description: collection.options?.title || collection.name,
|
||||
},
|
||||
];
|
||||
|
||||
if (options.withAssociation) {
|
||||
for (const field of associations) {
|
||||
tags.push({
|
||||
name: `${collection.name}.${field.name}`,
|
||||
description: `${relationTypeToString(field)} relationship, ${collection.options?.title || collection.name}/${
|
||||
field.options?.uiSchema?.title || field.name
|
||||
}`,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return tags;
|
||||
};
|
10
packages/plugins/api-doc/src/server/swagger/constants.ts
Normal file
10
packages/plugins/api-doc/src/server/swagger/constants.ts
Normal file
@ -0,0 +1,10 @@
|
||||
export const SchemaTypeMapping = {
|
||||
uid: 'string',
|
||||
json: 'object',
|
||||
jsonb: 'object',
|
||||
text: 'string',
|
||||
bigInt: 'integer',
|
||||
sort: 'integer',
|
||||
context: 'string',
|
||||
set: 'array',
|
||||
};
|
109
packages/plugins/api-doc/src/server/swagger/helpers.ts
Normal file
109
packages/plugins/api-doc/src/server/swagger/helpers.ts
Normal file
@ -0,0 +1,109 @@
|
||||
export const createDefaultActionSwagger = ({ collection }) => {
|
||||
const responses = {
|
||||
default: {
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
$ref: `#/components/schemas/${collection.name}`,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
const requestBody = {
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
$ref: `#/components/schemas/${collection.name}`,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
return {
|
||||
list: {
|
||||
method: 'get',
|
||||
responses,
|
||||
},
|
||||
create: {
|
||||
method: 'post',
|
||||
requestBody,
|
||||
},
|
||||
get: {
|
||||
method: 'get',
|
||||
responses,
|
||||
},
|
||||
update: {
|
||||
method: 'put',
|
||||
requestBody,
|
||||
responses,
|
||||
},
|
||||
destroy: {
|
||||
method: 'delete',
|
||||
responses,
|
||||
},
|
||||
add: {
|
||||
method: 'post',
|
||||
requestBody,
|
||||
responses,
|
||||
},
|
||||
set: {
|
||||
method: 'post',
|
||||
requestBody,
|
||||
},
|
||||
remove: {
|
||||
method: 'delete',
|
||||
responses,
|
||||
},
|
||||
toggle: {
|
||||
method: 'post',
|
||||
requestBody,
|
||||
responses,
|
||||
},
|
||||
move: {
|
||||
method: 'post',
|
||||
requestBody,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const getInterfaceCollection = (options: Record<string, any>) => {
|
||||
const accessors = {
|
||||
// 常规 actions
|
||||
list: 'list',
|
||||
create: 'create',
|
||||
get: 'get',
|
||||
update: 'update',
|
||||
delete: 'destroy',
|
||||
// associate 操作
|
||||
add: 'add',
|
||||
set: 'set',
|
||||
remove: 'remove',
|
||||
toggle: 'toggle',
|
||||
move: 'move',
|
||||
...(options.accessors || {}),
|
||||
};
|
||||
|
||||
const single = {
|
||||
'/{resourceName}': [accessors.list, accessors.create, accessors.delete],
|
||||
'/{resourceName}/{resourceIndex}': [accessors.get, accessors.update, accessors.delete],
|
||||
'/{associatedName}/{associatedIndex}/{resourceName}': [
|
||||
accessors.list,
|
||||
accessors.create,
|
||||
accessors.delete,
|
||||
accessors.toggle,
|
||||
accessors.add,
|
||||
accessors.remove,
|
||||
],
|
||||
'/{associatedName}/{associatedIndex}/{resourceName}/{resourceIndex}': [
|
||||
accessors.get,
|
||||
accessors.update,
|
||||
accessors.delete,
|
||||
accessors.remove,
|
||||
accessors.toggle,
|
||||
accessors.set,
|
||||
accessors.move,
|
||||
],
|
||||
};
|
||||
|
||||
return single;
|
||||
};
|
225
packages/plugins/api-doc/src/server/swagger/index.ts
Normal file
225
packages/plugins/api-doc/src/server/swagger/index.ts
Normal file
@ -0,0 +1,225 @@
|
||||
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();
|
||||
}
|
||||
|
||||
collection2Swagger(collectionName: string, withAssociation = true) {
|
||||
const collection = this.db.getCollection(collectionName);
|
||||
return collectionToSwaggerObject(collection, {
|
||||
withAssociation,
|
||||
});
|
||||
}
|
||||
|
||||
async getCollectionsSwagger(name?: string) {
|
||||
const base = await this.getBaseSwagger();
|
||||
let others = {};
|
||||
if (name) {
|
||||
const collectionSwagger = 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;
|
||||
}
|
||||
others = merge(others, this.collection2Swagger(collection.name, false));
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
52
packages/plugins/api-doc/src/server/swagger/loader.ts
Normal file
52
packages/plugins/api-doc/src/server/swagger/loader.ts
Normal file
@ -0,0 +1,52 @@
|
||||
import { PluginManager } from '@nocobase/server';
|
||||
import { requireModule } from '@nocobase/utils';
|
||||
import { merge } from './merge';
|
||||
|
||||
export const loadSwagger = (packageName: string) => {
|
||||
const prefixes = ['src', 'lib', 'dist'];
|
||||
const targets = ['swagger.json', 'swagger/index.json', 'swagger'];
|
||||
for (const prefix of prefixes) {
|
||||
for (const dict of targets) {
|
||||
try {
|
||||
const file = `${packageName}/${prefix}/${dict}`;
|
||||
const filePath = require.resolve(file);
|
||||
delete require.cache[filePath];
|
||||
return requireModule(file);
|
||||
} catch (error) {
|
||||
//
|
||||
}
|
||||
}
|
||||
}
|
||||
return {};
|
||||
};
|
||||
|
||||
export const getPluginsSwagger = async (db: any, pluginNames?: string[]) => {
|
||||
const nameFilter = pluginNames?.length ? { name: { $in: pluginNames } } : {};
|
||||
const plugins = await db.getRepository('applicationPlugins').find({
|
||||
filter: {
|
||||
enabled: true,
|
||||
...nameFilter,
|
||||
},
|
||||
});
|
||||
const swaggers = {};
|
||||
for (const plugin of plugins) {
|
||||
const packageName = PluginManager.getPackageName(plugin.get('name'));
|
||||
const res = loadSwagger(packageName);
|
||||
if (Object.keys(res).length) {
|
||||
swaggers[plugin.get('name')] = res;
|
||||
}
|
||||
}
|
||||
|
||||
return swaggers;
|
||||
};
|
||||
|
||||
export const mergeObjects = (objs: any[]) => {
|
||||
return objs.reduce((cur, obj) => {
|
||||
return merge(cur, obj);
|
||||
}, {});
|
||||
};
|
||||
|
||||
export const getSwaggerDocument = async (db: any, pluginNames?: string[]) => {
|
||||
const swaggers = await getPluginsSwagger(db, pluginNames);
|
||||
return mergeObjects(Object.values(swaggers));
|
||||
};
|
10
packages/plugins/api-doc/src/server/swagger/merge.ts
Normal file
10
packages/plugins/api-doc/src/server/swagger/merge.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import { merge as deepmerge } from '@nocobase/utils';
|
||||
|
||||
const overwriteMerge = (destinationArray, sourceArray, options) => sourceArray.concat(destinationArray);
|
||||
|
||||
export function merge(obj1: any, obj2: any, opts?: any) {
|
||||
return deepmerge(obj1, obj2, {
|
||||
arrayMerge: overwriteMerge,
|
||||
...opts,
|
||||
});
|
||||
}
|
42
packages/plugins/api-doc/src/swagger.ts
Normal file
42
packages/plugins/api-doc/src/swagger.ts
Normal file
@ -0,0 +1,42 @@
|
||||
export default {
|
||||
info: {
|
||||
title: 'NocoBase API - API doc plugin',
|
||||
},
|
||||
paths: {
|
||||
'/swagger:getUrls': {
|
||||
get: {
|
||||
description: 'Get all api-doc destination',
|
||||
tags: ['swagger'],
|
||||
responses: {
|
||||
200: {
|
||||
description: 'successful operation',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
$ref: '#/components/responses/SwaggerUrls',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
components: {
|
||||
responses: {
|
||||
SwaggerUrls: {
|
||||
type: 'array',
|
||||
items: {
|
||||
properties: {
|
||||
name: {
|
||||
type: 'string',
|
||||
},
|
||||
url: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
115
packages/plugins/api-keys/src/swagger/index.ts
Normal file
115
packages/plugins/api-keys/src/swagger/index.ts
Normal file
@ -0,0 +1,115 @@
|
||||
export default {
|
||||
info: {
|
||||
title: 'NocoBase API - API keys plugin',
|
||||
},
|
||||
tags: [],
|
||||
paths: {
|
||||
'/apiKeys:create': {
|
||||
post: {
|
||||
description: 'Create api key',
|
||||
tags: ['apiKeys'],
|
||||
requestBody: {
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
$ref: '#/components/schemas/apiKeys',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
responses: {
|
||||
200: {
|
||||
description: 'successful operation',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
token: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
'/apiKeys:list': {
|
||||
get: {
|
||||
description: 'get api keys',
|
||||
tags: ['apiKeys'],
|
||||
responses: {
|
||||
200: {
|
||||
description: 'successful operation',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'array',
|
||||
items: {
|
||||
$ref: '#/components/schemas/apiKeys',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
'/apiKeys:destroy/{filterByTk}': {
|
||||
delete: {
|
||||
description: 'Create api key',
|
||||
tags: ['apiKeys'],
|
||||
parameters: [
|
||||
{
|
||||
name: 'filterByTk',
|
||||
description: 'primary key',
|
||||
required: true,
|
||||
in: 'path',
|
||||
schema: {
|
||||
type: 'integer',
|
||||
example: 1,
|
||||
},
|
||||
},
|
||||
],
|
||||
responses: {
|
||||
200: {
|
||||
description: 'successful operation',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
components: {
|
||||
schemas: {
|
||||
apiKeys: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: {
|
||||
type: 'integer',
|
||||
},
|
||||
name: {
|
||||
type: 'string',
|
||||
example: 'key-name',
|
||||
},
|
||||
role: {
|
||||
type: 'object',
|
||||
// $ref: '#/components/schemas/roles'
|
||||
},
|
||||
expiresIn: {
|
||||
type: 'string',
|
||||
enum: ['1d', '7d', '30d', '90d', 'never'],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
/*
|
||||
|
||||
/api/apiKeys:create
|
||||
/api/apiKeys:list
|
||||
/api/apiKeys:destroy
|
||||
|
||||
*/
|
731
packages/plugins/auth/src/swagger/index.ts
Normal file
731
packages/plugins/auth/src/swagger/index.ts
Normal file
@ -0,0 +1,731 @@
|
||||
export default {
|
||||
info: {
|
||||
title: 'NocoBase API - Auth plugin',
|
||||
},
|
||||
paths: {
|
||||
'/auth:check': {
|
||||
get: {
|
||||
description: 'Check if the user is logged in',
|
||||
tags: ['Auth'],
|
||||
parameters: [
|
||||
{
|
||||
name: 'X-Authenticator',
|
||||
description: '登录方式标识',
|
||||
in: 'header',
|
||||
schema: {
|
||||
type: 'string',
|
||||
default: 'basic',
|
||||
},
|
||||
},
|
||||
],
|
||||
security: [],
|
||||
responses: {
|
||||
200: {
|
||||
description: 'successful operation',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
allOf: [
|
||||
{
|
||||
$ref: '#/components/schemas/user',
|
||||
},
|
||||
{
|
||||
type: 'object',
|
||||
properties: {
|
||||
roles: {
|
||||
$ref: '#/components/schemas/roles',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
'/auth:signIn': {
|
||||
post: {
|
||||
description: 'Sign in',
|
||||
tags: ['Basic auth'],
|
||||
security: [],
|
||||
parameters: [
|
||||
{
|
||||
name: 'X-Authenticator',
|
||||
description: '登录方式标识',
|
||||
in: 'header',
|
||||
schema: {
|
||||
type: 'string',
|
||||
default: 'basic',
|
||||
},
|
||||
},
|
||||
],
|
||||
requestBody: {
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
email: {
|
||||
type: 'string',
|
||||
description: '邮箱',
|
||||
},
|
||||
password: {
|
||||
type: 'string',
|
||||
description: '密码',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
responses: {
|
||||
200: {
|
||||
description: 'successful operation',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
token: {
|
||||
type: 'string',
|
||||
},
|
||||
user: {
|
||||
$ref: '#/components/schemas/user',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
401: {
|
||||
description: 'Unauthorized',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
$ref: '#/components/schemas/error',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
'/auth:signUp': {
|
||||
post: {
|
||||
description: 'Sign up',
|
||||
tags: ['Basic auth'],
|
||||
security: [],
|
||||
requestBody: {
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
email: {
|
||||
type: 'string',
|
||||
description: '邮箱',
|
||||
},
|
||||
password: {
|
||||
type: 'string',
|
||||
description: '密码',
|
||||
},
|
||||
confirm_password: {
|
||||
type: 'string',
|
||||
description: '确认密码',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
responses: {
|
||||
200: {
|
||||
description: 'ok',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
'/auth:signOut': {
|
||||
post: {
|
||||
description: 'Sign out',
|
||||
tags: ['Basic auth'],
|
||||
security: [],
|
||||
responses: {
|
||||
200: {
|
||||
description: 'ok',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
'/auth:lostPassword': {
|
||||
post: {
|
||||
description: 'Lost password',
|
||||
tags: ['Basic auth'],
|
||||
security: [],
|
||||
requestBody: {
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
email: {
|
||||
type: 'string',
|
||||
description: '邮箱',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
responses: {
|
||||
200: {
|
||||
description: 'successful operation',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
allOf: [
|
||||
{
|
||||
$ref: '#/components/schemas/user',
|
||||
},
|
||||
{
|
||||
type: 'object',
|
||||
properties: {
|
||||
resetToken: {
|
||||
type: 'string',
|
||||
description: '重置密码的token',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
400: {
|
||||
description: 'Please fill in your email address',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
$ref: '#/components/schemas/error',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
401: {
|
||||
description: 'The email is incorrect, please re-enter',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
$ref: '#/components/schemas/error',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
'/auth:resetPassword': {
|
||||
post: {
|
||||
description: 'Reset password',
|
||||
tags: ['Basic auth'],
|
||||
security: [],
|
||||
requestBody: {
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
email: {
|
||||
type: 'string',
|
||||
description: '邮箱',
|
||||
},
|
||||
password: {
|
||||
type: 'string',
|
||||
description: '密码',
|
||||
},
|
||||
resetToken: {
|
||||
type: 'string',
|
||||
description: '重置密码的token',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
responses: {
|
||||
200: {
|
||||
description: 'successful operation',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
$ref: '#/components/schemas/user',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
404: {
|
||||
description: 'User not found',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
$ref: '#/components/schemas/error',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
'/auth:getUserByResetToken': {
|
||||
get: {
|
||||
description: 'Get user by reset token',
|
||||
tags: ['Basic auth'],
|
||||
security: [],
|
||||
parameters: [
|
||||
{
|
||||
name: 'token',
|
||||
in: 'query',
|
||||
description: '重置密码的token',
|
||||
required: true,
|
||||
schema: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
],
|
||||
responses: {
|
||||
200: {
|
||||
description: 'ok',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
$ref: '#/components/schemas/user',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
401: {
|
||||
description: 'Unauthorized',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
$ref: '#/components/schemas/error',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
'authenticators:listTypes': {
|
||||
get: {
|
||||
description: 'List authenticator types',
|
||||
tags: ['Authenticator'],
|
||||
responses: {
|
||||
200: {
|
||||
description: 'ok',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
'authenticators:publicList': {
|
||||
get: {
|
||||
description: 'List enabled authenticators',
|
||||
tags: ['Authenticator'],
|
||||
security: [],
|
||||
responses: {
|
||||
200: {
|
||||
description: 'ok',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
name: {
|
||||
type: 'string',
|
||||
description: '登录方式标识',
|
||||
},
|
||||
title: {
|
||||
type: 'string',
|
||||
description: '登录方式标题',
|
||||
},
|
||||
authType: {
|
||||
type: 'string',
|
||||
description: '登录方式类型',
|
||||
},
|
||||
options: {
|
||||
type: 'object',
|
||||
description: '登录方式公开配置',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
'authenticators:create': {
|
||||
post: {
|
||||
description: 'Create authenticator',
|
||||
tags: ['Authenticator'],
|
||||
requestBody: {
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
name: {
|
||||
type: 'string',
|
||||
description: '登录方式标识',
|
||||
},
|
||||
authType: {
|
||||
type: 'string',
|
||||
description: '登录方式类型',
|
||||
},
|
||||
options: {
|
||||
type: 'object',
|
||||
description: '登录方式配置',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
responses: {
|
||||
200: {
|
||||
description: 'ok',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
$ref: '#/components/schemas/authenticator',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
'authenticators:list': {
|
||||
get: {
|
||||
description: 'List authenticators',
|
||||
tags: ['Authenticator'],
|
||||
responses: {
|
||||
200: {
|
||||
description: 'ok',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'array',
|
||||
items: {
|
||||
$ref: '#/components/schemas/authenticator',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
'authenticators:get': {
|
||||
get: {
|
||||
description: 'Get authenticator',
|
||||
tags: ['Authenticator'],
|
||||
parameters: [
|
||||
{
|
||||
name: 'filterByTk',
|
||||
in: 'query',
|
||||
description: 'ID',
|
||||
required: true,
|
||||
schema: {
|
||||
type: 'integer',
|
||||
},
|
||||
},
|
||||
],
|
||||
responses: {
|
||||
200: {
|
||||
description: 'ok',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
$ref: '#/components/schemas/authenticator',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
'authenticators:update': {
|
||||
post: {
|
||||
description: 'Update authenticator',
|
||||
tags: ['Authenticator'],
|
||||
parameters: [
|
||||
{
|
||||
name: 'filterByTk',
|
||||
in: 'query',
|
||||
description: 'ID',
|
||||
required: true,
|
||||
schema: {
|
||||
type: 'integer',
|
||||
},
|
||||
},
|
||||
],
|
||||
requestBody: {
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
$ref: '#/components/schemas/authenticator',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
responses: {
|
||||
200: {
|
||||
description: 'ok',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
$ref: '#/components/schemas/authenticator',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
'authenticators:destroy': {
|
||||
post: {
|
||||
description: 'Destroy authenticator',
|
||||
tags: ['Authenticator'],
|
||||
parameters: [
|
||||
{
|
||||
name: 'filterByTk',
|
||||
in: 'query',
|
||||
description: 'ID',
|
||||
required: true,
|
||||
schema: {
|
||||
type: 'integer',
|
||||
},
|
||||
},
|
||||
],
|
||||
responses: {
|
||||
200: {
|
||||
description: 'ok',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
components: {
|
||||
schemas: {
|
||||
user: {
|
||||
type: 'object',
|
||||
description: '用户',
|
||||
properties: {
|
||||
id: {
|
||||
type: 'integer',
|
||||
description: 'ID',
|
||||
},
|
||||
nickname: {
|
||||
type: 'string',
|
||||
description: '昵称',
|
||||
},
|
||||
email: {
|
||||
type: 'string',
|
||||
description: '邮箱',
|
||||
},
|
||||
phone: {
|
||||
type: 'string',
|
||||
description: '手机号',
|
||||
},
|
||||
appLang: {
|
||||
type: 'string',
|
||||
description: '用户使用语言',
|
||||
},
|
||||
systemSettings: {
|
||||
type: 'object',
|
||||
description: '系统设置',
|
||||
properties: {
|
||||
theme: {
|
||||
type: 'string',
|
||||
description: '用户使用主题',
|
||||
},
|
||||
},
|
||||
},
|
||||
createdAt: {
|
||||
type: 'string',
|
||||
format: 'date-time',
|
||||
description: '创建时间',
|
||||
},
|
||||
updatedAt: {
|
||||
type: 'string',
|
||||
format: 'date-time',
|
||||
description: '更新时间',
|
||||
},
|
||||
createdById: {
|
||||
type: 'integer',
|
||||
description: '创建人',
|
||||
},
|
||||
updatedById: {
|
||||
type: 'integer',
|
||||
description: '更新人',
|
||||
},
|
||||
},
|
||||
},
|
||||
roles: {
|
||||
type: 'array',
|
||||
description: '角色',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
title: {
|
||||
type: 'string',
|
||||
description: '角色名称',
|
||||
},
|
||||
name: {
|
||||
type: 'string',
|
||||
description: '角色标识',
|
||||
},
|
||||
description: {
|
||||
type: 'string',
|
||||
description: '角色描述',
|
||||
},
|
||||
hidden: {
|
||||
type: 'boolean',
|
||||
description: '是否隐藏',
|
||||
},
|
||||
default: {
|
||||
type: 'boolean',
|
||||
description: '是否默认',
|
||||
},
|
||||
allowConfigure: {
|
||||
type: 'boolean',
|
||||
description: '是否允许配置',
|
||||
},
|
||||
allowNewMenu: {
|
||||
type: 'boolean',
|
||||
description: '是否允许新建菜单',
|
||||
},
|
||||
snippets: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'string',
|
||||
},
|
||||
description: '接口权限',
|
||||
},
|
||||
strategy: {
|
||||
type: 'array',
|
||||
description: '数据表权限策略',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
actions: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'string',
|
||||
},
|
||||
description: '操作',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
createdAt: {
|
||||
type: 'string',
|
||||
format: 'date-time',
|
||||
description: '创建时间',
|
||||
},
|
||||
updatedAt: {
|
||||
type: 'string',
|
||||
format: 'date-time',
|
||||
description: '更新时间',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
authenticator: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: {
|
||||
type: 'integer',
|
||||
description: 'ID',
|
||||
},
|
||||
authType: {
|
||||
type: 'string',
|
||||
description: '登录方式类型',
|
||||
},
|
||||
name: {
|
||||
type: 'string',
|
||||
description: '登录方式标识',
|
||||
},
|
||||
title: {
|
||||
type: 'string',
|
||||
description: '登录方式标题',
|
||||
},
|
||||
options: {
|
||||
type: 'object',
|
||||
description: '登录方式配置',
|
||||
},
|
||||
description: {
|
||||
type: 'string',
|
||||
description: '登录方式描述',
|
||||
},
|
||||
enabled: {
|
||||
type: 'boolean',
|
||||
description: '是否启用',
|
||||
},
|
||||
createdAt: {
|
||||
type: 'string',
|
||||
format: 'date-time',
|
||||
description: '创建时间',
|
||||
},
|
||||
updatedAt: {
|
||||
type: 'string',
|
||||
format: 'date-time',
|
||||
description: '更新时间',
|
||||
},
|
||||
createdById: {
|
||||
type: 'integer',
|
||||
description: '创建人',
|
||||
},
|
||||
updatedById: {
|
||||
type: 'integer',
|
||||
description: '更新人',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
/*
|
||||
/api/auth:check
|
||||
/api/auth:signIn
|
||||
/api/auth:signUp
|
||||
/api/auth:signOut
|
||||
/api/auth:lostPassword
|
||||
/api/auth:resetPassword
|
||||
/api/auth:getUserByResetToken
|
||||
/api/auth:changePassword
|
||||
/api/authenticators:listTypes
|
||||
/api/authenticators:publicList
|
||||
/api/authenticators:create
|
||||
/api/authenticators:list
|
||||
/api/authenticators:get
|
||||
/api/authenticators:update
|
||||
/api/authenticators:destroy
|
||||
*/
|
20
packages/plugins/china-region/src/swagger/index.json
Normal file
20
packages/plugins/china-region/src/swagger/index.json
Normal file
@ -0,0 +1,20 @@
|
||||
{
|
||||
"openapi": "3.0.2",
|
||||
"info": {
|
||||
"title": "NocoBase API - China region plugin"
|
||||
},
|
||||
"paths": {
|
||||
"/chinaRegions:list": {
|
||||
"get": {
|
||||
"tags": ["chinaRegions"],
|
||||
"description": "",
|
||||
"parameters": [],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
7
packages/plugins/client/src/swagger/index.json
Normal file
7
packages/plugins/client/src/swagger/index.json
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"openapi": "3.0.2",
|
||||
"info": {
|
||||
"title": "NocoBase API - Client plugin"
|
||||
},
|
||||
"paths": {}
|
||||
}
|
273
packages/plugins/collection-manager/src/swagger/index.json
Normal file
273
packages/plugins/collection-manager/src/swagger/index.json
Normal file
@ -0,0 +1,273 @@
|
||||
{
|
||||
"openapi": "3.0.2",
|
||||
"info": {
|
||||
"title": "NocoBase API - Collection manager plugin"
|
||||
},
|
||||
"tags": [],
|
||||
"paths": {
|
||||
"/collections:list": {
|
||||
"get": {
|
||||
"tags": ["collections"],
|
||||
"description": "",
|
||||
"parameters": [],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/collections:get": {
|
||||
"get": {
|
||||
"tags": ["collections"],
|
||||
"description": "",
|
||||
"parameters": [],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/collections:create": {
|
||||
"post": {
|
||||
"tags": ["collections"],
|
||||
"description": "",
|
||||
"parameters": [],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/collections:update": {
|
||||
"post": {
|
||||
"tags": ["collections"],
|
||||
"description": "",
|
||||
"parameters": [],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/collections:destroy": {
|
||||
"post": {
|
||||
"tags": ["collections"],
|
||||
"description": "",
|
||||
"parameters": [],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/collections:move": {
|
||||
"post": {
|
||||
"tags": ["collections"],
|
||||
"description": "",
|
||||
"parameters": [],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/collections:setFields": {
|
||||
"post": {
|
||||
"tags": ["collections"],
|
||||
"description": "",
|
||||
"parameters": [],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/collections/{collectionName}/fields:get": {
|
||||
"get": {
|
||||
"tags": ["collections.fields"],
|
||||
"description": "",
|
||||
"parameters": [],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/collections/{collectionName}/fields:list": {
|
||||
"get": {
|
||||
"tags": ["collections.fields"],
|
||||
"description": "",
|
||||
"parameters": [],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/collections/{collectionName}/fields:create": {
|
||||
"post": {
|
||||
"tags": ["collections.fields"],
|
||||
"description": "",
|
||||
"parameters": [],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/collections/{collectionName}/fields:update": {
|
||||
"post": {
|
||||
"tags": ["collections.fields"],
|
||||
"description": "",
|
||||
"parameters": [],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/collections/{collectionName}/fields:destroy": {
|
||||
"post": {
|
||||
"tags": ["collections.fields"],
|
||||
"description": "",
|
||||
"parameters": [],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/collections/{collectionName}/fields:move": {
|
||||
"post": {
|
||||
"tags": ["collections.fields"],
|
||||
"description": "",
|
||||
"parameters": [],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/collectionCategories:list": {
|
||||
"post": {
|
||||
"tags": ["collectionCategories"],
|
||||
"description": "",
|
||||
"parameters": [],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/collectionCategories:get": {
|
||||
"post": {
|
||||
"tags": ["collectionCategories"],
|
||||
"description": "",
|
||||
"parameters": [],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/collectionCategories:create": {
|
||||
"post": {
|
||||
"tags": ["collectionCategories"],
|
||||
"description": "",
|
||||
"parameters": [],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/collectionCategories:update": {
|
||||
"post": {
|
||||
"tags": ["collectionCategories"],
|
||||
"description": "",
|
||||
"parameters": [],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/collectionCategories:destroy": {
|
||||
"post": {
|
||||
"tags": ["collectionCategories"],
|
||||
"description": "",
|
||||
"parameters": [],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/collectionCategories:move": {
|
||||
"post": {
|
||||
"tags": ["collectionCategories"],
|
||||
"description": "",
|
||||
"parameters": [],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/dbViews:get": {
|
||||
"get": {
|
||||
"tags": ["dbViews"],
|
||||
"description": "",
|
||||
"parameters": [],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/dbViews:list": {
|
||||
"get": {
|
||||
"tags": ["dbViews"],
|
||||
"description": "",
|
||||
"parameters": [],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/dbViews:query": {
|
||||
"get": {
|
||||
"tags": ["dbViews"],
|
||||
"description": "",
|
||||
"parameters": [],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
7
packages/plugins/data-visualization/src/swagger/index.ts
Normal file
7
packages/plugins/data-visualization/src/swagger/index.ts
Normal file
@ -0,0 +1,7 @@
|
||||
export default {
|
||||
info: {
|
||||
title: 'NocoBase API - Data visualization plugin',
|
||||
},
|
||||
tags: [],
|
||||
paths: {},
|
||||
};
|
21
packages/plugins/export/src/swagger/index.json
Normal file
21
packages/plugins/export/src/swagger/index.json
Normal file
@ -0,0 +1,21 @@
|
||||
{
|
||||
"openapi": "3.0.2",
|
||||
"info": {
|
||||
"title": "NocoBase API - Export plugin"
|
||||
},
|
||||
"tags": [],
|
||||
"paths": {
|
||||
"/{collectionName}:export": {
|
||||
"post": {
|
||||
"tags": ["$collection"],
|
||||
"description": "",
|
||||
"parameters": [],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
81
packages/plugins/file-manager/src/swagger/index.json
Normal file
81
packages/plugins/file-manager/src/swagger/index.json
Normal file
@ -0,0 +1,81 @@
|
||||
{
|
||||
"openapi": "3.0.2",
|
||||
"info": {
|
||||
"title": "NocoBase API - File manager plugin"
|
||||
},
|
||||
"tags": [],
|
||||
"paths": {
|
||||
"/{fileCollectionName}:create": {
|
||||
"post": {
|
||||
"tags": ["$collection"],
|
||||
"description": "",
|
||||
"parameters": [],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/storages:list": {
|
||||
"get": {
|
||||
"tags": ["storages"],
|
||||
"description": "",
|
||||
"parameters": [],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/storages:get": {
|
||||
"get": {
|
||||
"tags": ["storages"],
|
||||
"description": "",
|
||||
"parameters": [],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/storages:create": {
|
||||
"post": {
|
||||
"tags": ["storages"],
|
||||
"description": "",
|
||||
"parameters": [],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/storages:update": {
|
||||
"post": {
|
||||
"tags": ["storages"],
|
||||
"description": "",
|
||||
"parameters": [],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/storages:destroy": {
|
||||
"post": {
|
||||
"tags": ["storages"],
|
||||
"description": "",
|
||||
"parameters": [],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
7
packages/plugins/iframe-block/src/swagger/index.ts
Normal file
7
packages/plugins/iframe-block/src/swagger/index.ts
Normal file
@ -0,0 +1,7 @@
|
||||
export default {
|
||||
info: {
|
||||
title: 'NocoBase API - Iframe block plugin',
|
||||
},
|
||||
tags: [],
|
||||
paths: {},
|
||||
};
|
33
packages/plugins/import/src/swagger/index.json
Normal file
33
packages/plugins/import/src/swagger/index.json
Normal file
@ -0,0 +1,33 @@
|
||||
{
|
||||
"openapi": "3.0.2",
|
||||
"info": {
|
||||
"title": "NocoBase API - Import plugin"
|
||||
},
|
||||
"tags": [],
|
||||
"paths": {
|
||||
"/{collectionName}:importXlsx": {
|
||||
"post": {
|
||||
"tags": ["$collection"],
|
||||
"description": "",
|
||||
"parameters": [],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/{collectionName}:downloadXlsxTemplate": {
|
||||
"post": {
|
||||
"tags": ["$collection"],
|
||||
"description": "",
|
||||
"parameters": [],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
156
packages/plugins/localization-management/src/swagger/index.json
Normal file
156
packages/plugins/localization-management/src/swagger/index.json
Normal file
@ -0,0 +1,156 @@
|
||||
{
|
||||
"openapi": "3.0.2",
|
||||
"info": {
|
||||
"title": "NocoBase API - Localization management plugin"
|
||||
},
|
||||
"tags": [],
|
||||
"paths": {
|
||||
"/localization:sync": {
|
||||
"post": {
|
||||
"tags": ["localization"],
|
||||
"description": "Synchronize localization resources",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "X-Locale",
|
||||
"in": "header",
|
||||
"defalut": "en-US",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"enum": ["local", "menu", "db"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/localization:publish": {
|
||||
"post": {
|
||||
"tags": ["localization"],
|
||||
"description": "Publish localization resources (Reset cache)",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "X-Locale",
|
||||
"in": "header",
|
||||
"defalut": "en-US",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/localizationTexts:list": {
|
||||
"get": {
|
||||
"tags": ["localizationTexts"],
|
||||
"description": "Retrive localization texts with translations",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "X-Locale",
|
||||
"in": "header",
|
||||
"defalut": "en-US",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "keyword",
|
||||
"in": "query",
|
||||
"description": "Search keyword",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "hasTranslation",
|
||||
"in": "query",
|
||||
"description": "true - list all, false - list only untranslated",
|
||||
"schema": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"defalut": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/localizationTranslations:updateOrCreate": {
|
||||
"post": {
|
||||
"tags": ["localizationTranslations"],
|
||||
"description": "Update or create localization translations",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "textId",
|
||||
"in": "query",
|
||||
"schema": {
|
||||
"type": "integer"
|
||||
},
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "locale",
|
||||
"in": "query",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
},
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"textId": {
|
||||
"type": "integer"
|
||||
},
|
||||
"locale": {
|
||||
"type": "string"
|
||||
},
|
||||
"translation": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
78
packages/plugins/map/src/swagger/index.ts
Normal file
78
packages/plugins/map/src/swagger/index.ts
Normal file
@ -0,0 +1,78 @@
|
||||
export default {
|
||||
info: {
|
||||
title: 'NocoBase API - Map plugin',
|
||||
},
|
||||
paths: {
|
||||
'/map-configuration:get': {
|
||||
get: {
|
||||
description: 'Get map configuration',
|
||||
tags: ['map-configuration'],
|
||||
parameters: [
|
||||
{
|
||||
name: 'type',
|
||||
in: 'query',
|
||||
description: 'Map type',
|
||||
required: true,
|
||||
schema: {
|
||||
type: 'string',
|
||||
default: 'amap',
|
||||
enum: ['amap', 'google'],
|
||||
},
|
||||
},
|
||||
],
|
||||
responses: {
|
||||
200: {
|
||||
description: 'successful operation',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
$ref: '#/components/schemas/mapConfiguration',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
'/map-configuration:set': {
|
||||
post: {
|
||||
description: '设置·地图配置',
|
||||
tags: ['map-configuration'],
|
||||
requestBody: {
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
$ref: '#/components/schemas/mapConfiguration',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
responses: {
|
||||
200: {
|
||||
description: 'ok',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
components: {
|
||||
schemas: {
|
||||
mapConfiguration: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
accessKey: {
|
||||
type: 'string',
|
||||
},
|
||||
securityJsCode: {
|
||||
type: 'string',
|
||||
},
|
||||
type: {
|
||||
type: 'string',
|
||||
default: 'amap',
|
||||
enum: ['amap', 'google'],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
195
packages/plugins/multi-app-manager/src/swagger/index.ts
Normal file
195
packages/plugins/multi-app-manager/src/swagger/index.ts
Normal file
@ -0,0 +1,195 @@
|
||||
export default {
|
||||
info: {
|
||||
title: 'NocoBase API - Multi-app manager plugin',
|
||||
},
|
||||
tags: [],
|
||||
paths: {
|
||||
'/applications:list': {
|
||||
get: {
|
||||
tags: ['applications'],
|
||||
description: 'List all applications',
|
||||
responses: {
|
||||
200: {
|
||||
description: 'ok',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
$ref: '#/components/schemas/application',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
'/applications:create': {
|
||||
post: {
|
||||
tags: ['applications'],
|
||||
description: 'Update application',
|
||||
requestBody: {
|
||||
required: true,
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
$ref: '#/components/schemas/applicationFrom',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
responses: {
|
||||
200: {
|
||||
description: 'ok',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
$ref: '#/components/schemas/application',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
'/applications:update': {
|
||||
post: {
|
||||
tags: ['applications'],
|
||||
description: 'Update application',
|
||||
parameters: [
|
||||
{
|
||||
name: 'filterByTk',
|
||||
in: 'query',
|
||||
description: 'application name',
|
||||
required: true,
|
||||
schema: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
],
|
||||
requestBody: {
|
||||
required: true,
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
$ref: '#/components/schemas/applicationFrom',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
responses: {
|
||||
200: {
|
||||
description: 'ok',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
$ref: '#/components/schemas/application',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
'/applications:destroy': {
|
||||
post: {
|
||||
tags: ['applications'],
|
||||
description: 'Delete application',
|
||||
parameters: [
|
||||
{
|
||||
name: 'filterByTk',
|
||||
in: 'query',
|
||||
description: 'application name',
|
||||
required: true,
|
||||
schema: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
],
|
||||
responses: {
|
||||
200: {
|
||||
description: 'ok',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
components: {
|
||||
schemas: {
|
||||
applicationFrom: {
|
||||
allOf: [
|
||||
{
|
||||
$ref: '#/components/schemas/application',
|
||||
},
|
||||
{
|
||||
type: 'object',
|
||||
properties: {
|
||||
createdAt: {
|
||||
readOnly: true,
|
||||
},
|
||||
updatedAt: {
|
||||
readOnly: true,
|
||||
},
|
||||
status: {
|
||||
readOnly: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
application: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
name: {
|
||||
type: 'string',
|
||||
example: 'app-1',
|
||||
description: "The application's name",
|
||||
},
|
||||
displayName: {
|
||||
type: 'string',
|
||||
example: 'first application',
|
||||
description: "The application's display name",
|
||||
},
|
||||
pinned: {
|
||||
type: 'boolean',
|
||||
example: true,
|
||||
description: '是否在菜单上显示',
|
||||
},
|
||||
cname: {
|
||||
type: 'string',
|
||||
example: 'app-1.example.com',
|
||||
description: 'custom domain of the application',
|
||||
},
|
||||
status: {
|
||||
type: 'string',
|
||||
example: 'running',
|
||||
description: 'application status',
|
||||
},
|
||||
options: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
standaloneDeployment: {
|
||||
type: 'boolean',
|
||||
example: true,
|
||||
description: '是否为独立部署的子应用',
|
||||
},
|
||||
autoStart: {
|
||||
type: 'boolean',
|
||||
example: true,
|
||||
description: '应用是否默认跟随主应用启动',
|
||||
},
|
||||
},
|
||||
},
|
||||
createdAt: {
|
||||
type: 'string',
|
||||
format: 'date-time',
|
||||
description: '创建时间',
|
||||
},
|
||||
updatedAt: {
|
||||
type: 'string',
|
||||
format: 'date-time',
|
||||
description: '更新时间',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
157
packages/plugins/oidc/src/swagger/index.ts
Normal file
157
packages/plugins/oidc/src/swagger/index.ts
Normal file
@ -0,0 +1,157 @@
|
||||
const user = {
|
||||
type: 'object',
|
||||
description: '用户',
|
||||
properties: {
|
||||
id: {
|
||||
type: 'integer',
|
||||
description: 'ID',
|
||||
},
|
||||
nickname: {
|
||||
type: 'string',
|
||||
description: '昵称',
|
||||
},
|
||||
email: {
|
||||
type: 'string',
|
||||
description: '邮箱',
|
||||
},
|
||||
phone: {
|
||||
type: 'string',
|
||||
description: '手机号',
|
||||
},
|
||||
appLang: {
|
||||
type: 'string',
|
||||
description: '用户使用语言',
|
||||
},
|
||||
systemSettings: {
|
||||
type: 'object',
|
||||
description: '系统设置',
|
||||
properties: {
|
||||
theme: {
|
||||
type: 'string',
|
||||
description: '用户使用主题',
|
||||
},
|
||||
},
|
||||
},
|
||||
createdAt: {
|
||||
type: 'string',
|
||||
format: 'date-time',
|
||||
description: '创建时间',
|
||||
},
|
||||
updatedAt: {
|
||||
type: 'string',
|
||||
format: 'date-time',
|
||||
description: '更新时间',
|
||||
},
|
||||
createdById: {
|
||||
type: 'integer',
|
||||
description: '创建人',
|
||||
},
|
||||
updatedById: {
|
||||
type: 'integer',
|
||||
description: '更新人',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default {
|
||||
info: {
|
||||
title: 'NocoBase API - OIDC plugin',
|
||||
},
|
||||
paths: {
|
||||
'/oidc:getAuthUrl': {
|
||||
security: [],
|
||||
get: {
|
||||
description: 'Get OIDC authorization url',
|
||||
tags: ['OIDC'],
|
||||
parameters: [
|
||||
{
|
||||
name: 'X-Authenticator',
|
||||
description: '登录方式标识',
|
||||
in: 'header',
|
||||
schema: {
|
||||
type: 'string',
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
responses: {
|
||||
200: {
|
||||
description: 'ok',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
'/auth:signIn': {
|
||||
security: [],
|
||||
post: {
|
||||
description: 'OIDC sign in',
|
||||
tags: ['OIDC'],
|
||||
parameters: [
|
||||
{
|
||||
name: 'X-Authenticator',
|
||||
description: '登录方式标识',
|
||||
in: 'header',
|
||||
schema: {
|
||||
type: 'string',
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'nocobase_oidc',
|
||||
description: 'state校验值',
|
||||
in: 'cookie',
|
||||
schema: {
|
||||
type: 'string',
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
requestBody: {
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
code: {
|
||||
type: 'string',
|
||||
},
|
||||
state: {
|
||||
type: 'string',
|
||||
},
|
||||
iss: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
responses: {
|
||||
200: {
|
||||
description: 'ok',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
token: {
|
||||
type: 'string',
|
||||
},
|
||||
user,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
142
packages/plugins/saml/src/swagger/index.ts
Normal file
142
packages/plugins/saml/src/swagger/index.ts
Normal file
@ -0,0 +1,142 @@
|
||||
const user = {
|
||||
type: 'object',
|
||||
description: '用户',
|
||||
properties: {
|
||||
id: {
|
||||
type: 'integer',
|
||||
description: 'ID',
|
||||
},
|
||||
nickname: {
|
||||
type: 'string',
|
||||
description: '昵称',
|
||||
},
|
||||
email: {
|
||||
type: 'string',
|
||||
description: '邮箱',
|
||||
},
|
||||
phone: {
|
||||
type: 'string',
|
||||
description: '手机号',
|
||||
},
|
||||
appLang: {
|
||||
type: 'string',
|
||||
description: '用户使用语言',
|
||||
},
|
||||
systemSettings: {
|
||||
type: 'object',
|
||||
description: '系统设置',
|
||||
properties: {
|
||||
theme: {
|
||||
type: 'string',
|
||||
description: '用户使用主题',
|
||||
},
|
||||
},
|
||||
},
|
||||
createdAt: {
|
||||
type: 'string',
|
||||
format: 'date-time',
|
||||
description: '创建时间',
|
||||
},
|
||||
updatedAt: {
|
||||
type: 'string',
|
||||
format: 'date-time',
|
||||
description: '更新时间',
|
||||
},
|
||||
createdById: {
|
||||
type: 'integer',
|
||||
description: '创建人',
|
||||
},
|
||||
updatedById: {
|
||||
type: 'integer',
|
||||
description: '更新人',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default {
|
||||
info: {
|
||||
title: 'NocoBase API - SAML plugin',
|
||||
},
|
||||
paths: {
|
||||
'/saml:getAuthUrl': {
|
||||
security: [],
|
||||
get: {
|
||||
description: 'Get SAML authorization url',
|
||||
tags: ['SAML'],
|
||||
parameters: [
|
||||
{
|
||||
name: 'X-Authenticator',
|
||||
description: '登录方式标识',
|
||||
in: 'header',
|
||||
schema: {
|
||||
type: 'string',
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
responses: {
|
||||
200: {
|
||||
description: 'ok',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
'/auth:signIn': {
|
||||
security: [],
|
||||
post: {
|
||||
description: 'SAML sign in',
|
||||
tags: ['SAML'],
|
||||
parameters: [
|
||||
{
|
||||
name: 'X-Authenticator',
|
||||
description: '登录方式标识',
|
||||
in: 'header',
|
||||
schema: {
|
||||
type: 'string',
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
requestBody: {
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
samlResponse: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
responses: {
|
||||
200: {
|
||||
description: 'ok',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
user,
|
||||
token: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
7
packages/plugins/snapshot-field/src/swagger/index.ts
Normal file
7
packages/plugins/snapshot-field/src/swagger/index.ts
Normal file
@ -0,0 +1,7 @@
|
||||
export default {
|
||||
info: {
|
||||
title: 'NocoBase API - Snapshot field plugin',
|
||||
},
|
||||
tags: [],
|
||||
paths: {},
|
||||
};
|
31
packages/plugins/system-settings/src/swagger/index.json
Normal file
31
packages/plugins/system-settings/src/swagger/index.json
Normal file
@ -0,0 +1,31 @@
|
||||
{
|
||||
"openapi": "3.0.2",
|
||||
"info": {
|
||||
"title": "NocoBase API - System settings plugin"
|
||||
},
|
||||
"tags": [],
|
||||
"paths": {
|
||||
"/systemSettings:get": {
|
||||
"get": {
|
||||
"description": "",
|
||||
"parameters": [],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/systemSettings:update": {
|
||||
"post": {
|
||||
"description": "",
|
||||
"parameters": [],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -314,7 +314,7 @@ const ThemeCard = (props: Props) => {
|
||||
<Card
|
||||
hoverable
|
||||
extra={extra}
|
||||
title={t(item.config.name)}
|
||||
title={t(item.config?.name)}
|
||||
size="small"
|
||||
style={cardStyle}
|
||||
headStyle={{ minHeight: 38 }}
|
||||
|
@ -11,7 +11,7 @@ export function changeAlgorithmFromStringToFunction(themeConfig: ThemeItem) {
|
||||
if (_.isString(themeConfig.config.algorithm)) {
|
||||
themeConfig.config.algorithm = theme[themeConfig.config.algorithm];
|
||||
}
|
||||
if (Array.isArray(themeConfig.config.algorithm)) {
|
||||
if (Array.isArray(themeConfig.config?.algorithm)) {
|
||||
themeConfig.config.algorithm = themeConfig.config.algorithm.map((item) => {
|
||||
if (_.isString(item)) {
|
||||
return theme[item];
|
||||
|
223
packages/plugins/theme-editor/src/swagger/index.ts
Normal file
223
packages/plugins/theme-editor/src/swagger/index.ts
Normal file
@ -0,0 +1,223 @@
|
||||
export default {
|
||||
openapi: '3.0.2',
|
||||
info: {
|
||||
title: 'NocoBase API - Theme editor plugin',
|
||||
},
|
||||
components: {
|
||||
schemas: {
|
||||
Theme: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: {
|
||||
type: 'integer',
|
||||
},
|
||||
config: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
name: {
|
||||
type: 'string',
|
||||
},
|
||||
token: {
|
||||
type: 'object',
|
||||
additionalProperties: false,
|
||||
},
|
||||
},
|
||||
required: ['name'],
|
||||
additionalProperties: false,
|
||||
},
|
||||
optional: {
|
||||
type: 'boolean',
|
||||
},
|
||||
isBuiltIn: {
|
||||
type: 'boolean',
|
||||
},
|
||||
updatedAt: {
|
||||
type: 'string',
|
||||
format: 'date-time',
|
||||
},
|
||||
createdAt: {
|
||||
type: 'string',
|
||||
format: 'date-time',
|
||||
},
|
||||
},
|
||||
required: ['id', 'config', 'optional', 'isBuiltIn'],
|
||||
additionalProperties: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
tags: [],
|
||||
paths: {
|
||||
'/themeConfig:list': {
|
||||
get: {
|
||||
tags: ['themeConfig'],
|
||||
description: 'Get a list of themes',
|
||||
parameters: [],
|
||||
responses: {
|
||||
'200': {
|
||||
description: 'OK',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'array',
|
||||
items: {
|
||||
$ref: '#/components/schemas/Theme',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
'401': {
|
||||
description: 'Unauthorized',
|
||||
},
|
||||
'404': {
|
||||
description: 'Not Found',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
'/themeConfig:create': {
|
||||
post: {
|
||||
tags: ['themeConfig'],
|
||||
description: 'Create a new theme',
|
||||
requestBody: {
|
||||
required: true,
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
config: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
name: { type: 'string' },
|
||||
token: {
|
||||
type: 'object',
|
||||
},
|
||||
// 可以根据需要添加其他属性
|
||||
},
|
||||
required: ['name'],
|
||||
additionalProperties: false,
|
||||
},
|
||||
optional: { type: 'boolean' },
|
||||
isBuiltIn: { type: 'boolean' },
|
||||
},
|
||||
required: ['config'],
|
||||
additionalProperties: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
responses: {
|
||||
'200': {
|
||||
description: 'OK',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
$ref: '#/components/schemas/Theme',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
'400': {
|
||||
description: 'Bad Request',
|
||||
},
|
||||
'401': {
|
||||
description: 'Unauthorized',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
'/themeConfig:update': {
|
||||
post: {
|
||||
tags: ['themeConfig'],
|
||||
description: 'Update an existing theme',
|
||||
parameters: [
|
||||
{
|
||||
name: 'id',
|
||||
in: 'path',
|
||||
description: 'ID of the theme to update',
|
||||
required: true,
|
||||
schema: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
],
|
||||
requestBody: {
|
||||
required: true,
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
config: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
name: { type: 'string' },
|
||||
token: {
|
||||
type: 'object',
|
||||
},
|
||||
// 可以根据需要添加其他属性
|
||||
},
|
||||
additionalProperties: false,
|
||||
},
|
||||
optional: { type: 'boolean' },
|
||||
isBuiltIn: { type: 'boolean' },
|
||||
},
|
||||
additionalProperties: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
responses: {
|
||||
'200': {
|
||||
description: 'OK',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
$ref: '#/components/schemas/Theme',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
'400': {
|
||||
description: 'Bad Request',
|
||||
},
|
||||
'401': {
|
||||
description: 'Unauthorized',
|
||||
},
|
||||
'404': {
|
||||
description: 'Not Found',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
'/themeConfig:destroy': {
|
||||
post: {
|
||||
tags: ['themeConfig'],
|
||||
description: 'Delete an existing theme',
|
||||
parameters: [
|
||||
{
|
||||
name: 'id',
|
||||
in: 'path',
|
||||
description: 'ID of the theme to delete',
|
||||
required: true,
|
||||
schema: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
],
|
||||
responses: {
|
||||
'204': {
|
||||
description: 'No Content',
|
||||
},
|
||||
'401': {
|
||||
description: 'Unauthorized',
|
||||
},
|
||||
'404': {
|
||||
description: 'Not Found',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
@ -5,7 +5,7 @@ export type ThemeConfig = Config & { name?: string };
|
||||
export interface ThemeItem {
|
||||
id: number;
|
||||
/** 主题配置内容,一个 JSON 字符串 */
|
||||
config: ThemeConfig;
|
||||
config: ThemeConfig | null;
|
||||
/** 主题是否可选 */
|
||||
optional: boolean;
|
||||
isBuiltIn?: boolean;
|
||||
|
@ -48,7 +48,7 @@ export const uiSchemaActions = {
|
||||
() =>
|
||||
({
|
||||
readFromCache: true,
|
||||
} as GetPropertiesOptions),
|
||||
}) as GetPropertiesOptions,
|
||||
),
|
||||
insert: callRepositoryMethod('insert', 'values'),
|
||||
insertNewSchema: callRepositoryMethod('insertNewSchema', 'values'),
|
||||
|
333
packages/plugins/ui-schema-storage/src/swagger/index.ts
Normal file
333
packages/plugins/ui-schema-storage/src/swagger/index.ts
Normal file
@ -0,0 +1,333 @@
|
||||
export default {
|
||||
openapi: '3.0.2',
|
||||
info: {
|
||||
title: 'NocoBase API - UI schema storage plugin',
|
||||
},
|
||||
tags: [],
|
||||
paths: {
|
||||
'/uiSchemas:getJsonSchema/{uid}': {
|
||||
get: {
|
||||
tags: ['uiSchemas'],
|
||||
description: '',
|
||||
parameters: [
|
||||
{
|
||||
$ref: '#/components/parameters/uid',
|
||||
},
|
||||
],
|
||||
responses: {
|
||||
'200': {
|
||||
description: 'OK',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
$ref: '#/components/schemas/uiSchema',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
'/uiSchemas:getProperties/{uid}': {
|
||||
get: {
|
||||
tags: ['uiSchemas'],
|
||||
description: '获取ui schema的properties属性',
|
||||
parameters: [
|
||||
{
|
||||
$ref: '#/components/parameters/uid',
|
||||
},
|
||||
],
|
||||
responses: {
|
||||
'200': {
|
||||
description: 'OK',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
type: {
|
||||
type: 'string',
|
||||
},
|
||||
properties: {
|
||||
type: 'object',
|
||||
additionalProperties: {
|
||||
$ref: '#/components/schemas/uiSchema',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
'/uiSchemas:insert': {
|
||||
post: {
|
||||
tags: ['uiSchemas'],
|
||||
description: 'insert ui schema as root node',
|
||||
requestBody: {
|
||||
required: true,
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
$ref: '#/components/schemas/uiSchema',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
responses: {
|
||||
'200': {
|
||||
description: 'OK',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
$ref: '#/components/schemas/uiSchema',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
'/uiSchemas:remove/{uid}': {
|
||||
post: {
|
||||
tags: ['uiSchemas'],
|
||||
description: 'remove an ui schema node by uid',
|
||||
parameters: [
|
||||
{
|
||||
$ref: '#/components/parameters/uid',
|
||||
},
|
||||
],
|
||||
responses: {
|
||||
'200': {
|
||||
description: 'OK',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
'/uiSchemas:patch': {
|
||||
post: {
|
||||
tags: ['uiSchemas'],
|
||||
description: 'update an ui schema node by an new ui schema',
|
||||
requestBody: {
|
||||
required: true,
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
$ref: '#/components/schemas/uiSchema',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
responses: {
|
||||
'200': {
|
||||
description: 'OK',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
$ref: '#/components/schemas/uiSchema',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
'/uiSchemas:batchPatch': {
|
||||
post: {
|
||||
tags: ['uiSchemas'],
|
||||
description: 'batch update ui schema nodes by an new ui schema item',
|
||||
requestBody: {
|
||||
required: true,
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'array',
|
||||
items: {
|
||||
$ref: '#/components/schemas/uiSchema',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
responses: {
|
||||
'200': {
|
||||
description: 'OK',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
'/uiSchemas:clearAncestor/{uid}': {
|
||||
post: {
|
||||
tags: ['uiSchemas'],
|
||||
description: 'Removes all ancestors of a given node',
|
||||
parameters: [
|
||||
{
|
||||
$ref: '#/components/parameters/uid',
|
||||
},
|
||||
],
|
||||
responses: {
|
||||
'200': {
|
||||
description: 'OK',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
'/uiSchemas:insertAdjacent/{uid}': {
|
||||
post: {
|
||||
tags: ['uiSchemas'],
|
||||
description: 'insert a node adjacent to another node',
|
||||
parameters: [
|
||||
{
|
||||
$ref: '#/components/parameters/uid',
|
||||
},
|
||||
{
|
||||
name: 'position',
|
||||
in: 'query',
|
||||
required: true,
|
||||
description: 'position',
|
||||
schema: {
|
||||
type: 'string',
|
||||
enum: ['beforeBegin', 'afterBegin', 'beforeEnd', 'afterEnd'],
|
||||
},
|
||||
},
|
||||
],
|
||||
requestBody: {
|
||||
required: true,
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
schema: {
|
||||
$ref: '#/components/schemas/uiSchema',
|
||||
},
|
||||
wrap: {
|
||||
$ref: '#/components/schemas/uiSchema',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
responses: {
|
||||
'200': {
|
||||
description: 'OK',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
'/uiSchemas:saveAsTemplate': {
|
||||
post: {
|
||||
tags: ['uiSchemas'],
|
||||
description: 'save an ui schema as template',
|
||||
parameters: [
|
||||
{
|
||||
name: 'filterByTk',
|
||||
in: 'query',
|
||||
required: true,
|
||||
description: 'filterByTk',
|
||||
schema: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
],
|
||||
requestBody: {
|
||||
required: true,
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
key: {
|
||||
type: 'string',
|
||||
},
|
||||
|
||||
collectionName: {
|
||||
type: 'string',
|
||||
},
|
||||
componentName: {
|
||||
type: 'string',
|
||||
},
|
||||
name: {
|
||||
type: 'string',
|
||||
description: "template's name",
|
||||
},
|
||||
resourceName: {
|
||||
type: 'string',
|
||||
description: "template's resourceName",
|
||||
},
|
||||
uid: {
|
||||
type: 'string',
|
||||
description: 'ui schema id',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
responses: {
|
||||
'200': {
|
||||
description: 'OK',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
components: {
|
||||
parameters: {
|
||||
uid: {
|
||||
name: 'uid',
|
||||
in: 'path',
|
||||
required: true,
|
||||
description: 'x-uid',
|
||||
schema: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
},
|
||||
schemas: {
|
||||
uiSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
type: {
|
||||
type: 'string',
|
||||
example: 'void',
|
||||
},
|
||||
title: {
|
||||
type: 'string',
|
||||
example: '{{ t("Actions") }}',
|
||||
description: "ui schema's title",
|
||||
},
|
||||
'x-uid': {
|
||||
type: 'string',
|
||||
example: 'ygdvvfnw3h8',
|
||||
description: 'unique id of ui schema node',
|
||||
},
|
||||
'x-index': {
|
||||
type: 'integer',
|
||||
exmaple: 1,
|
||||
description: "ui schema's order index",
|
||||
},
|
||||
'x-async': {
|
||||
type: 'boolean',
|
||||
example: false,
|
||||
description: 'async or not',
|
||||
},
|
||||
name: {
|
||||
type: 'string',
|
||||
name: '2qakvs173rs',
|
||||
description: "ui schema's name",
|
||||
},
|
||||
properties: {
|
||||
type: 'object',
|
||||
additionalProperties: {
|
||||
$ref: '#/components/schemas/uiSchema',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
117
packages/plugins/users/src/swagger/index.json
Normal file
117
packages/plugins/users/src/swagger/index.json
Normal file
@ -0,0 +1,117 @@
|
||||
{
|
||||
"openapi": "3.0.2",
|
||||
"info": {
|
||||
"title": "NocoBase API - Users plugin"
|
||||
},
|
||||
"tags": [],
|
||||
"paths": {
|
||||
"/users:list": {
|
||||
"get": {
|
||||
"tags": ["users"],
|
||||
"description": "",
|
||||
"parameters": [],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/users:get": {
|
||||
"get": {
|
||||
"tags": ["users"],
|
||||
"description": "",
|
||||
"parameters": [],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/users:create": {
|
||||
"post": {
|
||||
"tags": ["users"],
|
||||
"description": "",
|
||||
"parameters": [],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/users:update": {
|
||||
"post": {
|
||||
"tags": ["users"],
|
||||
"description": "",
|
||||
"parameters": [],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/users:updateProfile": {
|
||||
"post": {
|
||||
"tags": ["users"],
|
||||
"description": "",
|
||||
"parameters": [],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/users:changePassword": {
|
||||
"post": {
|
||||
"tags": ["users"],
|
||||
"description": "",
|
||||
"parameters": [],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/users:destroy": {
|
||||
"post": {
|
||||
"tags": ["users"],
|
||||
"description": "",
|
||||
"parameters": [],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/{collectionName}/{collectionIndex}/createdBy:get": {
|
||||
"post": {
|
||||
"tags": ["$collection.createdBy"],
|
||||
"description": "",
|
||||
"parameters": [],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/{collectionName}/{collectionIndex}/updatedBy:get": {
|
||||
"post": {
|
||||
"tags": ["$collection.updatedBy"],
|
||||
"description": "",
|
||||
"parameters": [],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
117
packages/plugins/verification/src/swagger/index.json
Normal file
117
packages/plugins/verification/src/swagger/index.json
Normal file
@ -0,0 +1,117 @@
|
||||
{
|
||||
"openapi": "3.0.2",
|
||||
"info": {
|
||||
"title": "NocoBase API - Verification plugin"
|
||||
},
|
||||
"tags": [],
|
||||
"paths": {
|
||||
"/verifications:list": {
|
||||
"get": {
|
||||
"tags": ["verifications"],
|
||||
"description": "",
|
||||
"parameters": [],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/verifications:get": {
|
||||
"get": {
|
||||
"tags": ["verifications"],
|
||||
"description": "",
|
||||
"parameters": [],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/verifications:create": {
|
||||
"post": {
|
||||
"tags": ["verifications"],
|
||||
"description": "",
|
||||
"parameters": [],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/verifications:update": {
|
||||
"post": {
|
||||
"tags": ["verifications"],
|
||||
"description": "",
|
||||
"parameters": [],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/verifications:destroy": {
|
||||
"post": {
|
||||
"tags": ["verifications"],
|
||||
"description": "",
|
||||
"parameters": [],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/verifications_providers:list": {
|
||||
"get": {
|
||||
"tags": ["verifications_providers"],
|
||||
"description": "",
|
||||
"parameters": [],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/verifications_providers:create": {
|
||||
"post": {
|
||||
"tags": ["verifications_providers"],
|
||||
"description": "",
|
||||
"parameters": [],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/verifications_providers:update": {
|
||||
"post": {
|
||||
"tags": ["verifications_providers"],
|
||||
"description": "",
|
||||
"parameters": [],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/verifications_providers:destroy": {
|
||||
"post": {
|
||||
"tags": ["verifications_providers"],
|
||||
"description": "",
|
||||
"parameters": [],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -89,7 +89,7 @@ function useUpdateAction() {
|
||||
return;
|
||||
}
|
||||
await form.submit();
|
||||
await api.resource('flow_nodes', data.id).update?.({
|
||||
await api.resource('flow_nodes').update?.({
|
||||
filterByTk: data.id,
|
||||
values: {
|
||||
config: form.values,
|
||||
@ -181,7 +181,7 @@ export function RemoveButton() {
|
||||
if (!workflow) {
|
||||
return null;
|
||||
}
|
||||
const resource = api.resource('workflows.nodes', workflow.id);
|
||||
const resource = api.resource('flow_nodes');
|
||||
|
||||
async function onRemove() {
|
||||
async function onOk() {
|
||||
|
723
packages/plugins/workflow/src/swagger/index.ts
Normal file
723
packages/plugins/workflow/src/swagger/index.ts
Normal file
@ -0,0 +1,723 @@
|
||||
export default {
|
||||
openapi: '3.0.2',
|
||||
info: {
|
||||
title: 'NocoBase API - Workflow plugin',
|
||||
},
|
||||
tags: [],
|
||||
paths: {
|
||||
'/workflows:list': {
|
||||
get: {
|
||||
tags: ['workflows'],
|
||||
description: '',
|
||||
parameters: [],
|
||||
responses: {
|
||||
200: {
|
||||
description: 'OK',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
allOf: [
|
||||
{
|
||||
$ref: '#/components/schemas/workflow',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
'/workflows:get': {
|
||||
get: {
|
||||
tags: ['workflows'],
|
||||
description: 'Get single workflow',
|
||||
parameters: [],
|
||||
responses: {
|
||||
200: {
|
||||
description: 'OK',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
allOf: [
|
||||
{
|
||||
$ref: '#/components/schemas/workflow',
|
||||
},
|
||||
{
|
||||
type: 'object',
|
||||
properties: {
|
||||
nodes: {
|
||||
type: 'array',
|
||||
items: {
|
||||
$ref: '#/components/schemas/node',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
'/workflows:create': {
|
||||
post: {
|
||||
tags: ['workflows'],
|
||||
description: 'Create new workflow',
|
||||
parameters: [],
|
||||
requestBody: {
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
title: {
|
||||
$ref: '#/components/schemas/workflow/properties/title',
|
||||
},
|
||||
type: {
|
||||
$ref: '#/components/schemas/workflow/properties/type',
|
||||
},
|
||||
description: {
|
||||
$ref: '#/components/schemas/workflow/properties/description',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
responses: {
|
||||
200: {
|
||||
description: 'OK',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
allOf: [
|
||||
{
|
||||
$ref: '#/components/schemas/workflow',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
'/workflows:update': {
|
||||
post: {
|
||||
tags: ['workflows'],
|
||||
description: 'Update a workflow',
|
||||
parameters: [],
|
||||
requestBody: {
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
title: {
|
||||
$ref: '#/components/schemas/workflow/properties/title',
|
||||
},
|
||||
enabled: {
|
||||
$ref: '#/components/schemas/workflow/properties/enabled',
|
||||
},
|
||||
description: {
|
||||
$ref: '#/components/schemas/workflow/properties/description',
|
||||
},
|
||||
config: {
|
||||
$ref: '#/components/schemas/workflow/properties/config',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
responses: {
|
||||
200: {
|
||||
description: 'OK',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
'/workflows:destroy': {
|
||||
post: {
|
||||
tags: ['workflows'],
|
||||
description: 'Delete workflows. Also will delete all nodes and executions of the workflow.',
|
||||
parameters: [
|
||||
{
|
||||
name: 'filterByTk',
|
||||
in: 'query',
|
||||
description: 'Primary key of a record',
|
||||
schema: {
|
||||
type: 'integer',
|
||||
description: 'ID. The only workflow with ID will be deleted.',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'filter',
|
||||
in: 'query',
|
||||
description: 'Filter',
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
key: {
|
||||
type: 'string',
|
||||
description: 'Key. If provided, all workflow with same key will be deleted.',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
responses: {
|
||||
200: {
|
||||
description: 'OK',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
'/workflows:revision': {
|
||||
post: {
|
||||
tags: ['workflows'],
|
||||
description: 'Duplicate a workflow to a new version or a new workflow. All nodes will be duplicated too.',
|
||||
parameters: [
|
||||
{
|
||||
name: 'filterByTk',
|
||||
in: 'query',
|
||||
description: 'Primary key of a record',
|
||||
schema: {
|
||||
type: 'integer',
|
||||
description: 'ID. The workflow to duplicate.',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'filter',
|
||||
in: 'query',
|
||||
description: 'Filter',
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
key: {
|
||||
type: 'string',
|
||||
description:
|
||||
'Key. If provided, only duplicate to a new version. Or will be duplicated to a new workflow.',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
responses: {
|
||||
200: {
|
||||
description: 'OK',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
'/workflows:trigger': {
|
||||
post: {
|
||||
tags: ['workflows'],
|
||||
description: '',
|
||||
parameters: [
|
||||
{
|
||||
name: 'triggerWorkflows',
|
||||
in: 'query',
|
||||
description:
|
||||
'A combined string to describe workflows to trigger and context data to use. e.g. `?triggerWorkflows=1,2!category`',
|
||||
schema: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
],
|
||||
responses: {
|
||||
202: {
|
||||
description: 'Accepted',
|
||||
},
|
||||
400: {
|
||||
description: 'Bad Request',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
'/workflows/{workflowId}/nodes:create': {
|
||||
post: {
|
||||
tags: ['workflows.nodes'],
|
||||
description: 'Create a new node in workflow',
|
||||
parameters: [],
|
||||
requestBody: {
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
title: {
|
||||
$ref: '#/components/schemas/node/properties/title',
|
||||
},
|
||||
type: {
|
||||
$ref: '#/components/schemas/node/properties/type',
|
||||
},
|
||||
upstreamId: {
|
||||
$ref: '#/components/schemas/node/properties/upstreamId',
|
||||
},
|
||||
branchIndex: {
|
||||
$ref: '#/components/schemas/node/properties/branchIndex',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
responses: {
|
||||
200: {
|
||||
description: 'OK',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
allOf: [
|
||||
{
|
||||
$ref: '#/components/schemas/node',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
'/flow_nodes:update': {
|
||||
post: {
|
||||
tags: ['flow_nodes'],
|
||||
description: 'Update node properties.',
|
||||
parameters: [
|
||||
{
|
||||
name: 'filterByTk',
|
||||
in: 'query',
|
||||
description: 'Primary key of a record',
|
||||
schema: {
|
||||
type: 'integer',
|
||||
description: 'ID',
|
||||
},
|
||||
},
|
||||
],
|
||||
requestBody: {
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
title: {
|
||||
$ref: '#/components/schemas/node/properties/title',
|
||||
},
|
||||
config: {
|
||||
$ref: '#/components/schemas/node/properties/config',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
responses: {
|
||||
200: {
|
||||
description: 'OK',
|
||||
},
|
||||
400: {
|
||||
description: 'Bad Request. Node in exected workflow cannot be updated.',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
'/flow_nodes:destroy': {
|
||||
post: {
|
||||
tags: ['flow_nodes'],
|
||||
description: 'Delete a node. All nodes in sub-branches will also be deleted.',
|
||||
parameters: [
|
||||
{
|
||||
name: 'filterByTk',
|
||||
in: 'query',
|
||||
description: 'Primary key of a record',
|
||||
schema: {
|
||||
type: 'integer',
|
||||
description: 'ID',
|
||||
},
|
||||
},
|
||||
],
|
||||
responses: {
|
||||
200: {
|
||||
description: 'OK',
|
||||
},
|
||||
400: {
|
||||
description: 'Bad Request. Node in exected workflow cannot be deleted.',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
'/executions:list': {
|
||||
get: {
|
||||
tags: ['executions'],
|
||||
description: 'Get list of executions',
|
||||
parameters: [],
|
||||
responses: {
|
||||
200: {
|
||||
description: 'OK',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'array',
|
||||
description: 'List of executions',
|
||||
items: {
|
||||
$ref: '#/components/schemas/execution',
|
||||
type: 'object',
|
||||
not: {
|
||||
$ref: '#/components/schemas/execution/properties/jobs',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
'/executions:get': {
|
||||
get: {
|
||||
tags: ['executions'],
|
||||
description: 'Get single execution',
|
||||
parameters: [
|
||||
{
|
||||
name: 'filterByTk',
|
||||
in: 'query',
|
||||
description: 'Primary key of a record',
|
||||
schema: {
|
||||
type: 'integer',
|
||||
description: 'ID',
|
||||
},
|
||||
},
|
||||
],
|
||||
responses: {
|
||||
200: {
|
||||
description: 'OK',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
$ref: '#/components/schemas/execution',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
'/users_jobs:list': {
|
||||
get: {
|
||||
tags: ['users_jobs'],
|
||||
description: 'List manual jobs',
|
||||
parameters: [],
|
||||
responses: {
|
||||
200: {
|
||||
description: 'OK',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'array',
|
||||
description: 'List of manual jobs',
|
||||
items: {
|
||||
$ref: '#/components/schemas/user_job',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
'/users_jobs:get': {
|
||||
get: {
|
||||
tags: ['users_jobs'],
|
||||
description: 'Single user job',
|
||||
parameters: [],
|
||||
responses: {
|
||||
200: {
|
||||
description: 'OK',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
allOf: [
|
||||
{
|
||||
$ref: '#/components/schemas/user_job',
|
||||
},
|
||||
{
|
||||
type: 'object',
|
||||
properties: {
|
||||
execution: {
|
||||
$ref: '#/components/schemas/execution',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
'/users_jobs:submit': {
|
||||
post: {
|
||||
tags: ['users_jobs'],
|
||||
description: '',
|
||||
parameters: [
|
||||
{
|
||||
name: 'filterByTk',
|
||||
in: 'query',
|
||||
description: 'Primary key of a record',
|
||||
schema: {
|
||||
type: 'integer',
|
||||
description: 'ID',
|
||||
},
|
||||
},
|
||||
],
|
||||
requestBody: {
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
result: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
$formKey: {
|
||||
type: 'object',
|
||||
},
|
||||
_: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
responses: {
|
||||
202: {
|
||||
description: 'Accepted',
|
||||
},
|
||||
400: {
|
||||
description: 'Bad Request. Status of the job is not 0.',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
components: {
|
||||
schemas: {
|
||||
workflow: {
|
||||
type: 'object',
|
||||
description: 'Workflow',
|
||||
properties: {
|
||||
id: {
|
||||
type: 'integer',
|
||||
description: 'ID',
|
||||
},
|
||||
key: {
|
||||
type: 'string',
|
||||
description: 'Key. Variant versions of the same workflow share the same key.',
|
||||
},
|
||||
title: {
|
||||
type: 'string',
|
||||
description: 'Title',
|
||||
},
|
||||
description: {
|
||||
type: 'string',
|
||||
description: 'Description',
|
||||
},
|
||||
current: {
|
||||
type: 'boolean',
|
||||
description: 'Current version',
|
||||
},
|
||||
enabled: {
|
||||
type: 'boolean',
|
||||
description: 'Enabled',
|
||||
},
|
||||
type: {
|
||||
type: 'string',
|
||||
description: 'Event type',
|
||||
},
|
||||
config: {
|
||||
type: 'object',
|
||||
description: 'Configuration JSON object',
|
||||
},
|
||||
nodes: {
|
||||
type: 'array',
|
||||
description: 'Workflow nodes',
|
||||
},
|
||||
executions: {
|
||||
type: 'array',
|
||||
description: 'Executions',
|
||||
},
|
||||
revisions: {
|
||||
type: 'array',
|
||||
description: 'Revisions',
|
||||
},
|
||||
executed: {
|
||||
type: 'integer',
|
||||
description: 'Executed count for a single version',
|
||||
},
|
||||
allExecuted: {
|
||||
type: 'integer',
|
||||
description: 'Executed count for all versions of the same workflow',
|
||||
},
|
||||
},
|
||||
},
|
||||
node: {
|
||||
type: 'object',
|
||||
description: 'Workflow node',
|
||||
properties: {
|
||||
id: {
|
||||
type: 'integer',
|
||||
description: 'ID',
|
||||
},
|
||||
title: {
|
||||
type: 'string',
|
||||
description: 'Title',
|
||||
},
|
||||
workflowId: {
|
||||
type: 'integer',
|
||||
description: 'Workflow ID',
|
||||
},
|
||||
upstreamId: {
|
||||
type: 'integer',
|
||||
description: 'Upstream node ID',
|
||||
},
|
||||
upstream: {
|
||||
type: 'object',
|
||||
description: 'Upstream node',
|
||||
$ref: '#/components/schemas/node',
|
||||
},
|
||||
downstreamId: {
|
||||
type: 'integer',
|
||||
description: 'Downstream node ID in flow, not in sub-branches',
|
||||
$ref: '#/components/schemas/node',
|
||||
},
|
||||
downstream: {
|
||||
type: 'object',
|
||||
description: 'Downstream node',
|
||||
$ref: '#/components/schemas/node',
|
||||
},
|
||||
type: {
|
||||
type: 'string',
|
||||
description: 'Node type',
|
||||
},
|
||||
config: {
|
||||
type: 'object',
|
||||
description: 'Configuration JSON object',
|
||||
},
|
||||
branchIndex: {
|
||||
type: 'integer',
|
||||
description: 'Non-null if the node is a branch node of upstream',
|
||||
},
|
||||
branches: {
|
||||
type: 'array',
|
||||
description: 'Branch nodes under the node',
|
||||
items: {
|
||||
$ref: '#/components/schemas/node',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
execution: {
|
||||
type: 'object',
|
||||
description: 'Execution record of workflow',
|
||||
properties: {
|
||||
id: {
|
||||
type: 'integer',
|
||||
description: 'ID',
|
||||
},
|
||||
key: {
|
||||
type: 'string',
|
||||
description: 'Workflow key',
|
||||
},
|
||||
workflowId: {
|
||||
type: 'integer',
|
||||
description: 'Workflow ID',
|
||||
},
|
||||
context: {
|
||||
type: 'object',
|
||||
description: 'Context data',
|
||||
},
|
||||
status: {
|
||||
type: 'integer',
|
||||
description: 'Status of execution',
|
||||
},
|
||||
jobs: {
|
||||
type: 'array',
|
||||
description: 'Results of executed nodes',
|
||||
items: {
|
||||
$ref: '#/components/schemas/job',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
job: {
|
||||
type: 'object',
|
||||
description: 'Job record of exected node',
|
||||
properties: {
|
||||
id: {
|
||||
type: 'integer',
|
||||
description: 'ID',
|
||||
},
|
||||
executionId: {
|
||||
type: 'integer',
|
||||
description: 'Execution ID',
|
||||
},
|
||||
nodeId: {
|
||||
type: 'integer',
|
||||
description: 'Node ID',
|
||||
},
|
||||
status: {
|
||||
type: 'integer',
|
||||
description: 'Status of job',
|
||||
},
|
||||
result: {
|
||||
type: 'object',
|
||||
description: 'Result data of job',
|
||||
},
|
||||
},
|
||||
},
|
||||
user_job: {
|
||||
type: 'object',
|
||||
description: 'User job',
|
||||
properties: {
|
||||
id: {
|
||||
type: 'integer',
|
||||
description: 'ID',
|
||||
},
|
||||
executionId: {
|
||||
type: 'integer',
|
||||
description: 'Execution ID',
|
||||
},
|
||||
nodeId: {
|
||||
type: 'integer',
|
||||
description: 'Node ID',
|
||||
},
|
||||
workflowId: {
|
||||
type: 'integer',
|
||||
description: 'Workflow ID',
|
||||
},
|
||||
userId: {
|
||||
type: 'integer',
|
||||
description: 'User ID',
|
||||
},
|
||||
status: {
|
||||
type: 'integer',
|
||||
description: 'Status of job',
|
||||
},
|
||||
result: {
|
||||
type: 'object',
|
||||
description: 'Result data of job',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
@ -6,6 +6,7 @@
|
||||
"dependencies": {
|
||||
"@formily/json-schema": "2.x",
|
||||
"@nocobase/plugin-acl": "0.12.0-alpha.5",
|
||||
"@nocobase/plugin-api-doc": "0.12.0-alpha.5",
|
||||
"@nocobase/plugin-api-keys": "0.12.0-alpha.5",
|
||||
"@nocobase/plugin-audit-logs": "0.12.0-alpha.5",
|
||||
"@nocobase/plugin-auth": "0.12.0-alpha.5",
|
||||
|
@ -42,6 +42,7 @@ export class PresetNocoBase extends Plugin {
|
||||
'api-keys',
|
||||
'localization-management',
|
||||
'theme-editor',
|
||||
'api-doc',
|
||||
];
|
||||
|
||||
splitNames(name: string) {
|
||||
|
96
yarn.lock
96
yarn.lock
@ -456,19 +456,61 @@
|
||||
csstype "^3.0.8"
|
||||
tslib "^2.0.3"
|
||||
|
||||
"@antv/x6-react-shape@^1.6.2":
|
||||
version "1.6.4"
|
||||
resolved "https://registry.npmmirror.com/@antv/x6-react-shape/-/x6-react-shape-1.6.4.tgz#e7e9368ce6e0b896cc9882cb08f516406a955fb6"
|
||||
|
||||
"@antv/x6@^1.9.0":
|
||||
version "1.34.13"
|
||||
resolved "https://registry.npmmirror.com/@antv/x6/-/x6-1.34.13.tgz#d87df19b24d2005433b8c18289f8556f256a5530"
|
||||
"@antv/x6-common@^2.0.13":
|
||||
version "2.0.14"
|
||||
resolved "https://registry.npmmirror.com/@antv/x6-common/-/x6-common-2.0.14.tgz#ab89959df2f4bfb7626615ab60fc7203d339359a"
|
||||
integrity sha512-/8OmYV2hxUIwNKt/YurWhGmhbTBu95mZRcF5cwUVp0oR0SmOGNFqMT710w1uvLAfWmfdoq/oEcCuScW/L8mOLw==
|
||||
dependencies:
|
||||
csstype "^3.0.3"
|
||||
jquery "^3.5.1"
|
||||
jquery-mousewheel "^3.1.13"
|
||||
lodash-es "^4.17.15"
|
||||
mousetrap "^1.6.5"
|
||||
utility-types "^3.10.0"
|
||||
|
||||
"@antv/x6-geometry@^2.0.5":
|
||||
version "2.0.5"
|
||||
resolved "https://registry.npmmirror.com/@antv/x6-geometry/-/x6-geometry-2.0.5.tgz#c158317d74135bedd78c2fdeb76f9c7cfa0ef0aa"
|
||||
integrity sha512-MId6riEQkxphBpVeTcL4ZNXL4lScyvDEPLyIafvWMcWNTGK0jgkK7N20XSzqt8ltJb0mGUso5s56mrk8ysHu2A==
|
||||
|
||||
"@antv/x6-plugin-dnd@^2.0.0":
|
||||
version "2.0.6"
|
||||
resolved "https://registry.npmmirror.com/@antv/x6-plugin-dnd/-/x6-plugin-dnd-2.0.6.tgz#403b8f45dc18f0622512298f556a531ed10a158b"
|
||||
integrity sha512-HD0PgL27ZV+X1Q0gwQ0ltm67J5okvAdGndFv5p8Pp9bV5PKe8B2BzLF1CZwhBroz58xkXJVcGH0TT4NmrHjbRw==
|
||||
|
||||
"@antv/x6-plugin-export@^2.0.0":
|
||||
version "2.1.6"
|
||||
resolved "https://registry.npmmirror.com/@antv/x6-plugin-export/-/x6-plugin-export-2.1.6.tgz#f8cedab3707c43728baaf0f0bfb1e75ec4a61a30"
|
||||
integrity sha512-m0ukMmZhrFE5n7uCR43DVQBdiUfpjGN+vm1mc+6RTZdHK8pa6Mxr0RZztaxPy34YA4tli+bGY3ePslsNPfh6PQ==
|
||||
|
||||
"@antv/x6-plugin-minimap@^2.0.0":
|
||||
version "2.0.5"
|
||||
resolved "https://registry.npmmirror.com/@antv/x6-plugin-minimap/-/x6-plugin-minimap-2.0.5.tgz#bd4699408715b4709e77e83edf14ceb0e767b1f6"
|
||||
integrity sha512-6L+W80AvoEZG4cwRxgPxvYLeKhqkBJEt4hTnZ2NWWcvBx02F9RJKRZVEEUemqE12mKUv4HT6WtbQV0QIfkTEMw==
|
||||
|
||||
"@antv/x6-plugin-scroller@^2.0.0":
|
||||
version "2.0.10"
|
||||
resolved "https://registry.npmmirror.com/@antv/x6-plugin-scroller/-/x6-plugin-scroller-2.0.10.tgz#bdd39d3530e469bbe72c5feca3b6787512232a3d"
|
||||
integrity sha512-CrC5zCbtPe7LJEKK8ucLcDN+9lMtTJNApFmFObAiR/y2LO1QFCHz8GGLVeNOZaIEmJbR/Z/etpJkvc1LE7WhTg==
|
||||
|
||||
"@antv/x6-plugin-selection@^2.0.0":
|
||||
version "2.1.7"
|
||||
resolved "https://registry.npmmirror.com/@antv/x6-plugin-selection/-/x6-plugin-selection-2.1.7.tgz#8964041d595b732bec5883a1f77757bededec594"
|
||||
integrity sha512-ODfYLNwKSaLgIYMYfMW4dYQ9KKFOVBEdH0BzvyG8mfRol+JtZuyrm4BRbAvMryQKHrSxs4JCYXTFd2F7ZTuxLQ==
|
||||
|
||||
"@antv/x6-plugin-snapline@^2.0.0":
|
||||
version "2.1.7"
|
||||
resolved "https://registry.npmmirror.com/@antv/x6-plugin-snapline/-/x6-plugin-snapline-2.1.7.tgz#1b4e3be0a281ba324117b7ac7b9b8c39e484cce2"
|
||||
integrity sha512-AsysoCb9vES0U2USNhEpYuO/W8I0aYfkhlbee5Kt4NYiMfQfZKQyqW/YjDVaS2pm38C1NKu1LdPVk/BBr4CasA==
|
||||
|
||||
"@antv/x6-react-shape@^2.0.0":
|
||||
version "2.2.2"
|
||||
resolved "https://registry.npmmirror.com/@antv/x6-react-shape/-/x6-react-shape-2.2.2.tgz#b0caf31219fa3219b4e7a4960583e2c3affb870c"
|
||||
integrity sha512-uwehrdNCPkgBpVfsxEQk1M77Q+q9D7+HkqIm4Fm3Oz4kzkKCoc3KoxCIoLimbANAhqAPEJ40sRR0ZY+lzmP7WQ==
|
||||
|
||||
"@antv/x6@^2.0.0":
|
||||
version "2.13.1"
|
||||
resolved "https://registry.npmmirror.com/@antv/x6/-/x6-2.13.1.tgz#c344eb797edc0e437ab1bed599099a8bd68a4ada"
|
||||
integrity sha512-aAj3SlOi4KhBhpoiX4WCsJ61glztSmZy6Vb0aWZTFZXErfp1sUjTmrThJeTG6IQZbXW+7GcGjFGI04roOa05Bg==
|
||||
dependencies:
|
||||
"@antv/x6-common" "^2.0.13"
|
||||
"@antv/x6-geometry" "^2.0.5"
|
||||
utility-types "^3.10.0"
|
||||
|
||||
"@arvinxu/layout-kit@^1":
|
||||
@ -9639,7 +9681,7 @@ commander@^10.0.0:
|
||||
version "10.0.1"
|
||||
resolved "https://registry.npmmirror.com/commander/-/commander-10.0.1.tgz#881ee46b4f77d1c1dccc5823433aa39b022cbe06"
|
||||
|
||||
commander@^2.19.0, commander@^2.20.0, commander@^2.20.3, commander@^2.8.1, commander@^2.9.0:
|
||||
commander@^2.19.0, commander@^2.20.0, commander@^2.8.1, commander@^2.9.0:
|
||||
version "2.20.3"
|
||||
resolved "https://registry.npmmirror.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
|
||||
|
||||
@ -10351,10 +10393,6 @@ cssesc@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.npmmirror.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee"
|
||||
|
||||
cssfilter@0.0.10:
|
||||
version "0.0.10"
|
||||
resolved "https://registry.npmjs.org/cssfilter/-/cssfilter-0.0.10.tgz#c6d2672632a2e5c83e013e6864a42ce8defd20ae"
|
||||
|
||||
cssnano-preset-default@^4.0.8:
|
||||
version "4.0.8"
|
||||
resolved "https://registry.npmmirror.com/cssnano-preset-default/-/cssnano-preset-default-4.0.8.tgz#920622b1fc1e95a34e8838203f1397a504f2d3ff"
|
||||
@ -10489,7 +10527,7 @@ cssstyle@^2.3.0:
|
||||
dependencies:
|
||||
cssom "~0.3.6"
|
||||
|
||||
csstype@^3.0.10, csstype@^3.0.2, csstype@^3.0.3, csstype@^3.0.8:
|
||||
csstype@^3.0.10, csstype@^3.0.2, csstype@^3.0.8:
|
||||
version "3.1.2"
|
||||
resolved "https://registry.npmmirror.com/csstype/-/csstype-3.1.2.tgz#1d4bf9d572f11c14031f0436e1c10bc1f571f50b"
|
||||
|
||||
@ -16240,14 +16278,6 @@ joycon@^3.0.1:
|
||||
version "3.1.1"
|
||||
resolved "https://registry.yarnpkg.com/joycon/-/joycon-3.1.1.tgz#bce8596d6ae808f8b68168f5fc69280996894f03"
|
||||
|
||||
jquery-mousewheel@^3.1.13:
|
||||
version "3.1.13"
|
||||
resolved "https://registry.npmmirror.com/jquery-mousewheel/-/jquery-mousewheel-3.1.13.tgz#06f0335f16e353a695e7206bf50503cb523a6ee5"
|
||||
|
||||
jquery@^3.5.1:
|
||||
version "3.7.0"
|
||||
resolved "https://registry.npmmirror.com/jquery/-/jquery-3.7.0.tgz#fe2c01a05da500709006d8790fe21c8a39d75612"
|
||||
|
||||
js-base64@^2.5.2:
|
||||
version "2.6.4"
|
||||
resolved "https://registry.npmmirror.com/js-base64/-/js-base64-2.6.4.tgz#f4e686c5de1ea1f867dbcad3d46d969428df98c4"
|
||||
@ -18218,10 +18248,6 @@ moo@^0.5.0, moo@^0.5.1:
|
||||
version "0.5.2"
|
||||
resolved "https://registry.npmmirror.com/moo/-/moo-0.5.2.tgz#f9fe82473bc7c184b0d32e2215d3f6e67278733c"
|
||||
|
||||
mousetrap@^1.6.5:
|
||||
version "1.6.5"
|
||||
resolved "https://registry.npmmirror.com/mousetrap/-/mousetrap-1.6.5.tgz#8a766d8c272b08393d5f56074e0b5ec183485bf9"
|
||||
|
||||
move-concurrently@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.npmmirror.com/move-concurrently/-/move-concurrently-1.0.1.tgz#be2c005fda32e0b29af1f05d7c4b33214c701f92"
|
||||
@ -23992,6 +24018,11 @@ svgson@^4.1.0:
|
||||
omit-deep "0.3.0"
|
||||
xml-reader "2.4.3"
|
||||
|
||||
swagger-ui-dist@^5.3.1:
|
||||
version "5.3.1"
|
||||
resolved "https://registry.npmmirror.com/swagger-ui-dist/-/swagger-ui-dist-5.3.1.tgz#ae76a74136152d790b06a8b71ca389cac35ab78f"
|
||||
integrity sha512-El78OvXp9zMasfPrshtkW1CRx8AugAKoZuGGOTW+8llJzOV1RtDJYqQRz/6+2OakjeWWnZuRlN2Qj1Y0ilux3w==
|
||||
|
||||
swr@^2.0.0:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.npmmirror.com/swr/-/swr-2.2.0.tgz#575c6ac1bec087847f4c86a39ccbc0043c834d6a"
|
||||
@ -26000,13 +26031,6 @@ xregexp@2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.npmmirror.com/xregexp/-/xregexp-2.0.0.tgz#52a63e56ca0b84a7f3a5f3d61872f126ad7a5943"
|
||||
|
||||
xss@^1.0.14:
|
||||
version "1.0.14"
|
||||
resolved "https://registry.npmjs.org/xss/-/xss-1.0.14.tgz#4f3efbde75ad0d82e9921cc3c95e6590dd336694"
|
||||
dependencies:
|
||||
commander "^2.20.3"
|
||||
cssfilter "0.0.10"
|
||||
|
||||
xtend@^4.0.0, xtend@~4.0.0, xtend@~4.0.1:
|
||||
version "4.0.2"
|
||||
resolved "https://registry.npmmirror.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"
|
||||
|
Loading…
Reference in New Issue
Block a user