0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2024-12-16 20:26:19 -05:00

chore(core): add supplementary openapi json (#4472)

* chore(core): support supplementary openapi json

* refactor(core): copy json files to build
This commit is contained in:
Gao Sun 2023-09-12 14:27:40 +08:00 committed by GitHub
parent 00e72714de
commit e35d099c35
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 70 additions and 7 deletions

13
.vscode/settings.json vendored
View file

@ -5,7 +5,10 @@
"source.fixAll.stylelint": true "source.fixAll.stylelint": true
} }
}, },
"stylelint.validate": ["css", "scss"], "stylelint.validate": [
"css",
"scss"
],
"eslint.workingDirectories": [ "eslint.workingDirectories": [
{ {
"pattern": "./packages/*", "pattern": "./packages/*",
@ -23,6 +26,14 @@
"editor.codeActionsOnSave": { "editor.codeActionsOnSave": {
"source.fixAll.eslint": true, "source.fixAll.eslint": true,
}, },
"json.schemas": [
{
"fileMatch": [
"packages/core/src/routes/*.openapi.json"
],
"url": "https://raw.githubusercontent.com/OAI/OpenAPI-Specification/main/schemas/v3.0/schema.json"
}
],
"cSpell.words": [ "cSpell.words": [
"Alipay", "Alipay",
"CIAM", "CIAM",

View file

@ -12,7 +12,7 @@
}, },
"scripts": { "scripts": {
"precommit": "lint-staged", "precommit": "lint-staged",
"copyfiles": "copyfiles -u 1 src/**/*.md build", "copyfiles": "copyfiles -u 1 src/routes/**/*.openapi.json build/",
"build": "rm -rf build/ && tsc -p tsconfig.build.json && pnpm run copyfiles", "build": "rm -rf build/ && tsc -p tsconfig.build.json && pnpm run copyfiles",
"build:test": "rm -rf build/ && tsc -p tsconfig.test.json --sourcemap", "build:test": "rm -rf build/ && tsc -p tsconfig.test.json --sourcemap",
"lint": "eslint --ext .ts src", "lint": "eslint --ext .ts src",

View file

@ -0,0 +1,15 @@
{
"paths": {
"/api/status": {
"get": {
"summary": "Health check",
"description": "The traditional health check API. No authentication needed.\n\n> **Note**\n> Even if 204 is returned, it does not guarantee all the APIs are working properly since they may depend on additional resources or external services.",
"responses": {
"204": {
"description": "The Logto core service is healthy."
}
}
}
}
}
}

View file

@ -51,8 +51,6 @@ describe('GET /swagger.json', () => {
it('should contain the specific paths', async () => { it('should contain the specific paths', async () => {
const response = await mockSwaggerRequest.get('/swagger.json'); const response = await mockSwaggerRequest.get('/swagger.json');
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
expect(Object.entries(response.body.paths)).toHaveLength(2);
expect(response.body.paths).toMatchObject({ expect(response.body.paths).toMatchObject({
/* eslint-disable @typescript-eslint/no-unsafe-assignment */ /* eslint-disable @typescript-eslint/no-unsafe-assignment */
'/api/mock': { '/api/mock': {

View file

@ -1,5 +1,10 @@
import fs from 'node:fs/promises';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { httpCodeToMessage } from '@logto/core-kit'; import { httpCodeToMessage } from '@logto/core-kit';
import { conditionalArray, deduplicate, toTitle } from '@silverhand/essentials'; import { conditionalArray, deduplicate, toTitle } from '@silverhand/essentials';
import deepmerge from 'deepmerge';
import type { IMiddleware } from 'koa-router'; import type { IMiddleware } from 'koa-router';
import type Router from 'koa-router'; import type Router from 'koa-router';
import type { OpenAPIV3 } from 'openapi-types'; import type { OpenAPIV3 } from 'openapi-types';
@ -146,6 +151,27 @@ const isManagementApiRouter = ({ stack }: Router) =>
.filter(({ path }) => !path.includes('.*')) .filter(({ path }) => !path.includes('.*'))
.some(({ stack }) => stack.some((function_) => isKoaAuthMiddleware(function_))); .some(({ stack }) => stack.some((function_) => isKoaAuthMiddleware(function_)));
/**
* Recursively find all supplement files (files end with `.openapi.json`) for the given
* directory.
*/
/* eslint-disable @silverhand/fp/no-mutating-methods, no-await-in-loop */
const findSupplementFiles = async (directory: string) => {
const result: string[] = [];
for (const file of await fs.readdir(directory)) {
const stats = await fs.stat(path.join(directory, file));
if (stats.isDirectory()) {
result.push(...(await findSupplementFiles(path.join(directory, file))));
} else if (file.endsWith('.openapi.json')) {
result.push(path.join(directory, file));
}
}
return result;
};
/* eslint-enable @silverhand/fp/no-mutating-methods, no-await-in-loop */
// Keep using `any` to accept various custom context types. // Keep using `any` to accept various custom context types.
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
export default function swaggerRoutes<T extends AnonymousRouter, R extends Router<unknown, any>>( export default function swaggerRoutes<T extends AnonymousRouter, R extends Router<unknown, any>>(
@ -185,17 +211,30 @@ export default function swaggerRoutes<T extends AnonymousRouter, R extends Route
pathMap.set(path, { ...pathMap.get(path), [method]: operation }); pathMap.set(path, { ...pathMap.get(path), [method]: operation });
} }
const document: OpenAPIV3.Document = { // Current path should be the root directory of routes files.
const supplementPaths = await findSupplementFiles(path.dirname(fileURLToPath(import.meta.url)));
const supplementDocuments = await Promise.all(
supplementPaths.map(
// eslint-disable-next-line no-restricted-syntax
async (path) => JSON.parse(await fs.readFile(path, 'utf8')) as Record<string, unknown>
)
);
const baseDocument: OpenAPIV3.Document = {
openapi: '3.0.1', openapi: '3.0.1',
info: { info: {
title: 'Logto Core', title: 'Logto Core',
version: '0.1.0', description: 'Management APIs for Logto core service.',
version: 'Cloud',
}, },
paths: Object.fromEntries(pathMap), paths: Object.fromEntries(pathMap),
components: { schemas: translationSchemas }, components: { schemas: translationSchemas },
}; };
ctx.body = document; ctx.body = supplementDocuments.reduce(
(document, supplement) => deepmerge(document, supplement),
baseDocument
);
return next(); return next();
}); });