Skip to content

Commit

Permalink
chore: openapi doc generator (#2644)
Browse files Browse the repository at this point in the history
* chore: extract the type and comment from apis

* chore: template code

* feat: openapi

* pref: openapi generator. send into public/openapi folder
  • Loading branch information
FinleyGe authored Sep 9, 2024
1 parent 5f3c8e9 commit 78ad279
Show file tree
Hide file tree
Showing 12 changed files with 2,221 additions and 31 deletions.
3 changes: 1 addition & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,7 @@
"postinstall": "sh ./scripts/postinstall.sh",
"initIcon": "node ./scripts/icon/init.js",
"previewIcon": "node ./scripts/icon/index.js",
"i18n:delete-unused-keys": "node ./scripts/i18n/delete-unused-keys.js",
"i18n:query": "node ./scripts/i18n/query.js"
"api:gen": "tsc ./scripts/openapi/index.ts && node ./scripts/openapi/index.js && npx @redocly/cli build-docs ./scripts/openapi/openapi.json -o ./projects/app/public/openapi/index.html"
},
"devDependencies": {
"@chakra-ui/cli": "^2.4.1",
Expand Down
1,245 changes: 1,218 additions & 27 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

443 changes: 443 additions & 0 deletions projects/app/public/openapi/index.html

Large diffs are not rendered by default.

16 changes: 14 additions & 2 deletions projects/app/src/pages/api/support/outLink/list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,25 @@ import { ManagePermissionVal } from '@fastgpt/global/support/permission/constant
import type { ApiRequestProps } from '@fastgpt/service/type/next';
import { NextAPI } from '@/service/middleware/entry';
import { OutLinkSchema } from '@fastgpt/global/support/outLink/type';

export const ApiMetadata = {
name: '获取应用内所有 Outlink',
author: 'Finley',
version: '0.1.0'
};

// Outlink
export type OutLinkListQuery = {
appId: string;
type: string;
appId: string; // 应用 ID
type: string; // 类型
};

export type OutLinkListBody = {};

// 响应: 应用内全部 Outlink
export type OutLinkListResponse = OutLinkSchema[];

// 查询应用内全部 Outlink
async function handler(
req: ApiRequestProps<OutLinkListBody, OutLinkListQuery>
): Promise<OutLinkListResponse> {
Expand Down
1 change: 1 addition & 0 deletions scripts/openapi/.env
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
SEARCH_PATH=support
3 changes: 3 additions & 0 deletions scripts/openapi/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
*.js
openapi.json
openapi.out
54 changes: 54 additions & 0 deletions scripts/openapi/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { parseAPI } from './utils';
import * as fs from 'fs';
import * as path from 'path';
import { convertOpenApi } from './openapi';

const rootPath = 'projects/app/src/pages/api';
const exclude = ['/admin', '/proApi'];

function getAllFiles(dir: string) {
let files: string[] = [];
const stat = fs.statSync(dir);
if (stat.isDirectory()) {
const list = fs.readdirSync(dir);
list.forEach((item) => {
const fullPath = path.join(dir, item);
if (!exclude.some((excluded) => fullPath.includes(excluded))) {
files = files.concat(getAllFiles(fullPath));
}
});
} else {
files.push(dir);
}
return files;
}

const searchPath = process.env.SEARCH_PATH || '';

const files = getAllFiles(path.join(rootPath, searchPath));
// console.log(files)
const apis = files.map((file) => {
return parseAPI({ path: file, rootPath });
});

const openapi = convertOpenApi({
apis,
openapi: '3.0.0',
info: {
title: 'FastGPT OpenAPI',
version: '1.0.0',
author: 'FastGPT'
},
servers: [
{
url: 'http://localhost:4000'
}
]
});

const json = JSON.stringify(openapi, null, 2);

fs.writeFileSync('./scripts/openapi/openapi.json', json);
fs.writeFileSync('./scripts/openapi/openapi.out', JSON.stringify(apis, null, 2));

console.log('Total APIs:', files.length);
181 changes: 181 additions & 0 deletions scripts/openapi/openapi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
import { ApiType } from './type';

type OpenAPIParameter = {
name: string;
in: string;
description: string;
required: boolean;
schema: {
type: string;
};
};

type OpenAPIResponse = {
[code: string]: {
description?: string;
content: {
[mediaType: string]: {
schema: {
type: string;
properties?: {
[key: string]: {
type: string;
description?: string;
};
};
};
};
};
};
};

type PathType = {
[method: string]: {
description: string;
parameters: OpenAPIParameter[];
responses: OpenAPIResponse;
};
};

type PathsType = {
[url: string]: PathType;
};

type OpenApiType = {
openapi: string;
info: {
title: string;
version: string;
author: string;
};
paths: PathsType;
servers?: {
url: string;
}[];
};

export function convertPath(api: ApiType): PathType {
const method = api.method.toLowerCase();
const parameters: any[] = [];
if (api.query) {
if (Array.isArray(api.query)) {
api.query.forEach((item) => {
parameters.push({
name: item.key,
description: item.comment,
in: 'query',
required: item.required,
schema: {
type: item.type
}
});
});
} else {
parameters.push({
description: api.query.comment,
name: api.query.key,
in: 'query',
required: api.query.required,
schema: {
type: api.query.type
}
});
}
} else if (api.body) {
if (Array.isArray(api.body)) {
api.body.forEach((item) => {
parameters.push({
description: item.comment,
name: item.key,
in: 'body',
required: item.required,
schema: {
type: item.type
}
});
});
}
}

const responses: OpenAPIResponse = (() => {
if (api.response) {
if (Array.isArray(api.response)) {
const properties: {
[key: string]: {
type: string;
description?: string;
};
} = {};

api.response.forEach((item) => {
properties[item.type] = {
type: item.key ?? item.type,
description: item.comment
};
});
const res: OpenAPIResponse = {
'200': {
description: api.description ?? '',
content: {
'application/json': {
schema: {
type: 'object',
properties
}
}
}
}
};
return res;
} else {
return {
'200': {
description: api.response.comment ?? '',
content: {
'application/json': {
schema: {
type: api.response.type
}
}
}
}
};
}
} else {
return {
'200': {
description: api.description ?? '',
content: {
'application/json': {
schema: {
type: 'object'
}
}
}
}
};
}
})();
return {
[method]: {
description: api.description ?? '',
parameters,
responses
}
};
}
export function convertOpenApi({
apis,
...rest
}: {
apis: ApiType[];
} & Omit<OpenApiType, 'paths'>): OpenApiType {
const paths: PathsType = {};
apis.forEach((api) => {
paths[api.url] = convertPath(api);
});
return {
paths,
...rest
};
}
21 changes: 21 additions & 0 deletions scripts/openapi/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"name": "test",
"module": "index.js",
"scripts": {
"build": "tsc index.ts"
},
"devDependencies": {
"@babel/types": "^7.25.6",
"@types/babel__generator": "^7.6.8",
"@types/babel__traverse": "^7.20.6"
},
"peerDependencies": {
"typescript": "^5.0.0"
},
"dependencies": {
"@babel/generator": "^7.25.6",
"@babel/parser": "^7.25.6",
"@babel/traverse": "^7.25.6",
"babel": "^6.23.0"
}
}
40 changes: 40 additions & 0 deletions scripts/openapi/template.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
```ts
import type { ApiRequestProps, ApiResponseType } from '@fastgpt/service/type/next';
import { NextAPI } from '@/service/middleware/entry';

// This should be at the top of the file after the imports
export const ApiMetadata = {
name: 'template example api',
author: 'Finley',
version: '0.1.0',
}

export type TemplateQuery = {
// The App's ID
appId?: string[],
// The App's Name
name: string,
// The App's Description
description: string | Something<AppDetailType>,
};

export type TemplateBody = {
// The App's Name
name: string,
};

// This is the response type for the API
export type TemplateResponse = AppDetailType;

// This is the template API for FASTGPT NextAPI
async function handler(
req: ApiRequestProps<TemplateBody, TemplateQuery>,
res: ApiResponseType<any>,
): Promise<TemplateResponse> {

return {}
}

export default NextAPI(handler);

```
22 changes: 22 additions & 0 deletions scripts/openapi/type.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
export type ApiMetaData = {
name?: string;
author?: string;
version?: string;
};

export type ApiType = {
description?: string;
path: string;
url: string;
query?: itemType | itemType[];
body?: itemType | itemType[];
response?: itemType | itemType[];
method: 'GET' | 'POST' | 'PUT' | 'DELETE';
} & ApiMetaData;

export type itemType = {
comment?: string;
key?: string;
type: string;
required?: boolean;
};
Loading

0 comments on commit 78ad279

Please sign in to comment.