diff --git a/package-lock.json b/package-lock.json index de18eec..8b65efc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "1.0.0", "license": "ISC", "dependencies": { + "@koa/router": "^12.0.0", "dotenv": "^10.0.0", "http-status-codes": "^2.2.0", "js-yaml": "^4.1.0", @@ -32,7 +33,6 @@ "uuid": "^9.0.0" }, "devDependencies": { - "@koa/router": "^12.0.0", "@types/js-yaml": "^4.0.5", "@types/koa": "^2.13.8", "@types/koa__router": "^12.0.0", @@ -43,7 +43,7 @@ "@types/node": "^18.16.18", "@typescript-eslint/eslint-plugin": "^5.60.1", "@typescript-eslint/parser": "^5.60.1", - "axios": "^1.4.0", + "axios": "^1.6.0", "eslint": "^8.43.0", "eslint-config-airbnb-base": "^15.0.0", "eslint-config-prettier": "^8.6.0", @@ -495,7 +495,6 @@ "version": "12.0.0", "resolved": "https://registry.npmjs.org/@koa/router/-/router-12.0.0.tgz", "integrity": "sha512-cnnxeKHXlt7XARJptflGURdJaO+ITpNkOHmQu7NHmCoRinPbyvFzce/EG/E8Zy81yQ1W9MoSdtklc3nyaDReUw==", - "dev": true, "dependencies": { "http-errors": "^2.0.0", "koa-compose": "^4.1.0", @@ -1524,9 +1523,9 @@ } }, "node_modules/axios": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.4.0.tgz", - "integrity": "sha512-S4XCWMEmzvo64T9GfvQDOXgYRDJ/wsSZc7Jvdgx5u1sd0JwsuPLqb3SYmusag+edF6ziyMensPVqLTSc1PiSEA==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.0.tgz", + "integrity": "sha512-EZ1DYihju9pwVB+jg67ogm+Tmqc6JmhamRN6I4Zt8DfZu5lbcQGw3ozH9lFejSJgs/ibaef3A9PMXPLeefFGJg==", "dev": true, "dependencies": { "follow-redirects": "^1.15.0", @@ -4451,7 +4450,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", - "dev": true, "engines": { "node": ">= 0.6" } @@ -5257,8 +5255,7 @@ "node_modules/path-to-regexp": { "version": "6.2.1", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.2.1.tgz", - "integrity": "sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw==", - "dev": true + "integrity": "sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw==" }, "node_modules/path-type": { "version": "4.0.0", @@ -7367,7 +7364,6 @@ "version": "12.0.0", "resolved": "https://registry.npmjs.org/@koa/router/-/router-12.0.0.tgz", "integrity": "sha512-cnnxeKHXlt7XARJptflGURdJaO+ITpNkOHmQu7NHmCoRinPbyvFzce/EG/E8Zy81yQ1W9MoSdtklc3nyaDReUw==", - "dev": true, "requires": { "http-errors": "^2.0.0", "koa-compose": "^4.1.0", @@ -8177,9 +8173,9 @@ "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==" }, "axios": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.4.0.tgz", - "integrity": "sha512-S4XCWMEmzvo64T9GfvQDOXgYRDJ/wsSZc7Jvdgx5u1sd0JwsuPLqb3SYmusag+edF6ziyMensPVqLTSc1PiSEA==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.0.tgz", + "integrity": "sha512-EZ1DYihju9pwVB+jg67ogm+Tmqc6JmhamRN6I4Zt8DfZu5lbcQGw3ozH9lFejSJgs/ibaef3A9PMXPLeefFGJg==", "dev": true, "requires": { "follow-redirects": "^1.15.0", @@ -10357,8 +10353,7 @@ "methods": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", - "dev": true + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==" }, "micromatch": { "version": "4.0.5", @@ -10946,8 +10941,7 @@ "path-to-regexp": { "version": "6.2.1", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.2.1.tgz", - "integrity": "sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw==", - "dev": true + "integrity": "sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw==" }, "path-type": { "version": "4.0.0", diff --git a/package.json b/package.json index 9963e92..bbba109 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,6 @@ "start:dev": "npx nodemon" }, "devDependencies": { - "@koa/router": "^12.0.0", "@types/js-yaml": "^4.0.5", "@types/koa": "^2.13.8", "@types/koa__router": "^12.0.0", @@ -19,7 +18,7 @@ "@types/node": "^18.16.18", "@typescript-eslint/eslint-plugin": "^5.60.1", "@typescript-eslint/parser": "^5.60.1", - "axios": "^1.4.0", + "axios": "^1.6.0", "eslint": "^8.43.0", "eslint-config-airbnb-base": "^15.0.0", "eslint-config-prettier": "^8.6.0", @@ -32,6 +31,7 @@ "prettier": "^2.8.8" }, "dependencies": { + "@koa/router": "^12.0.0", "dotenv": "^10.0.0", "http-status-codes": "^2.2.0", "js-yaml": "^4.1.0", @@ -56,4 +56,4 @@ }, "author": "", "license": "ISC" -} +} \ No newline at end of file diff --git a/src/helpers/common.ts b/src/helpers/common.ts index 38df5e0..b514919 100644 --- a/src/helpers/common.ts +++ b/src/helpers/common.ts @@ -3,7 +3,7 @@ export const Headers = { }; export const ErrorMessages = { - SWAGGER_NOT_PARSABLE: 'Could not parse swagger data', + SWAGGER_NOT_PARSABLE: 'Could not parse swagger file content', SWAGGER_NAME_ALREADY_EXISTS: 'Swagger with the same name already exists', SWAGGER_TITLE_ALREADY_EXISTS: 'Swagger with the same title already exists', SWAGGER_NOT_FOUND: 'Swagger not found', diff --git a/src/server/database/connectors/consts.ts b/src/server/database/connectors/consts.ts index b7ca51f..c749d18 100644 --- a/src/server/database/connectors/consts.ts +++ b/src/server/database/connectors/consts.ts @@ -1,3 +1,4 @@ export const TableNames = { SWAGGERS_TABLE_NAME: 'swaggers', + SWAGGERS_CONTENT_TABLE_NAME: 'swaggers_content', }; diff --git a/src/server/database/connectors/sequlize/migrations/00_initial.ts b/src/server/database/connectors/sequlize/migrations/00_initial.ts index 11750c0..8db559b 100644 --- a/src/server/database/connectors/sequlize/migrations/00_initial.ts +++ b/src/server/database/connectors/sequlize/migrations/00_initial.ts @@ -1,7 +1,17 @@ import { DataTypes } from 'sequelize'; import { TableNames } from '../../consts'; -export const up = async ({ context }) => +export const up = async ({ context }) => { + await context.createTable(TableNames.SWAGGERS_CONTENT_TABLE_NAME, { + id: { + type: DataTypes.UUID, + primaryKey: true, + }, + file_content: { + type: DataTypes.TEXT('long'), + allowNull: false, + }, + }); await context.createTable(TableNames.SWAGGERS_TABLE_NAME, { id: { type: DataTypes.UUID, @@ -12,9 +22,13 @@ export const up = async ({ context }) => type: DataTypes.STRING, allowNull: false, }, - data: { - type: DataTypes.TEXT('long'), + file_content_id: { + type: DataTypes.UUID, allowNull: false, + references: { + model: TableNames.SWAGGERS_CONTENT_TABLE_NAME, + key: 'id', + }, }, created_at: { type: DataTypes.DATE, @@ -25,6 +39,7 @@ export const up = async ({ context }) => allowNull: false, }, }); +}; export const down = async ({ context: queryInterface }) => await queryInterface.dropTable(TableNames.SWAGGERS_TABLE_NAME); diff --git a/src/server/database/models/swagger.ts b/src/server/database/models/swagger.ts index 81e2ed6..0261c6e 100644 --- a/src/server/database/models/swagger.ts +++ b/src/server/database/models/swagger.ts @@ -1,5 +1,5 @@ import { v4 } from 'uuid'; -import { DataTypes, Model, Optional, Sequelize } from 'sequelize'; +import { DataTypes, Model, ModelStatic, Sequelize } from 'sequelize'; import { TableNames } from '../connectors/consts'; export enum SwaggerFormats { @@ -7,30 +7,61 @@ export enum SwaggerFormats { Json = 'json', } -interface SwaggerAttributes { +export interface SwaggerFileContent { + id: string; + file_content: string; +} + +export interface InternalSwaggerResource { id: string; name: string; - data: string; + file_content_id?: string; + FileContent?: SwaggerFileContent; created_at?: Date; updated_at?: Date; } -export type SwaggerRequestBody = Optional< - SwaggerAttributes, - 'id' | 'created_at' | 'updated_at' ->; -export type SwaggerResource = Required; +export type SwaggerRequestBody = { + name: string; + file_content: string; +}; +export type SwaggerResource = Omit< + InternalSwaggerResource, + 'file_content_id' +> & { + file_content?: string; +}; class SwaggerModel - extends Model - implements SwaggerAttributes + extends Model + implements InternalSwaggerResource { + public static FileContent: ModelStatic>; + public id!: string; public name!: string; - public data!: string; + public readonly file_content_id!: string; public readonly created_at!: Date; public readonly updated_at!: Date; public static initializeModel(sequelizeClient: Sequelize): void { + this.FileContent = sequelizeClient.define( + 'FileContent', + { + id: { + type: DataTypes.UUID, + primaryKey: true, + }, + file_content: { + type: DataTypes.TEXT('long'), + allowNull: false, + }, + }, + { + tableName: TableNames.SWAGGERS_CONTENT_TABLE_NAME, + timestamps: false, + } + ); + this.init( { id: { @@ -41,27 +72,68 @@ class SwaggerModel type: DataTypes.STRING, allowNull: false, }, - data: { - type: DataTypes.TEXT('long'), - allowNull: false, - }, }, { tableName: TableNames.SWAGGERS_TABLE_NAME, sequelize: sequelizeClient, } ); + + this.belongsTo(this.FileContent, { + foreignKey: { + name: 'file_content_id', + allowNull: false, + }, + }); } public static fromRequest(requestBody: SwaggerRequestBody): SwaggerResource { const now = new Date(); + return { id: v4(), - ...requestBody, + name: requestBody.name, + file_content: requestBody.file_content, created_at: now, updated_at: now, }; } + + public static toResponse(swagger: SwaggerModel): SwaggerResource { + const { file_content_id, FileContent, ...restSwaggerProps } = + swagger.dataValues; + + if (FileContent) { + return this.toResponseWithFileContent(restSwaggerProps, FileContent); + } + + return restSwaggerProps; + } + + private static toResponseWithFileContent( + swagger: SwaggerResource, + swaggerFileContent: SwaggerFileContent + ): SwaggerResource { + return { + id: swagger.id, + name: swagger.name, + file_content: swaggerFileContent.file_content, + created_at: swagger.created_at, + updated_at: swagger.updated_at, + }; + } + + public static async includeFileContent( + swagger: SwaggerModel + ): Promise { + const fileContent = (await swagger[ + 'getFileContent' + ]()) as SwaggerFileContent; + + const { file_content_id, ...restSwaggerProps } = swagger.dataValues; + + return this.toResponseWithFileContent(restSwaggerProps, fileContent); + } } export default SwaggerModel; diff --git a/src/server/database/repositories/swaggerRepo.ts b/src/server/database/repositories/swaggerRepo.ts index 5811f17..244e8e7 100644 --- a/src/server/database/repositories/swaggerRepo.ts +++ b/src/server/database/repositories/swaggerRepo.ts @@ -1,14 +1,21 @@ +import { v4 } from 'uuid'; import SwaggerModel, { SwaggerRequestBody, + InternalSwaggerResource, SwaggerResource, } from '../models/swagger'; export class SwaggersRepository { - public static async getAllSwaggers(): Promise { - const swaggers = await SwaggerModel.findAll(); + public static async getAllSwaggers( + includeFileContent = false + ): Promise { + const swaggers = await SwaggerModel.findAll({ + include: includeFileContent ? SwaggerModel.FileContent : undefined, + }); + return swaggers; } - public static async getSwagger(id: string): Promise { + public static async getSwagger(id: string): Promise { const swaggers = await SwaggerModel.findByPk(id); return swaggers; } @@ -16,26 +23,50 @@ export class SwaggersRepository { public static async createSwagger( swagger: SwaggerResource ): Promise { - const createdSwagger = await SwaggerModel.create(swagger); - return createdSwagger; + const { file_content, ...restSwaggerProps } = swagger; + const swaggerFileContent = { + id: v4(), + file_content: file_content, + }; + + await SwaggerModel.FileContent.create(swaggerFileContent); + await SwaggerModel.create({ + ...restSwaggerProps, + file_content_id: swaggerFileContent.id, + } as InternalSwaggerResource); + + return swagger; } public static async updateSwagger( existingResource: SwaggerModel, payload: SwaggerRequestBody - ): Promise { - const updatedSwagger = await existingResource.update({ - ...payload, - updated_at: new Date(), - }); + ): Promise { + if (payload.name) { + await existingResource.update({ + name: payload.name, + updated_at: new Date(), + }); + } - return updatedSwagger; + if (payload.file_content) { + await SwaggerModel.FileContent.update( + { + file_content: payload.file_content, + }, + { where: { id: existingResource.file_content_id } } + ); + } } - public static async deleteSwagger(id: string): Promise { + public static async deleteSwagger(swagger: SwaggerModel): Promise { const deletedSwagger = await SwaggerModel.destroy({ - where: { id }, + where: { id: swagger.id }, }); + SwaggerModel.FileContent.destroy({ + where: { id: swagger.file_content_id }, + }); + return !!deletedSwagger; } } diff --git a/src/server/middlewares/findSwagger.ts b/src/server/middlewares/findSwagger.ts index c3b2554..5d99e12 100644 --- a/src/server/middlewares/findSwagger.ts +++ b/src/server/middlewares/findSwagger.ts @@ -1,12 +1,12 @@ import { Context, Next } from 'koa'; import { StatusCodes } from 'http-status-codes'; -import { SwaggerResource } from '../database/models/swagger'; +import SwaggerModel from '../database/models/swagger'; import { SwaggersRepository } from '../database/repositories/swaggerRepo'; import { ErrorMessages } from '../../helpers/common'; declare module 'koa' { interface ExtendableContext { - swaggerResource?: SwaggerResource; + swaggerResource?: SwaggerModel; } } diff --git a/src/server/middlewares/swaggerDuplicationValidator.ts b/src/server/middlewares/swaggerDuplicationValidator.ts index 0098700..9f4f60a 100644 --- a/src/server/middlewares/swaggerDuplicationValidator.ts +++ b/src/server/middlewares/swaggerDuplicationValidator.ts @@ -1,10 +1,13 @@ import { Context, Next } from 'koa'; import { StatusCodes } from 'http-status-codes'; -import { SwaggerRequestBody } from '../database/models/swagger'; +import { + SwaggerFileContent, + SwaggerRequestBody, +} from '../database/models/swagger'; import { SwaggersRepository } from '../database/repositories/swaggerRepo'; import { ErrorMessages } from '../../helpers/common'; -const extractSwaggerTitle = (swaggerContent: string): string => +const extractSwaggerTitleFromContent = (swaggerContent: string): string => JSON.parse(swaggerContent)?.info?.title; export const swaggerDuplicationValidator = (): (( @@ -13,34 +16,41 @@ export const swaggerDuplicationValidator = (): (( ) => void) => { return async (ctx: Context, next: Next) => { const requestBody = ctx.request.body as SwaggerRequestBody; - const swaggerName = requestBody.name.toLowerCase(); - const swaggerTitle = extractSwaggerTitle(requestBody.data); - let existingSwaggers = await SwaggersRepository.getAllSwaggers(); - if (ctx.request.method === 'PUT') { - existingSwaggers = existingSwaggers.filter( - (swagger) => swagger.id !== ctx.params.swagger_id + if (requestBody.file_content) { + const swaggerName = requestBody.name.toLowerCase(); + const swaggerTitle = extractSwaggerTitleFromContent( + requestBody.file_content ); - } - const existingSwaggerNames = existingSwaggers.map((swagger) => - swagger.name.toLowerCase() - ); - const existingSwaggerTitles = existingSwaggers.map((swagger) => - extractSwaggerTitle(swagger.data) - ); + let existingSwaggers = await SwaggersRepository.getAllSwaggers(true); + if (ctx.request.method === 'PUT') { + existingSwaggers = existingSwaggers.filter( + (swagger) => swagger.id !== ctx.params.swagger_id + ); + } - if (existingSwaggerNames.includes(swaggerName)) { - ctx.throw( - StatusCodes.CONFLICT, - ErrorMessages.SWAGGER_NAME_ALREADY_EXISTS - ); - } - if (existingSwaggerTitles.includes(swaggerTitle)) { - ctx.throw( - StatusCodes.CONFLICT, - ErrorMessages.SWAGGER_TITLE_ALREADY_EXISTS + const existingSwaggerNames = existingSwaggers.map((swagger) => + swagger.name.toLowerCase() ); + const existingSwaggerTitles = existingSwaggers.map((swagger) => { + const swaggerFileContent = swagger['FileContent'] + .dataValues as SwaggerFileContent; + return extractSwaggerTitleFromContent(swaggerFileContent.file_content); + }); + + if (existingSwaggerNames.includes(swaggerName)) { + ctx.throw( + StatusCodes.CONFLICT, + ErrorMessages.SWAGGER_NAME_ALREADY_EXISTS + ); + } + if (existingSwaggerTitles.includes(swaggerTitle)) { + ctx.throw( + StatusCodes.CONFLICT, + ErrorMessages.SWAGGER_TITLE_ALREADY_EXISTS + ); + } } await next(); diff --git a/src/server/middlewares/swaggerParser.ts b/src/server/middlewares/swaggerParser.ts index d4e2491..7d1dd58 100644 --- a/src/server/middlewares/swaggerParser.ts +++ b/src/server/middlewares/swaggerParser.ts @@ -7,27 +7,28 @@ import { ErrorMessages, Headers } from '../../helpers/common'; export const swaggerParser = (): ((ctx: Context, next: Next) => void) => { return async (ctx: Context, next: Next) => { const swaggerFormat = ctx.get(Headers.X_SWAGGER_FORMAT) as SwaggerFormats; - const swaggerData = (ctx.request.body as SwaggerRequestBody).data; - - if (swaggerFormat === SwaggerFormats.Yaml) { - try { - const jsonContent = YamlToJson.load(swaggerData); - (ctx.request.body as SwaggerRequestBody).data = - JSON.stringify(jsonContent); - } catch (err) { - ctx.throw( - StatusCodes.BAD_REQUEST, - `${ErrorMessages.SWAGGER_NOT_PARSABLE}: [${err.message}].` - ); - } - } else if (swaggerFormat === SwaggerFormats.Json) { - try { - JSON.parse(swaggerData); - } catch (err) { - ctx.throw( - StatusCodes.BAD_REQUEST, - `${ErrorMessages.SWAGGER_NOT_PARSABLE}: [${err.message}].` - ); + const swaggerData = (ctx.request.body as SwaggerRequestBody).file_content; + if (swaggerData) { + if (swaggerFormat === SwaggerFormats.Yaml) { + try { + const jsonContent = YamlToJson.load(swaggerData); + (ctx.request.body as SwaggerRequestBody).file_content = + JSON.stringify(jsonContent); + } catch (err) { + ctx.throw( + StatusCodes.BAD_REQUEST, + `${ErrorMessages.SWAGGER_NOT_PARSABLE}: [${err.message}].` + ); + } + } else if (swaggerFormat === SwaggerFormats.Json) { + try { + JSON.parse(swaggerData); + } catch (err) { + ctx.throw( + StatusCodes.BAD_REQUEST, + `${ErrorMessages.SWAGGER_NOT_PARSABLE}: [${err.message}].` + ); + } } } diff --git a/src/server/swaggers/controller.ts b/src/server/swaggers/controller.ts index 12a87b1..7419ddd 100644 --- a/src/server/swaggers/controller.ts +++ b/src/server/swaggers/controller.ts @@ -6,7 +6,20 @@ import { SwaggersRepository } from '../database/repositories/swaggerRepo'; export default class SwaggersController { // Get async getAllSwaggers(ctx: Context): Promise { - ctx.body = await SwaggersRepository.getAllSwaggers(); + const includeFileContent = ctx.request.query['expand'] === 'file_content'; + + const allSwaggers = await SwaggersRepository.getAllSwaggers( + includeFileContent + ); + + ctx.body = allSwaggers.map((swagger) => SwaggerModel.toResponse(swagger)); + ctx.status = StatusCodes.OK; + } + + // Get + async getSwagger(ctx: Context): Promise { + const swagger = ctx.swaggerResource; + ctx.body = await SwaggerModel.includeFileContent(swagger); ctx.status = StatusCodes.OK; } @@ -27,12 +40,9 @@ export default class SwaggersController { const existingResource = ctx.swaggerResource; const requestBody = ctx.request.body as SwaggerRequestBody; - const updatedSwagger = await SwaggersRepository.updateSwagger( - existingResource as SwaggerModel, - requestBody - ); + await SwaggersRepository.updateSwagger(existingResource, requestBody); - ctx.body = updatedSwagger; + ctx.body = await SwaggerModel.includeFileContent(existingResource); ctx.status = StatusCodes.OK; } @@ -40,7 +50,7 @@ export default class SwaggersController { async deleteSwagger(ctx: Context): Promise { const swagger = ctx.swaggerResource; - await SwaggersRepository.deleteSwagger(swagger.id); + await SwaggersRepository.deleteSwagger(swagger); ctx.body = undefined; ctx.status = StatusCodes.NO_CONTENT; diff --git a/src/server/swaggers/router.ts b/src/server/swaggers/router.ts index d1668eb..16e5e97 100644 --- a/src/server/swaggers/router.ts +++ b/src/server/swaggers/router.ts @@ -17,6 +17,9 @@ export default function swaggersRouter(): Middleware { router.get('/', openApiValidator(), (ctx) => swaggersController.getAllSwaggers(ctx) ); + router.get('/:swagger_id', openApiValidator(), findSwagger(), (ctx) => + swaggersController.getSwagger(ctx) + ); // Create router.post( diff --git a/src/swagger.yaml b/src/swagger.yaml index b538cdc..8232ab4 100644 --- a/src/swagger.yaml +++ b/src/swagger.yaml @@ -11,11 +11,18 @@ paths: # Swaggers /v1/swaggers: get: + parameters: + - in: query + name: expand + description: a parameter for including swagger file content to the response swaggers list + schema: + type: string + enum: ["file_content"] tags: - Swaggers summary: Get All Swaggers description: Get all swaggers (APIs) of the API-Catalog - operationId: getSwagger + operationId: getSwaggers responses: 200: description: "OK" @@ -61,6 +68,23 @@ paths: required: true schema: $ref: "#/components/schemas/SwaggerId" + get: + tags: + - Swaggers + summary: Get Swagger + description: Get a swagger in the API-Catalog + operationId: getSwagger + responses: + 200: + description: "OK" + content: + application/json: + schema: + $ref: "#/components/schemas/SwaggerResource" + 400: + $ref: "#/components/responses/BadRequest" + 500: + $ref: "#/components/responses/InternalServerError" put: tags: - Swaggers @@ -208,12 +232,12 @@ components: type: object required: - name - - data + - file_content additionalProperties: false properties: name: $ref: "#/components/schemas/SwaggerName" - data: + file_content: $ref: "#/components/schemas/SwaggerData" SwaggerPutRequest: @@ -222,7 +246,7 @@ components: properties: name: $ref: "#/components/schemas/SwaggerName" - data: + file_content: $ref: "#/components/schemas/SwaggerData" SwaggerResource: @@ -230,7 +254,7 @@ components: required: - id - name - - data + - file_content - created_at - updated_at additionalProperties: false @@ -239,7 +263,7 @@ components: $ref: "#/components/schemas/SwaggerId" name: $ref: "#/components/schemas/SwaggerName" - data: + file_content: $ref: "#/components/schemas/SwaggerData" created_at: $ref: "#/components/schemas/CreatedAt" diff --git a/ui/src/App.js b/ui/src/App.js index bacd1c3..ae25d57 100644 --- a/ui/src/App.js +++ b/ui/src/App.js @@ -1,5 +1,3 @@ - - import { useEffect, useState } from 'react'; import SideBar from './components/SideBar/SideBar'; import SwaggerViewer from './components/SwaggerViewer/SwaggerViewer'; @@ -13,6 +11,7 @@ function App() { const [swaggersMap, setSwaggersMap] = useState({}) const [swaggerTitles, setSwaggerTitles] = useState([]) const [selectedSwaggerTitle, setSelectedSwaggerTitle] = useState('') + const [swaggerContentMap, setSwaggerContentMap] = useState({}) useEffect(() => { async function fetchData() { @@ -24,6 +23,7 @@ function App() { const swaggerTitles = Object.keys(fetchedSwaggers); setSwaggerTitles(swaggerTitles); setSelectedSwaggerTitle(swaggerTitles[0]); + await getSwaggerContentIfNeeded(fetchedSwaggers[swaggerTitles[0]]?.id) setIsLoading(false); } @@ -31,16 +31,29 @@ function App() { fetchData(); }, []) + const getSwaggerContentIfNeeded = async (swaggerId) => { + if (!swaggerContentMap[swaggerId]) { + const swaggerContent = await SwaggersApi.fetchSwaggerContent(swaggerId) + setSwaggerContentMap({ + ...swaggerContentMap, + [swaggerId]: swaggerContent.file_content + }) + } + } + return (
setSelectedSwaggerTitle(swaggerTitle)} + onSwaggerSelected={(swaggerTitle) => { + setSelectedSwaggerTitle(swaggerTitle) + getSwaggerContentIfNeeded(swaggersMap[swaggerTitle]?.id) + }} /> {(isLoading || (!isLoading && swaggerTitles.length === 0)) ? : - + }
) diff --git a/ui/src/apis/SwaggersApi.js b/ui/src/apis/SwaggersApi.js index befd56c..1964a74 100644 --- a/ui/src/apis/SwaggersApi.js +++ b/ui/src/apis/SwaggersApi.js @@ -1,9 +1,12 @@ +const API_ENDPOINT = `@@@PUBLIC_API@@@/v1/swaggers`; + + export default class SwaggersApi { // Get static async fetchSwaggers() { const fetchedSwaggers = []; - (await (await fetch(`@@@PUBLIC_API@@@/v1/swaggers`)).json()) + (await (await fetch(API_ENDPOINT)).json()) .sort((a, b) => a.name < b.name ? -1 : 1) .forEach(swagger => { fetchedSwaggers[swagger.name] = swagger; @@ -11,4 +14,8 @@ export default class SwaggersApi { return fetchedSwaggers } + + static async fetchSwaggerContent(swaggerId) { + return (await (await fetch(`${API_ENDPOINT}/${swaggerId}`)).json()) + } } \ No newline at end of file