From 4680b8d22a4db10735334c9c11d45ac2134fb579 Mon Sep 17 00:00:00 2001 From: Nayan Lakhwani Date: Wed, 28 Feb 2024 05:22:43 -0500 Subject: [PATCH 01/43] fix: handle case where 1st active sync collection is made[] --- src/modules/workspace/repositories/collection.repository.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/workspace/repositories/collection.repository.ts b/src/modules/workspace/repositories/collection.repository.ts index d19a1f68..67a3011f 100644 --- a/src/modules/workspace/repositories/collection.repository.ts +++ b/src/modules/workspace/repositories/collection.repository.ts @@ -262,7 +262,7 @@ export class CollectionRepository { ); const data = await this.db .collection(Collections.COLLECTION) - .findOne({ _id: activeCollection.id, activeSync: true }); + .findOne({ _id: activeCollection?.id, activeSync: true }); return data; } } From b669ba9528fdb8492961c1250135d27a1d700add Mon Sep 17 00:00:00 2001 From: Nayan Lakhwani Date: Thu, 29 Feb 2024 19:29:12 +0530 Subject: [PATCH 02/43] feat: init[] --- src/modules/common/models/openapi20.model.ts | 144 ++++++++++++++++++ src/modules/common/models/openapi303.model.ts | 2 +- src/modules/common/services/parser.service.ts | 26 +++- 3 files changed, 169 insertions(+), 3 deletions(-) create mode 100644 src/modules/common/models/openapi20.model.ts diff --git a/src/modules/common/models/openapi20.model.ts b/src/modules/common/models/openapi20.model.ts new file mode 100644 index 00000000..6b5abfd0 --- /dev/null +++ b/src/modules/common/models/openapi20.model.ts @@ -0,0 +1,144 @@ +export interface OpenAPI20 { + swagger: string; + info: InfoObject; + host?: string; + basePath?: string; + schemes?: string[]; + consumes?: string[]; + produces?: string[]; + paths: PathsObject; + definitions?: DefinitionsObject; + parameters?: ParametersDefinitionsObject; + responses?: ResponsesDefinitionsObject; + securityDefinitions?: SecurityDefinitionsObject; + security?: SecurityRequirementObject[]; + tags?: TagObject[]; + externalDocs?: ExternalDocumentationObject; +} + +interface InfoObject { + title: string; + description?: string; + termsOfService?: string; + contact?: ContactObject; + license?: LicenseObject; + version: string; +} + +interface ContactObject { + name?: string; + url?: string; + email?: string; +} + +interface LicenseObject { + name: string; + url?: string; +} + +interface PathsObject { + [path: string]: PathItemObject; +} + +interface PathItemObject { + $ref?: string; + get?: OperationObject; + put?: OperationObject; + post?: OperationObject; + delete?: OperationObject; + options?: OperationObject; + head?: OperationObject; + patch?: OperationObject; + parameters?: (ParameterObject | ReferenceObject)[]; +} + +interface OperationObject { + tags?: string[]; + summary?: string; + description?: string; + externalDocs?: ExternalDocumentationObject; + operationId?: string; + consumes?: string[]; + produces?: string[]; + parameters?: (ParameterObject | ReferenceObject)[]; + responses: ResponsesObject; + schemes?: string[]; + deprecated?: boolean; + security?: SecurityRequirementObject[]; +} + +interface ParameterObject { + name: string; + in: string; + description?: string; + required?: boolean; + schema?: SchemaObject; +} + +interface ReferenceObject { + $ref: string; +} + +interface ResponsesObject { + [code: string]: ResponseObject | ReferenceObject; +} + +interface ResponseObject { + description: string; + schema?: SchemaObject; +} + +interface SchemaObject { + type: string; + format?: string; + items?: SchemaObject; + properties?: { + [name: string]: SchemaObject; + }; + required?: string[]; + enum?: any[]; +} + +interface DefinitionsObject { + [name: string]: SchemaObject; +} + +interface ParametersDefinitionsObject { + [name: string]: ParameterObject; +} + +interface ResponsesDefinitionsObject { + [name: string]: ResponseObject; +} + +interface SecurityDefinitionsObject { + [name: string]: SecuritySchemeObject; +} + +interface SecuritySchemeObject { + type: string; + description?: string; + name: string; + in: string; + flow: string; + authorizationUrl: string; + tokenUrl: string; + scopes: { + [scope: string]: string; + }; +} + +interface SecurityRequirementObject { + [name: string]: string[]; +} + +interface TagObject { + name: string; + description?: string; + externalDocs?: ExternalDocumentationObject; +} + +interface ExternalDocumentationObject { + description?: string; + url: string; +} diff --git a/src/modules/common/models/openapi303.model.ts b/src/modules/common/models/openapi303.model.ts index fc5d3da1..fafe69eb 100644 --- a/src/modules/common/models/openapi303.model.ts +++ b/src/modules/common/models/openapi303.model.ts @@ -111,7 +111,7 @@ interface ExternalDocumentationObject { export interface ParameterObject { name: string; - in: "query" | "header" | "path" | "cookie"; + in: "query" | "header" | "path" | "cookie" | "body"; description?: string; required?: boolean; deprecated?: boolean; diff --git a/src/modules/common/services/parser.service.ts b/src/modules/common/services/parser.service.ts index 47f9a4ca..e52ddad9 100644 --- a/src/modules/common/services/parser.service.ts +++ b/src/modules/common/services/parser.service.ts @@ -51,7 +51,7 @@ export class ParserService { requestObj.request.method = innerKey.toUpperCase() as HTTPMethods; requestObj.request.operationId = innerValue.operationId; requestObj.request.url = baseUrl + key; - + let newBody = []; if (innerValue.parameters?.length) { requestObj.request.queryParams = innerValue.parameters.filter( (param: ParameterObject) => param.in === "query", @@ -62,8 +62,30 @@ export class ParserService { requestObj.request.headers = innerValue.parameters.filter( (param: ParameterObject) => param.in === "header", ); + newBody = innerValue.parameters.filter( + (param: ParameterObject) => param.in === "body", + ); } - if (innerValue.requestBody) { + + if (newBody.length) { + for (const body of newBody) { + const bodyToPush: RequestBody = {} as RequestBody; + const schema = body.schema; + const ref = schema["$ref"]; + if (ref) { + const schemaName = ref.slice( + ref.lastIndexOf("/") + 1, + ref.length, + ); + bodyToPush.schema = (openApiDocument as any).definitions[ + schemaName + ]; + } else { + bodyToPush.schema = (schema as any).schema; + } + requestObj.request.body.push(bodyToPush); + } + } else if (innerValue.requestBody) { requestObj.request.body = []; const bodyTypes = innerValue.requestBody.content; for (const [type, schema] of Object.entries(bodyTypes)) { From 22cb7a6b66716cacd097ff8e234d8e425556af02 Mon Sep 17 00:00:00 2001 From: Nayan Lakhwani Date: Sun, 3 Mar 2024 07:56:02 -0500 Subject: [PATCH 03/43] feat: add auth info while saving api request[] --- src/modules/common/models/collection.model.ts | 15 +++++++ .../payloads/collectionRequest.payload.ts | 39 +++++++++++++++++++ 2 files changed, 54 insertions(+) diff --git a/src/modules/common/models/collection.model.ts b/src/modules/common/models/collection.model.ts index ba8bf084..bd5c1f85 100644 --- a/src/modules/common/models/collection.model.ts +++ b/src/modules/common/models/collection.model.ts @@ -30,6 +30,13 @@ export enum BodyModeEnum { "text/html" = "text/html", } +export enum AuthModeEnum { + "No Auth" = "No Auth", + "API Key" = "API Key", + "Bearer Token" = "Bearer Token", + "Basic Auth" = "Basic Auth", +} + export enum SourceTypeEnum { SPEC = "SPEC", USER = "USER", @@ -119,6 +126,14 @@ export class RequestMetaData { @IsNotEmpty() selectedRequestBodyType?: BodyModeEnum; + @ApiProperty({ + enum: AuthModeEnum, + }) + @IsEnum({ AuthModeEnum }) + @IsString() + @IsNotEmpty() + selectedRequestAuthType?: AuthModeEnum; + @ApiProperty({ example: { name: "search", diff --git a/src/modules/workspace/payloads/collectionRequest.payload.ts b/src/modules/workspace/payloads/collectionRequest.payload.ts index ea07fb54..62682281 100644 --- a/src/modules/workspace/payloads/collectionRequest.payload.ts +++ b/src/modules/workspace/payloads/collectionRequest.payload.ts @@ -20,6 +20,26 @@ import { // eslint-disable-next-line @typescript-eslint/no-unused-vars +enum ApiKeyParamTypeEnum { + HEADER = "Header", + Query = "Query Parameter", +} +class Auth { + apiKey?: ApiKey; + bearerToken?: string; + basicAuth?: BasicAuth; +} + +class ApiKey { + authKey?: string; + authValue?: string; + addTo: ApiKeyParamTypeEnum; +} +class BasicAuth { + username?: string; + password?: string; +} + export class CollectionRequestBody { @ApiProperty({ example: "application/json" }) @IsEnum(BodyModeEnum) @@ -118,6 +138,25 @@ export class CollectionRequestMetaData { @ValidateNested({ each: true }) @IsOptional() headers?: Params[]; + + @ApiProperty({ + type: [Auth], + example: { + apiKey: { + authKey: "", + authValue: "", + paramType: "header", + }, + bearerToken: "", + basicToken: { + username: "", + password: "", + }, + }, + }) + @Type(() => Auth) + @IsOptional() + auth?: Auth; } export class CollectionRequestItem { From 2aad17f64e3ef631fd8f5b8e869625b4933eac5b Mon Sep 17 00:00:00 2001 From: Nayan Lakhwani Date: Sun, 3 Mar 2024 15:28:54 -0500 Subject: [PATCH 04/43] feat: open api schema parsing[] --- .../common/services/helper/parser.helper.ts | 83 ++++++++ src/modules/common/services/parser.service.ts | 177 +++++++++--------- 2 files changed, 172 insertions(+), 88 deletions(-) create mode 100644 src/modules/common/services/helper/parser.helper.ts diff --git a/src/modules/common/services/helper/parser.helper.ts b/src/modules/common/services/helper/parser.helper.ts new file mode 100644 index 00000000..7039d130 --- /dev/null +++ b/src/modules/common/services/helper/parser.helper.ts @@ -0,0 +1,83 @@ +export function resolveAllComponentRefs(spec: any) { + if (!spec) return spec; + + if (typeof spec === "object") { + const componentToResolve = spec.components ?? spec.definitions; + const resolvedSpec: { [key: string]: any } = {}; + for (const key in spec) { + if (componentToResolve.schemas) { + resolvedSpec[key] = resolveComponentRef(spec[key], componentToResolve); + } else { + resolvedSpec[key] = resolveDefinitionRef(spec[key], componentToResolve); + } + } + return resolvedSpec; + } + + return spec; +} + +function resolveComponentRef(data: any, components: any): any { + if (!data) return data; + + if (typeof data === "object") { + if (data.hasOwnProperty("$ref") && typeof data["$ref"] === "string") { + const refPath = data["$ref"]; + if (refPath.startsWith("#/components/schemas/")) { + const schemaName = refPath.split("/").pop(); + if ( + components && + components.schemas && + components.schemas[schemaName] + ) { + return resolveComponentRef( + components.schemas[schemaName], + components, + ); + } else { + console.warn(`Reference "${refPath}" not found in components`); + return data; + } + } else { + return data; + } + } else { + const newData: { [key: string]: any } = {}; + for (const key in data) { + newData[key] = resolveComponentRef(data[key], components); + } + return newData; + } + } + + return data; +} + +function resolveDefinitionRef(data: any, definitions: any): any { + if (!data) return data; + + if (typeof data === "object") { + if (data.hasOwnProperty("$ref") && typeof data["$ref"] === "string") { + const refPath = data["$ref"]; + if (refPath.startsWith("#/definitions/")) { + const schemaName = refPath.split("/").pop(); + if (definitions && definitions[schemaName]) { + return resolveDefinitionRef(definitions[schemaName], definitions); + } else { + console.warn(`Reference "${refPath}" not found in definitions`); + return data; + } + } else { + return data; + } + } else { + const newData: { [key: string]: any } = {}; + for (const key in data) { + newData[key] = resolveDefinitionRef(data[key], definitions); + } + return newData; + } + } + + return data; +} diff --git a/src/modules/common/services/parser.service.ts b/src/modules/common/services/parser.service.ts index e52ddad9..433f664d 100644 --- a/src/modules/common/services/parser.service.ts +++ b/src/modules/common/services/parser.service.ts @@ -1,5 +1,4 @@ import SwaggerParser from "@apidevtools/swagger-parser"; -// import * as util from "util"; import { BodyModeEnum, Collection, @@ -16,6 +15,8 @@ import { ContextService } from "./context.service"; import { v4 as uuidv4 } from "uuid"; import { CollectionService } from "@src/modules/workspace/services/collection.service"; import { WithId } from "mongodb"; +import { resolveAllComponentRefs } from "./helper/parser.helper"; +import { OpenAPI20 } from "../models/openapi20.model"; @Injectable() export class ParserService { @@ -23,6 +24,7 @@ export class ParserService { private readonly contextService: ContextService, private readonly collectionService: CollectionService, ) {} + async parse( file: string, activeSync?: boolean, @@ -32,102 +34,101 @@ export class ParserService { collection: WithId; existingCollection: boolean; }> { - const openApiDocument = (await SwaggerParser.parse(file)) as OpenAPI303; + let openApiDocument = (await SwaggerParser.parse(file)) as + | OpenAPI303 + | OpenAPI20; const baseUrl = this.getBaseUrl(openApiDocument); let existingCollection: WithId | null = null; const folderObjMap = new Map(); - for (const [key, value] of Object.entries(openApiDocument.paths)) { - //key will be endpoints /put and values will its apis post ,put etc - for (const [innerKey, innerValue] of Object.entries(value)) { - //key will be api methods and values will it's desc - const requestObj: CollectionItem = {} as CollectionItem; - requestObj.name = key; - requestObj.description = innerValue.description; - requestObj.type = ItemTypeEnum.REQUEST; - requestObj.source = SourceTypeEnum.SPEC; - requestObj.id = uuidv4(); - requestObj.isDeleted = false; - requestObj.request = {} as RequestMetaData; - requestObj.request.method = innerKey.toUpperCase() as HTTPMethods; - requestObj.request.operationId = innerValue.operationId; - requestObj.request.url = baseUrl + key; - let newBody = []; - if (innerValue.parameters?.length) { - requestObj.request.queryParams = innerValue.parameters.filter( - (param: ParameterObject) => param.in === "query", - ); - requestObj.request.pathParams = innerValue.parameters.filter( - (param: ParameterObject) => param.in === "path", - ); - requestObj.request.headers = innerValue.parameters.filter( - (param: ParameterObject) => param.in === "header", - ); - newBody = innerValue.parameters.filter( - (param: ParameterObject) => param.in === "body", - ); - } - - if (newBody.length) { - for (const body of newBody) { - const bodyToPush: RequestBody = {} as RequestBody; - const schema = body.schema; - const ref = schema["$ref"]; - if (ref) { - const schemaName = ref.slice( - ref.lastIndexOf("/") + 1, - ref.length, - ); - bodyToPush.schema = (openApiDocument as any).definitions[ - schemaName - ]; - } else { - bodyToPush.schema = (schema as any).schema; - } - requestObj.request.body.push(bodyToPush); + if (openApiDocument.hasOwnProperty("components")) { + openApiDocument = resolveAllComponentRefs(openApiDocument) as OpenAPI303; + for (const [key, value] of Object.entries(openApiDocument.paths)) { + //key will be endpoints /put and values will its apis post ,put etc + for (const [innerKey, innerValue] of Object.entries(value)) { + //key will be api methods and values will it's desc + const requestObj: CollectionItem = {} as CollectionItem; + requestObj.name = key; + requestObj.description = innerValue.description; + requestObj.type = ItemTypeEnum.REQUEST; + requestObj.source = SourceTypeEnum.SPEC; + requestObj.id = uuidv4(); + requestObj.isDeleted = false; + requestObj.request = {} as RequestMetaData; + requestObj.request.method = innerKey.toUpperCase() as HTTPMethods; + requestObj.request.operationId = innerValue.operationId; + requestObj.request.url = baseUrl + key; + if (innerValue.parameters?.length) { + requestObj.request.queryParams = innerValue.parameters.filter( + (param: ParameterObject) => param.in === "query", + ); + requestObj.request.pathParams = innerValue.parameters.filter( + (param: ParameterObject) => param.in === "path", + ); + requestObj.request.headers = innerValue.parameters.filter( + (param: ParameterObject) => param.in === "header", + ); } - } else if (innerValue.requestBody) { - requestObj.request.body = []; - const bodyTypes = innerValue.requestBody.content; - for (const [type, schema] of Object.entries(bodyTypes)) { - const body: RequestBody = {} as RequestBody; - body.type = Object.values(BodyModeEnum).find( - (enumMember) => enumMember === type, - ) as BodyModeEnum; - const ref = (schema as any).schema?.$ref; - if (ref) { - const schemaName = ref.slice( - ref.lastIndexOf("/") + 1, - ref.length, - ); - body.schema = openApiDocument.components.schemas[schemaName]; - } else { + if (innerValue.requestBody) { + requestObj.request.body = []; + const bodyTypes = innerValue.requestBody.content; + for (const [type, schema] of Object.entries(bodyTypes)) { + const body: RequestBody = {} as RequestBody; + body.type = Object.values(BodyModeEnum).find( + (enumMember) => enumMember === type, + ) as BodyModeEnum; body.schema = (schema as any).schema; + requestObj.request.body.push(body); } - requestObj.request.body.push(body); } + //Add to a folder + const tag = innerValue.tags ? innerValue.tags[0] : "default"; + const tagArr = + openApiDocument?.tags?.length > 0 && + openApiDocument.tags.filter((tagObj) => { + return tagObj.name === tag; + }); + let folderObj: CollectionItem = folderObjMap.get(tag); + if (!folderObj) { + folderObj = {} as CollectionItem; + folderObj.name = tag; + folderObj.description = tagArr ? tagArr[0].description : ""; + folderObj.isDeleted = false; + folderObj.type = ItemTypeEnum.FOLDER; + folderObj.id = uuidv4(); + folderObj.items = []; + } + folderObj.items.push(requestObj); + folderObjMap.set(folderObj.name, folderObj); } - //Add to a folder - const tag = innerValue.tags ? innerValue.tags[0] : "default"; - const tagArr = - openApiDocument?.tags?.length > 0 && - openApiDocument.tags.filter((tagObj) => { - return tagObj.name === tag; - }); - let folderObj: CollectionItem = folderObjMap.get(tag); - if (!folderObj) { - folderObj = {} as CollectionItem; - folderObj.name = tag; - folderObj.description = tagArr ? tagArr[0].description : ""; - folderObj.isDeleted = false; - folderObj.type = ItemTypeEnum.FOLDER; - folderObj.id = uuidv4(); - folderObj.items = []; - } - folderObj.items.push(requestObj); - folderObjMap.set(folderObj.name, folderObj); } - } + } else if (openApiDocument.hasOwnProperty("definitions")) { + openApiDocument = resolveAllComponentRefs(openApiDocument) as OpenAPI20; + console.log("Yet to implement"); + // newBody = innerValue.parameters.filter( + // (param: ParameterObject) => param.in === "body", + // ); + // if (newBody.length) { + // for (const body of newBody) { + // const bodyToPush: RequestBody = {} as RequestBody; + // const schema = body.schema; + // const ref = schema["$ref"]; + // if (ref) { + // const schemaName = ref.slice( + // ref.lastIndexOf("/") + 1, + // ref.length, + // ); + // bodyToPush.schema = (openApiDocument as any).definitions[ + // schemaName + // ]; + // } else { + // bodyToPush.schema = (schema as any).schema; + // } + // requestObj.request.body.push(bodyToPush); + // } + // } + return; + } const itemObject = Object.fromEntries(folderObjMap); let items: CollectionItem[] = []; let totalRequests = 0; @@ -307,7 +308,7 @@ export class ParserService { return mergedArray; } - getBaseUrl(openApiDocument: OpenAPI303): string { + getBaseUrl(openApiDocument: any): string { const basePath = openApiDocument.basePath ? openApiDocument.basePath : ""; if (openApiDocument.host) { return "https://" + openApiDocument.host + basePath; From 42ca2f99453a250e254e9586f59bed55ee2510ed Mon Sep 17 00:00:00 2001 From: Nayan Lakhwani Date: Mon, 4 Mar 2024 20:04:43 +0530 Subject: [PATCH 05/43] feat: oapi2 transform[] --- a.json | 1716 +++++++++++++++++ a2.json | 83 + b.json | 83 + .../services/helper/oapi2.transformer.ts | 207 ++ src/modules/common/services/parser.service.ts | 52 +- test.js | 2 + 6 files changed, 2119 insertions(+), 24 deletions(-) create mode 100644 a.json create mode 100644 a2.json create mode 100644 b.json create mode 100644 src/modules/common/services/helper/oapi2.transformer.ts create mode 100644 test.js diff --git a/a.json b/a.json new file mode 100644 index 00000000..8e5c5f8a --- /dev/null +++ b/a.json @@ -0,0 +1,1716 @@ +{ + "swagger": "2.0", + "info": { + "description": "This is a sample server Petstore server. You can find out more about Swagger at [http://swagger.io](http://swagger.io) or on [irc.freenode.net, #swagger](http://swagger.io/irc/). For this sample, you can use the api key `special-key` to test the authorization filters.", + "version": "1.0.6", + "title": "Swagger Petstore", + "termsOfService": "http://swagger.io/terms/", + "contact": { + "email": "apiteam@swagger.io" + }, + "license": { + "name": "Apache 2.0", + "url": "http://www.apache.org/licenses/LICENSE-2.0.html" + } + }, + "host": "petstore.swagger.io", + "basePath": "/v2", + "tags": { + "0": { + "name": "pet", + "description": "Everything about your Pets", + "externalDocs": { + "description": "Find out more", + "url": "http://swagger.io" + } + }, + "1": { + "name": "store", + "description": "Access to Petstore orders" + }, + "2": { + "name": "user", + "description": "Operations about user", + "externalDocs": { + "description": "Find out more about our store", + "url": "http://swagger.io" + } + } + }, + "schemes": { + "0": "https", + "1": "http" + }, + "paths": { + "/pet/{petId}/uploadImage": { + "post": { + "tags": { + "0": "pet" + }, + "summary": "uploads an image", + "description": "", + "operationId": "uploadFile", + "consumes": { + "0": "multipart/form-data" + }, + "produces": { + "0": "application/json" + }, + "parameters": { + "0": { + "name": "petId", + "in": "path", + "description": "ID of pet to update", + "required": true, + "type": "integer", + "format": "int64" + }, + "1": { + "name": "additionalMetadata", + "in": "formData", + "description": "Additional data to pass to server", + "required": false, + "type": "string" + }, + "2": { + "name": "file", + "in": "formData", + "description": "file to upload", + "required": false, + "type": "file" + } + }, + "responses": { + "200": { + "description": "successful operation", + "schema": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "format": "int32" + }, + "type": { + "type": "string" + }, + "message": { + "type": "string" + } + } + } + } + }, + "security": { + "0": { + "petstore_auth": { + "0": "write:pets", + "1": "read:pets" + } + } + } + } + }, + "/pet": { + "post": { + "tags": { + "0": "pet" + }, + "summary": "Add a new pet to the store", + "description": "", + "operationId": "addPet", + "consumes": { + "0": "application/json", + "1": "application/xml" + }, + "produces": { + "0": "application/json", + "1": "application/xml" + }, + "parameters": { + "0": { + "in": "body", + "name": "body", + "description": "Pet object that needs to be added to the store", + "required": true, + "schema": { + "type": "object", + "required": { + "0": "name", + "1": "photoUrls" + }, + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "category": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string" + } + }, + "xml": { + "name": "Category" + } + }, + "name": { + "type": "string", + "example": "doggie" + }, + "photoUrls": { + "type": "array", + "xml": { + "wrapped": true + }, + "items": { + "type": "string", + "xml": { + "name": "photoUrl" + } + } + }, + "tags": { + "type": "array", + "xml": { + "wrapped": true + }, + "items": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string" + } + }, + "xml": { + "name": "Tag" + } + } + }, + "status": { + "type": "string", + "description": "pet status in the store", + "enum": { + "0": "available", + "1": "pending", + "2": "sold" + } + } + }, + "xml": { + "name": "Pet" + } + } + } + }, + "responses": { + "405": { + "description": "Invalid input" + } + }, + "security": { + "0": { + "petstore_auth": { + "0": "write:pets", + "1": "read:pets" + } + } + } + }, + "put": { + "tags": { + "0": "pet" + }, + "summary": "Update an existing pet", + "description": "", + "operationId": "updatePet", + "consumes": { + "0": "application/json", + "1": "application/xml" + }, + "produces": { + "0": "application/json", + "1": "application/xml" + }, + "parameters": { + "0": { + "in": "body", + "name": "body", + "description": "Pet object that needs to be added to the store", + "required": true, + "schema": { + "type": "object", + "required": { + "0": "name", + "1": "photoUrls" + }, + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "category": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string" + } + }, + "xml": { + "name": "Category" + } + }, + "name": { + "type": "string", + "example": "doggie" + }, + "photoUrls": { + "type": "array", + "xml": { + "wrapped": true + }, + "items": { + "type": "string", + "xml": { + "name": "photoUrl" + } + } + }, + "tags": { + "type": "array", + "xml": { + "wrapped": true + }, + "items": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string" + } + }, + "xml": { + "name": "Tag" + } + } + }, + "status": { + "type": "string", + "description": "pet status in the store", + "enum": { + "0": "available", + "1": "pending", + "2": "sold" + } + } + }, + "xml": { + "name": "Pet" + } + } + } + }, + "responses": { + "400": { + "description": "Invalid ID supplied" + }, + "404": { + "description": "Pet not found" + }, + "405": { + "description": "Validation exception" + } + }, + "security": { + "0": { + "petstore_auth": { + "0": "write:pets", + "1": "read:pets" + } + } + } + } + }, + "/pet/findByStatus": { + "get": { + "tags": { + "0": "pet" + }, + "summary": "Finds Pets by status", + "description": "Multiple status values can be provided with comma separated strings", + "operationId": "findPetsByStatus", + "produces": { + "0": "application/json", + "1": "application/xml" + }, + "parameters": { + "0": { + "name": "status", + "in": "query", + "description": "Status values that need to be considered for filter", + "required": true, + "type": "array", + "items": { + "type": "string", + "enum": { + "0": "available", + "1": "pending", + "2": "sold" + }, + "default": "available" + }, + "collectionFormat": "multi" + } + }, + "responses": { + "200": { + "description": "successful operation", + "schema": { + "type": "array", + "items": { + "type": "object", + "required": { + "0": "name", + "1": "photoUrls" + }, + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "category": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string" + } + }, + "xml": { + "name": "Category" + } + }, + "name": { + "type": "string", + "example": "doggie" + }, + "photoUrls": { + "type": "array", + "xml": { + "wrapped": true + }, + "items": { + "type": "string", + "xml": { + "name": "photoUrl" + } + } + }, + "tags": { + "type": "array", + "xml": { + "wrapped": true + }, + "items": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string" + } + }, + "xml": { + "name": "Tag" + } + } + }, + "status": { + "type": "string", + "description": "pet status in the store", + "enum": { + "0": "available", + "1": "pending", + "2": "sold" + } + } + }, + "xml": { + "name": "Pet" + } + } + } + }, + "400": { + "description": "Invalid status value" + } + }, + "security": { + "0": { + "petstore_auth": { + "0": "write:pets", + "1": "read:pets" + } + } + } + } + }, + "/pet/findByTags": { + "get": { + "tags": { + "0": "pet" + }, + "summary": "Finds Pets by tags", + "description": "Multiple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing.", + "operationId": "findPetsByTags", + "produces": { + "0": "application/json", + "1": "application/xml" + }, + "parameters": { + "0": { + "name": "tags", + "in": "query", + "description": "Tags to filter by", + "required": true, + "type": "array", + "items": { + "type": "string" + }, + "collectionFormat": "multi" + } + }, + "responses": { + "200": { + "description": "successful operation", + "schema": { + "type": "array", + "items": { + "type": "object", + "required": { + "0": "name", + "1": "photoUrls" + }, + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "category": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string" + } + }, + "xml": { + "name": "Category" + } + }, + "name": { + "type": "string", + "example": "doggie" + }, + "photoUrls": { + "type": "array", + "xml": { + "wrapped": true + }, + "items": { + "type": "string", + "xml": { + "name": "photoUrl" + } + } + }, + "tags": { + "type": "array", + "xml": { + "wrapped": true + }, + "items": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string" + } + }, + "xml": { + "name": "Tag" + } + } + }, + "status": { + "type": "string", + "description": "pet status in the store", + "enum": { + "0": "available", + "1": "pending", + "2": "sold" + } + } + }, + "xml": { + "name": "Pet" + } + } + } + }, + "400": { + "description": "Invalid tag value" + } + }, + "security": { + "0": { + "petstore_auth": { + "0": "write:pets", + "1": "read:pets" + } + } + }, + "deprecated": true + } + }, + "/pet/{petId}": { + "get": { + "tags": { + "0": "pet" + }, + "summary": "Find pet by ID", + "description": "Returns a single pet", + "operationId": "getPetById", + "produces": { + "0": "application/json", + "1": "application/xml" + }, + "parameters": { + "0": { + "name": "petId", + "in": "path", + "description": "ID of pet to return", + "required": true, + "type": "integer", + "format": "int64" + } + }, + "responses": { + "200": { + "description": "successful operation", + "schema": { + "type": "object", + "required": { + "0": "name", + "1": "photoUrls" + }, + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "category": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string" + } + }, + "xml": { + "name": "Category" + } + }, + "name": { + "type": "string", + "example": "doggie" + }, + "photoUrls": { + "type": "array", + "xml": { + "wrapped": true + }, + "items": { + "type": "string", + "xml": { + "name": "photoUrl" + } + } + }, + "tags": { + "type": "array", + "xml": { + "wrapped": true + }, + "items": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string" + } + }, + "xml": { + "name": "Tag" + } + } + }, + "status": { + "type": "string", + "description": "pet status in the store", + "enum": { + "0": "available", + "1": "pending", + "2": "sold" + } + } + }, + "xml": { + "name": "Pet" + } + } + }, + "400": { + "description": "Invalid ID supplied" + }, + "404": { + "description": "Pet not found" + } + }, + "security": { + "0": { + "api_key": {} + } + } + }, + "post": { + "tags": { + "0": "pet" + }, + "summary": "Updates a pet in the store with form data", + "description": "", + "operationId": "updatePetWithForm", + "consumes": { + "0": "application/x-www-form-urlencoded" + }, + "produces": { + "0": "application/json", + "1": "application/xml" + }, + "parameters": { + "0": { + "name": "petId", + "in": "path", + "description": "ID of pet that needs to be updated", + "required": true, + "type": "integer", + "format": "int64" + }, + "1": { + "name": "name", + "in": "formData", + "description": "Updated name of the pet", + "required": false, + "type": "string" + }, + "2": { + "name": "status", + "in": "formData", + "description": "Updated status of the pet", + "required": false, + "type": "string" + } + }, + "responses": { + "405": { + "description": "Invalid input" + } + }, + "security": { + "0": { + "petstore_auth": { + "0": "write:pets", + "1": "read:pets" + } + } + } + }, + "delete": { + "tags": { + "0": "pet" + }, + "summary": "Deletes a pet", + "description": "", + "operationId": "deletePet", + "produces": { + "0": "application/json", + "1": "application/xml" + }, + "parameters": { + "0": { + "name": "api_key", + "in": "header", + "required": false, + "type": "string" + }, + "1": { + "name": "petId", + "in": "path", + "description": "Pet id to delete", + "required": true, + "type": "integer", + "format": "int64" + } + }, + "responses": { + "400": { + "description": "Invalid ID supplied" + }, + "404": { + "description": "Pet not found" + } + }, + "security": { + "0": { + "petstore_auth": { + "0": "write:pets", + "1": "read:pets" + } + } + } + } + }, + "/store/order": { + "post": { + "tags": { + "0": "store" + }, + "summary": "Place an order for a pet", + "description": "", + "operationId": "placeOrder", + "consumes": { + "0": "application/json" + }, + "produces": { + "0": "application/json", + "1": "application/xml" + }, + "parameters": { + "0": { + "in": "body", + "name": "body", + "description": "order placed for purchasing the pet", + "required": true, + "schema": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "petId": { + "type": "integer", + "format": "int64" + }, + "quantity": { + "type": "integer", + "format": "int32" + }, + "shipDate": { + "type": "string", + "format": "date-time" + }, + "status": { + "type": "string", + "description": "Order Status", + "enum": { + "0": "placed", + "1": "approved", + "2": "delivered" + } + }, + "complete": { + "type": "boolean" + } + }, + "xml": { + "name": "Order" + } + } + } + }, + "responses": { + "200": { + "description": "successful operation", + "schema": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "petId": { + "type": "integer", + "format": "int64" + }, + "quantity": { + "type": "integer", + "format": "int32" + }, + "shipDate": { + "type": "string", + "format": "date-time" + }, + "status": { + "type": "string", + "description": "Order Status", + "enum": { + "0": "placed", + "1": "approved", + "2": "delivered" + } + }, + "complete": { + "type": "boolean" + } + }, + "xml": { + "name": "Order" + } + } + }, + "400": { + "description": "Invalid Order" + } + } + } + }, + "/store/order/{orderId}": { + "get": { + "tags": { + "0": "store" + }, + "summary": "Find purchase order by ID", + "description": "For valid response try integer IDs with value >= 1 and <= 10. Other values will generated exceptions", + "operationId": "getOrderById", + "produces": { + "0": "application/json", + "1": "application/xml" + }, + "parameters": { + "0": { + "name": "orderId", + "in": "path", + "description": "ID of pet that needs to be fetched", + "required": true, + "type": "integer", + "maximum": 10, + "minimum": 1, + "format": "int64" + } + }, + "responses": { + "200": { + "description": "successful operation", + "schema": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "petId": { + "type": "integer", + "format": "int64" + }, + "quantity": { + "type": "integer", + "format": "int32" + }, + "shipDate": { + "type": "string", + "format": "date-time" + }, + "status": { + "type": "string", + "description": "Order Status", + "enum": { + "0": "placed", + "1": "approved", + "2": "delivered" + } + }, + "complete": { + "type": "boolean" + } + }, + "xml": { + "name": "Order" + } + } + }, + "400": { + "description": "Invalid ID supplied" + }, + "404": { + "description": "Order not found" + } + } + }, + "delete": { + "tags": { + "0": "store" + }, + "summary": "Delete purchase order by ID", + "description": "For valid response try integer IDs with positive integer value. Negative or non-integer values will generate API errors", + "operationId": "deleteOrder", + "produces": { + "0": "application/json", + "1": "application/xml" + }, + "parameters": { + "0": { + "name": "orderId", + "in": "path", + "description": "ID of the order that needs to be deleted", + "required": true, + "type": "integer", + "minimum": 1, + "format": "int64" + } + }, + "responses": { + "400": { + "description": "Invalid ID supplied" + }, + "404": { + "description": "Order not found" + } + } + } + }, + "/store/inventory": { + "get": { + "tags": { + "0": "store" + }, + "summary": "Returns pet inventories by status", + "description": "Returns a map of status codes to quantities", + "operationId": "getInventory", + "produces": { + "0": "application/json" + }, + "parameters": {}, + "responses": { + "200": { + "description": "successful operation", + "schema": { + "type": "object", + "additionalProperties": { + "type": "integer", + "format": "int32" + } + } + } + }, + "security": { + "0": { + "api_key": {} + } + } + } + }, + "/user/createWithArray": { + "post": { + "tags": { + "0": "user" + }, + "summary": "Creates list of users with given input array", + "description": "", + "operationId": "createUsersWithArrayInput", + "consumes": { + "0": "application/json" + }, + "produces": { + "0": "application/json", + "1": "application/xml" + }, + "parameters": { + "0": { + "in": "body", + "name": "body", + "description": "List of user object", + "required": true, + "schema": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "username": { + "type": "string" + }, + "firstName": { + "type": "string" + }, + "lastName": { + "type": "string" + }, + "email": { + "type": "string" + }, + "password": { + "type": "string" + }, + "phone": { + "type": "string" + }, + "userStatus": { + "type": "integer", + "format": "int32", + "description": "User Status" + } + }, + "xml": { + "name": "User" + } + } + } + } + }, + "responses": { + "default": { + "description": "successful operation" + } + } + } + }, + "/user/createWithList": { + "post": { + "tags": { + "0": "user" + }, + "summary": "Creates list of users with given input array", + "description": "", + "operationId": "createUsersWithListInput", + "consumes": { + "0": "application/json" + }, + "produces": { + "0": "application/json", + "1": "application/xml" + }, + "parameters": { + "0": { + "in": "body", + "name": "body", + "description": "List of user object", + "required": true, + "schema": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "username": { + "type": "string" + }, + "firstName": { + "type": "string" + }, + "lastName": { + "type": "string" + }, + "email": { + "type": "string" + }, + "password": { + "type": "string" + }, + "phone": { + "type": "string" + }, + "userStatus": { + "type": "integer", + "format": "int32", + "description": "User Status" + } + }, + "xml": { + "name": "User" + } + } + } + } + }, + "responses": { + "default": { + "description": "successful operation" + } + } + } + }, + "/user/{username}": { + "get": { + "tags": { + "0": "user" + }, + "summary": "Get user by user name", + "description": "", + "operationId": "getUserByName", + "produces": { + "0": "application/json", + "1": "application/xml" + }, + "parameters": { + "0": { + "name": "username", + "in": "path", + "description": "The name that needs to be fetched. Use user1 for testing. ", + "required": true, + "type": "string" + } + }, + "responses": { + "200": { + "description": "successful operation", + "schema": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "username": { + "type": "string" + }, + "firstName": { + "type": "string" + }, + "lastName": { + "type": "string" + }, + "email": { + "type": "string" + }, + "password": { + "type": "string" + }, + "phone": { + "type": "string" + }, + "userStatus": { + "type": "integer", + "format": "int32", + "description": "User Status" + } + }, + "xml": { + "name": "User" + } + } + }, + "400": { + "description": "Invalid username supplied" + }, + "404": { + "description": "User not found" + } + } + }, + "put": { + "tags": { + "0": "user" + }, + "summary": "Updated user", + "description": "This can only be done by the logged in user.", + "operationId": "updateUser", + "consumes": { + "0": "application/json" + }, + "produces": { + "0": "application/json", + "1": "application/xml" + }, + "parameters": { + "0": { + "name": "username", + "in": "path", + "description": "name that need to be updated", + "required": true, + "type": "string" + }, + "1": { + "in": "body", + "name": "body", + "description": "Updated user object", + "required": true, + "schema": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "username": { + "type": "string" + }, + "firstName": { + "type": "string" + }, + "lastName": { + "type": "string" + }, + "email": { + "type": "string" + }, + "password": { + "type": "string" + }, + "phone": { + "type": "string" + }, + "userStatus": { + "type": "integer", + "format": "int32", + "description": "User Status" + } + }, + "xml": { + "name": "User" + } + } + } + }, + "responses": { + "400": { + "description": "Invalid user supplied" + }, + "404": { + "description": "User not found" + } + } + }, + "delete": { + "tags": { + "0": "user" + }, + "summary": "Delete user", + "description": "This can only be done by the logged in user.", + "operationId": "deleteUser", + "produces": { + "0": "application/json", + "1": "application/xml" + }, + "parameters": { + "0": { + "name": "username", + "in": "path", + "description": "The name that needs to be deleted", + "required": true, + "type": "string" + } + }, + "responses": { + "400": { + "description": "Invalid username supplied" + }, + "404": { + "description": "User not found" + } + } + } + }, + "/user/login": { + "get": { + "tags": { + "0": "user" + }, + "summary": "Logs user into the system", + "description": "", + "operationId": "loginUser", + "produces": { + "0": "application/json", + "1": "application/xml" + }, + "parameters": { + "0": { + "name": "username", + "in": "query", + "description": "The user name for login", + "required": true, + "type": "string" + }, + "1": { + "name": "password", + "in": "query", + "description": "The password for login in clear text", + "required": true, + "type": "string" + } + }, + "responses": { + "200": { + "description": "successful operation", + "headers": { + "X-Expires-After": { + "type": "string", + "format": "date-time", + "description": "date in UTC when token expires" + }, + "X-Rate-Limit": { + "type": "integer", + "format": "int32", + "description": "calls per hour allowed by the user" + } + }, + "schema": { + "type": "string" + } + }, + "400": { + "description": "Invalid username/password supplied" + } + } + } + }, + "/user/logout": { + "get": { + "tags": { + "0": "user" + }, + "summary": "Logs out current logged in user session", + "description": "", + "operationId": "logoutUser", + "produces": { + "0": "application/json", + "1": "application/xml" + }, + "parameters": {}, + "responses": { + "default": { + "description": "successful operation" + } + } + } + }, + "/user": { + "post": { + "tags": { + "0": "user" + }, + "summary": "Create user", + "description": "This can only be done by the logged in user.", + "operationId": "createUser", + "consumes": { + "0": "application/json" + }, + "produces": { + "0": "application/json", + "1": "application/xml" + }, + "parameters": { + "0": { + "in": "body", + "name": "body", + "description": "Created user object", + "required": true, + "schema": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "username": { + "type": "string" + }, + "firstName": { + "type": "string" + }, + "lastName": { + "type": "string" + }, + "email": { + "type": "string" + }, + "password": { + "type": "string" + }, + "phone": { + "type": "string" + }, + "userStatus": { + "type": "integer", + "format": "int32", + "description": "User Status" + } + }, + "xml": { + "name": "User" + } + } + } + }, + "responses": { + "default": { + "description": "successful operation" + } + } + } + } + }, + "securityDefinitions": { + "api_key": { + "type": "apiKey", + "name": "api_key", + "in": "header" + }, + "petstore_auth": { + "type": "oauth2", + "authorizationUrl": "https://petstore.swagger.io/oauth/authorize", + "flow": "implicit", + "scopes": { + "read:pets": "read your pets", + "write:pets": "modify pets in your account" + } + } + }, + "definitions": { + "ApiResponse": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "format": "int32" + }, + "type": { + "type": "string" + }, + "message": { + "type": "string" + } + } + }, + "Category": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string" + } + }, + "xml": { + "name": "Category" + } + }, + "Pet": { + "type": "object", + "required": { + "0": "name", + "1": "photoUrls" + }, + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "category": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string" + } + }, + "xml": { + "name": "Category" + } + }, + "name": { + "type": "string", + "example": "doggie" + }, + "photoUrls": { + "type": "array", + "xml": { + "wrapped": true + }, + "items": { + "type": "string", + "xml": { + "name": "photoUrl" + } + } + }, + "tags": { + "type": "array", + "xml": { + "wrapped": true + }, + "items": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string" + } + }, + "xml": { + "name": "Tag" + } + } + }, + "status": { + "type": "string", + "description": "pet status in the store", + "enum": { + "0": "available", + "1": "pending", + "2": "sold" + } + } + }, + "xml": { + "name": "Pet" + } + }, + "Tag": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string" + } + }, + "xml": { + "name": "Tag" + } + }, + "Order": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "petId": { + "type": "integer", + "format": "int64" + }, + "quantity": { + "type": "integer", + "format": "int32" + }, + "shipDate": { + "type": "string", + "format": "date-time" + }, + "status": { + "type": "string", + "description": "Order Status", + "enum": { + "0": "placed", + "1": "approved", + "2": "delivered" + } + }, + "complete": { + "type": "boolean" + } + }, + "xml": { + "name": "Order" + } + }, + "User": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "username": { + "type": "string" + }, + "firstName": { + "type": "string" + }, + "lastName": { + "type": "string" + }, + "email": { + "type": "string" + }, + "password": { + "type": "string" + }, + "phone": { + "type": "string" + }, + "userStatus": { + "type": "integer", + "format": "int32", + "description": "User Status" + } + }, + "xml": { + "name": "User" + } + } + }, + "externalDocs": { + "description": "Find out more about Swagger", + "url": "http://swagger.io" + } +} diff --git a/a2.json b/a2.json new file mode 100644 index 00000000..6c97031e --- /dev/null +++ b/a2.json @@ -0,0 +1,83 @@ +{ + "id": "0bad133f-1cfd-47c2-b33c-94279a413397", + "name": "Auth", + "description": "", + "type": "REQUEST", + "request": { + "method": "GET", + "url": "?queryParamKey=queryParamValue", + "body": { + "raw": "{\n\t\"jsonKey\": \"jsonValue\"\n}", + "urlencoded": [ + { + "key": "", + "value": "", + "checked": false + } + ], + "formdata": { + "text": [ + { + "key": "formDataTextKey", + "value": "formDataTextValue", + "checked": true + }, + { + "key": "", + "value": "", + "checked": false + } + ], + "file": [ + { + "key": "formDataFileKey", + "value": "/Users/nayanlakhwani/Downloads/giphy.gif", + "checked": true, + "base": "#@#/Users/nayanlakhwani/Downloads/giphy.gif" + }, + { + "key": "", + "value": "", + "checked": false + } + ] + } + }, + "headers": [ + { + "key": "headerKey", + "value": "headerValue", + "checked": true + }, + { + "key": "", + "value": "", + "checked": false + } + ], + "queryParams": [ + { + "key": "queryParamKey", + "value": "queryParamValue", + "checked": true + }, + { + "key": "", + "value": "", + "checked": false + } + ], + "auth": { + "bearerToken": "bearerToken", + "basicAuth": { + "username": "basicAuthUsername", + "password": "basicAuthPassword" + }, + "apiKey": { + "authKey": "apiKey", + "authValue": "apikeyValue", + "addTo": "Header" + } + } + } +} diff --git a/b.json b/b.json new file mode 100644 index 00000000..6c97031e --- /dev/null +++ b/b.json @@ -0,0 +1,83 @@ +{ + "id": "0bad133f-1cfd-47c2-b33c-94279a413397", + "name": "Auth", + "description": "", + "type": "REQUEST", + "request": { + "method": "GET", + "url": "?queryParamKey=queryParamValue", + "body": { + "raw": "{\n\t\"jsonKey\": \"jsonValue\"\n}", + "urlencoded": [ + { + "key": "", + "value": "", + "checked": false + } + ], + "formdata": { + "text": [ + { + "key": "formDataTextKey", + "value": "formDataTextValue", + "checked": true + }, + { + "key": "", + "value": "", + "checked": false + } + ], + "file": [ + { + "key": "formDataFileKey", + "value": "/Users/nayanlakhwani/Downloads/giphy.gif", + "checked": true, + "base": "#@#/Users/nayanlakhwani/Downloads/giphy.gif" + }, + { + "key": "", + "value": "", + "checked": false + } + ] + } + }, + "headers": [ + { + "key": "headerKey", + "value": "headerValue", + "checked": true + }, + { + "key": "", + "value": "", + "checked": false + } + ], + "queryParams": [ + { + "key": "queryParamKey", + "value": "queryParamValue", + "checked": true + }, + { + "key": "", + "value": "", + "checked": false + } + ], + "auth": { + "bearerToken": "bearerToken", + "basicAuth": { + "username": "basicAuthUsername", + "password": "basicAuthPassword" + }, + "apiKey": { + "authKey": "apiKey", + "authValue": "apikeyValue", + "addTo": "Header" + } + } + } +} diff --git a/src/modules/common/services/helper/oapi2.transformer.ts b/src/modules/common/services/helper/oapi2.transformer.ts new file mode 100644 index 00000000..b4ccb502 --- /dev/null +++ b/src/modules/common/services/helper/oapi2.transformer.ts @@ -0,0 +1,207 @@ +// @ts-nocheck +export function transformPath( + pathName: string, + pathObject: any, + security: any, +) { + const transformedObject = {} as any; + const method = Object.keys(pathObject)[0].toUpperCase(); // Assuming the first key is the HTTP method + pathObject = Object.values(pathObject)[0]; + transformedObject.name = pathName; + transformedObject.description = + pathObject.summary || pathObject.description || ""; // Use summary or description if available + transformedObject.type = "REQUEST"; + + transformedObject.request = {}; + + transformedObject.request.method = method; + + // Extract URL path and query parameters + const urlParts = pathName.split("/").filter((p) => p != ""); + let url = ""; + const queryParams = [] as any; + for (let i = 0; i < urlParts.length; i++) { + if (urlParts[i].startsWith("{")) { + url += "/{" + urlParts[i].slice(1, -1) + "}"; // Replace path parameter with placeholder + } else { + url += "/" + urlParts[i]; + } + if (i + 1 < urlParts.length && urlParts[i + 1].includes("=")) { + const queryParam = urlParts[i + 1].split("="); + queryParams.push({ + key: queryParam[0], + value: queryParam[1], + checked: true, + }); + i++; // Skip the next part as it's already processed as query param + } + } + transformedObject.request.url = url; + transformedObject.request.queryParams = queryParams; + + // Handle request body based on schema + transformedObject.request.body = {}; + let consumes: any = null; + if (pathObject.consumes) { + consumes = Object.values(pathObject.consumes) || []; + if (consumes.includes("application/json")) { + transformedObject.request.body.raw = ""; // Placeholder for raw JSON body + } else if (consumes.includes("application/x-www-form-urlencoded")) { + transformedObject.request.body.urlencoded = []; // Array for form-urlencoded data + } else if (consumes.includes("multipart/form-data")) { + transformedObject.request.body.formdata = { + text: [], + file: [], + }; // Text and file data for multipart form data + } + } + + // Handle headers based on schema + transformedObject.request.headers = []; + + // Handle authentication based on schema + transformedObject.request.auth = {}; + + if (security.api_key) { + transformedObject.request.auth = { + apiKey: { + authKey: security.api_key.name, + authValue: "", + addTo: "", + }, + }; + if (security.api_key.in === "header") { + transformedObject.request.headers.push({ + key: security.api_key.name, + value: "", + checked: false, + }); + transformedObject.request.auth.apiKey.addTo = "Header"; + } else if (security.api_key.in === "query") { + transformedObject.request.queryParams.push({ + key: security.api_key.name, + value: "", + checked: false, + }); + transformedObject.request.auth.apiKey.addTo = "Query Parameters"; + } + } + + // Parse request body parameters + const parameters: + | { + in: string; + name: string; + example: string; + type: string; + schema: { + type: string; + properties: { + type: string; + example: string; + }[]; + }; + }[] + | [] = pathObject.parameters || []; + for (const param of Object.values(parameters)) { + const paramIn = param.in; + const paramName = param.name; + const paramValue = param.example || getExampleValue(param.type); // Assuming example value is representative + + switch (paramIn) { + case "body": + if (consumes) { + if (consumes.includes("application/json")) { + const schema = param.schema; + if (schema && schema.type === "object") { + const properties = schema.properties || {}; + const bodyObject = {}; + for (const [propertyName, property] of Object.entries( + properties, + )) { + const exampleType = property.type; + const exampleValue = property.example; // Use example if available + bodyObject[propertyName] = + exampleValue || + buildExampleValue(property) || + getExampleValue(exampleType); + } + transformedObject.request.body.raw = JSON.stringify(bodyObject); + } + } else if (consumes.includes("application/x-www-form-urlencoded")) { + transformedObject.request.body.urlencoded.push({ + key: paramName, + value: paramValue, + checked: false, + }); + } else if (consumes.includes("multipart/form-data")) { + if (param.type === "file") { + transformedObject.request.body.formdata.file.push({ + key: paramName, + value: paramValue, + checked: false, + base: "#@#" + paramValue, + }); + } else { + transformedObject.request.body.formdata.text.push({ + key: paramName, + value: paramValue, + checked: false, + }); + } + } + } + break; + case "header": + transformedObject.request.headers.push({ + key: paramName, + value: paramValue, + checked: true, + }); + break; + case "query": + transformedObject.request.queryParams.push({ + key: paramName, + value: paramValue, + checked: false, + }); + break; + } + } + + return transformedObject; +} + +function getExampleValue(exampleType) { + switch (exampleType) { + case "string": + return ""; // Or a default string value + case "number": + return 0; // Or a default number value + case "integer": + return 0; // Or a default number value + case "boolean": + return false; // Or a default boolean value + case "array": + return []; // Empty array + case "object": + return {}; // Empty object + default: + return ""; // Or a generic default value + } +} + +function buildExampleValue(property) { + if (property.type === "object") { + const nestedProperties = property.properties || {}; + const nestedObject = {}; + for (const [nestedPropertyName, nestedProperty] of Object.entries( + nestedProperties, + )) { + nestedObject[nestedPropertyName] = buildExampleValue(nestedProperty); + } + return nestedObject; + } else { + return property.example || getExampleValue(property.type); + } +} diff --git a/src/modules/common/services/parser.service.ts b/src/modules/common/services/parser.service.ts index 433f664d..7bca1be9 100644 --- a/src/modules/common/services/parser.service.ts +++ b/src/modules/common/services/parser.service.ts @@ -17,6 +17,8 @@ import { CollectionService } from "@src/modules/workspace/services/collection.se import { WithId } from "mongodb"; import { resolveAllComponentRefs } from "./helper/parser.helper"; import { OpenAPI20 } from "../models/openapi20.model"; +import { transformPath } from "./helper/oapi2.transformer"; +import * as util from "util"; @Injectable() export class ParserService { @@ -103,31 +105,33 @@ export class ParserService { } } else if (openApiDocument.hasOwnProperty("definitions")) { openApiDocument = resolveAllComponentRefs(openApiDocument) as OpenAPI20; - console.log("Yet to implement"); - // newBody = innerValue.parameters.filter( - // (param: ParameterObject) => param.in === "body", - // ); - // if (newBody.length) { - // for (const body of newBody) { - // const bodyToPush: RequestBody = {} as RequestBody; - // const schema = body.schema; - // const ref = schema["$ref"]; - // if (ref) { - // const schemaName = ref.slice( - // ref.lastIndexOf("/") + 1, - // ref.length, - // ); - // bodyToPush.schema = (openApiDocument as any).definitions[ - // schemaName - // ]; - // } else { - // bodyToPush.schema = (schema as any).schema; - // } - // requestObj.request.body.push(bodyToPush); - // } - // } + const transformedPaths: Array = []; + for (const [pathName, pathObject] of Object.entries( + openApiDocument.paths, + )) { + transformedPaths.push( + transformPath( + pathName, + pathObject, + openApiDocument.securityDefinitions, + ), + ); + } - return; + // const requestObj: CollectionItem = { + // id: uuid, + // name: request.items.name, + // type: request.items.type, + // description: request.items.description, + // source: SourceTypeEnum.USER, + // isDeleted: false, + // createdBy: userName, + // updatedBy: userName, + // createdAt: new Date(), + // updatedAt: new Date(), + // }; + console.log("Implementation done"); + console.log(util.inspect(transformedPaths, false, null, true)); } const itemObject = Object.fromEntries(folderObjMap); let items: CollectionItem[] = []; diff --git a/test.js b/test.js new file mode 100644 index 00000000..24f5e47c --- /dev/null +++ b/test.js @@ -0,0 +1,2 @@ + +console.log(util.inspect(transformedPaths, false, null, true)); From 6b406979dae4f3706a90191340aa9870dce32e33 Mon Sep 17 00:00:00 2001 From: Nayan Lakhwani Date: Mon, 4 Mar 2024 23:50:41 -0500 Subject: [PATCH 06/43] feat: open api 2 schema parsing done[] --- src/modules/common/models/collection.model.ts | 2 +- .../services/helper/oapi2.transformer.ts | 191 ++++++++++++------ src/modules/common/services/parser.service.ts | 35 +--- 3 files changed, 133 insertions(+), 95 deletions(-) diff --git a/src/modules/common/models/collection.model.ts b/src/modules/common/models/collection.model.ts index dfda661f..edfdf1a5 100644 --- a/src/modules/common/models/collection.model.ts +++ b/src/modules/common/models/collection.model.ts @@ -116,7 +116,7 @@ export class RequestMetaData { }) @IsEnum({ BodyModeEnum }) @IsString() - @IsNotEmpty() + @IsOptional() selectedRequestBodyType?: BodyModeEnum; @ApiProperty({ diff --git a/src/modules/common/services/helper/oapi2.transformer.ts b/src/modules/common/services/helper/oapi2.transformer.ts index b4ccb502..4b54b391 100644 --- a/src/modules/common/services/helper/oapi2.transformer.ts +++ b/src/modules/common/services/helper/oapi2.transformer.ts @@ -1,16 +1,73 @@ // @ts-nocheck -export function transformPath( - pathName: string, - pathObject: any, - security: any, -) { +import { v4 as uuidv4 } from "uuid"; +import { ItemTypeEnum, SourceTypeEnum } from "../../models/collection.model"; + +export function createCollectionItems(openApiDocument, user) { + let collectionItems = []; + + //Get all collection items + for (const [pathName, pathObject] of Object.entries(openApiDocument.paths)) { + let request = transformPath( + pathName, + pathObject, + openApiDocument.securityDefinitions, + ); + collectionItems.push({ + id: uuidv4(), + name: request.name, + tag: request.tag, + type: ItemTypeEnum.REQUEST, + description: request.description, + operationId: request.operationId, + source: SourceTypeEnum.SPEC, + request: request.request, + isDeleted: false, + createdBy: user.name, + updatedBy: user.name, + createdAt: new Date(), + updatedAt: new Date(), + }); + } + + const baseUrl = getBaseUrl(openApiDocument); + + //Assigning requests to folders according to their tag + const folderMap = new Map(); + for (const item of collectionItems) { + item.request.url = baseUrl + item.request.url; + let tagDescription = ""; + for (const tag of Object.values(openApiDocument?.tags)) { + if (tag.name === item.tag) { + tagDescription = tag.description; + } + } + let folderObj = folderMap.get(item.tag); + if (!folderObj) { + folderObj = {}; + folderObj.name = item.tag; + folderObj.description = tagDescription; + folderObj.isDeleted = false; + folderObj.type = ItemTypeEnum.FOLDER; + folderObj.id = uuidv4(); + folderObj.items = []; + } + delete item.tag; + folderObj.items.push(item); + folderMap.set(folderObj.name, folderObj); + } + return folderMap; +} + +function transformPath(pathName: string, pathObject: any, security: any) { const transformedObject = {} as any; const method = Object.keys(pathObject)[0].toUpperCase(); // Assuming the first key is the HTTP method pathObject = Object.values(pathObject)[0]; + transformedObject.tag = pathObject.tags ? pathObject.tags[0] : "default"; + transformedObject.name = pathName; transformedObject.description = pathObject.summary || pathObject.description || ""; // Use summary or description if available - transformedObject.type = "REQUEST"; + transformedObject.operationId = pathObject.operationId; transformedObject.request = {}; @@ -39,6 +96,8 @@ export function transformPath( transformedObject.request.url = url; transformedObject.request.queryParams = queryParams; + transformedObject.request.pathParams = []; + // Handle request body based on schema transformedObject.request.body = {}; let consumes: any = null; @@ -46,13 +105,14 @@ export function transformPath( consumes = Object.values(pathObject.consumes) || []; if (consumes.includes("application/json")) { transformedObject.request.body.raw = ""; // Placeholder for raw JSON body + transformedObject.request.selectedRequestBodyType = "application/json"; } else if (consumes.includes("application/x-www-form-urlencoded")) { transformedObject.request.body.urlencoded = []; // Array for form-urlencoded data + transformedObject.request.selectedRequestBodyType = + "application/x-www-form-urlencoded"; } else if (consumes.includes("multipart/form-data")) { - transformedObject.request.body.formdata = { - text: [], - file: [], - }; // Text and file data for multipart form data + transformedObject.request.body.formdata = {}; // Text and file data for multipart form data + transformedObject.request.selectedRequestBodyType = "multipart/form-data"; } } @@ -88,21 +148,7 @@ export function transformPath( } // Parse request body parameters - const parameters: - | { - in: string; - name: string; - example: string; - type: string; - schema: { - type: string; - properties: { - type: string; - example: string; - }[]; - }; - }[] - | [] = pathObject.parameters || []; + const parameters = pathObject.parameters || []; for (const param of Object.values(parameters)) { const paramIn = param.in; const paramName = param.name; @@ -110,45 +156,20 @@ export function transformPath( switch (paramIn) { case "body": - if (consumes) { - if (consumes.includes("application/json")) { - const schema = param.schema; - if (schema && schema.type === "object") { - const properties = schema.properties || {}; - const bodyObject = {}; - for (const [propertyName, property] of Object.entries( - properties, - )) { - const exampleType = property.type; - const exampleValue = property.example; // Use example if available - bodyObject[propertyName] = - exampleValue || - buildExampleValue(property) || - getExampleValue(exampleType); - } - transformedObject.request.body.raw = JSON.stringify(bodyObject); - } - } else if (consumes.includes("application/x-www-form-urlencoded")) { - transformedObject.request.body.urlencoded.push({ - key: paramName, - value: paramValue, - checked: false, - }); - } else if (consumes.includes("multipart/form-data")) { - if (param.type === "file") { - transformedObject.request.body.formdata.file.push({ - key: paramName, - value: paramValue, - checked: false, - base: "#@#" + paramValue, - }); - } else { - transformedObject.request.body.formdata.text.push({ - key: paramName, - value: paramValue, - checked: false, - }); + if (consumes && consumes.includes("application/json")) { + const schema = param.schema; + if (schema && schema.type === "object") { + const properties = schema.properties || {}; + const bodyObject = {}; + for (const [propertyName, property] of Object.entries(properties)) { + const exampleType = property.type; + const exampleValue = property.example; // Use example if available + bodyObject[propertyName] = + exampleValue || + buildExampleValue(property) || + getExampleValue(exampleType); } + transformedObject.request.body.raw = JSON.stringify(bodyObject); } } break; @@ -166,6 +187,41 @@ export function transformPath( checked: false, }); break; + case "path": + transformedObject.request.pathParams.push({ + key: paramName, + value: paramValue, + checked: false, + }); + break; + case "formData": + if ( + consumes && + consumes.includes("application/x-www-form-urlencoded") + ) { + transformedObject.request.body.urlencoded.push({ + key: paramName, + value: paramValue, + checked: false, + }); + } else if (consumes && consumes.includes("multipart/form-data")) { + if (param.type === "file") { + transformedObject.request.body.formdata.file = []; + transformedObject.request.body.formdata.file.push({ + key: paramName, + value: paramValue, + checked: false, + base: "#@#" + paramValue, + }); + } else { + transformedObject.request.body.formdata.text = []; + transformedObject.request.body.formdata.text.push({ + key: paramName, + value: paramValue, + checked: false, + }); + } + } } } @@ -205,3 +261,12 @@ function buildExampleValue(property) { return property.example || getExampleValue(property.type); } } + +function getBaseUrl(openApiDocument) { + const basePath = openApiDocument.basePath ? openApiDocument.basePath : ""; + if (openApiDocument.host) { + return "https://" + openApiDocument.host + basePath; + } else { + return "http://localhost:{{PORT}}" + basePath; + } +} \ No newline at end of file diff --git a/src/modules/common/services/parser.service.ts b/src/modules/common/services/parser.service.ts index 7bca1be9..980e8152 100644 --- a/src/modules/common/services/parser.service.ts +++ b/src/modules/common/services/parser.service.ts @@ -17,8 +17,7 @@ import { CollectionService } from "@src/modules/workspace/services/collection.se import { WithId } from "mongodb"; import { resolveAllComponentRefs } from "./helper/parser.helper"; import { OpenAPI20 } from "../models/openapi20.model"; -import { transformPath } from "./helper/oapi2.transformer"; -import * as util from "util"; +import { createCollectionItems } from "./helper/oapi2.transformer"; @Injectable() export class ParserService { @@ -41,7 +40,8 @@ export class ParserService { | OpenAPI20; const baseUrl = this.getBaseUrl(openApiDocument); let existingCollection: WithId | null = null; - const folderObjMap = new Map(); + let folderObjMap = new Map(); + const user = await this.contextService.get("user"); if (openApiDocument.hasOwnProperty("components")) { openApiDocument = resolveAllComponentRefs(openApiDocument) as OpenAPI303; for (const [key, value] of Object.entries(openApiDocument.paths)) { @@ -105,33 +105,7 @@ export class ParserService { } } else if (openApiDocument.hasOwnProperty("definitions")) { openApiDocument = resolveAllComponentRefs(openApiDocument) as OpenAPI20; - const transformedPaths: Array = []; - for (const [pathName, pathObject] of Object.entries( - openApiDocument.paths, - )) { - transformedPaths.push( - transformPath( - pathName, - pathObject, - openApiDocument.securityDefinitions, - ), - ); - } - - // const requestObj: CollectionItem = { - // id: uuid, - // name: request.items.name, - // type: request.items.type, - // description: request.items.description, - // source: SourceTypeEnum.USER, - // isDeleted: false, - // createdBy: userName, - // updatedBy: userName, - // createdAt: new Date(), - // updatedAt: new Date(), - // }; - console.log("Implementation done"); - console.log(util.inspect(transformedPaths, false, null, true)); + folderObjMap = createCollectionItems(openApiDocument, user); } const itemObject = Object.fromEntries(folderObjMap); let items: CollectionItem[] = []; @@ -145,7 +119,6 @@ export class ParserService { items.map((itemObj) => { totalRequests = totalRequests + itemObj.items?.length; }); - const user = await this.contextService.get("user"); if (activeSync) { let mergedFolderItems: CollectionItem[] = []; From ba56d5db42d6f99da9c26fdabbe59bc22a77f2d1 Mon Sep 17 00:00:00 2001 From: Md Asif Raza Date: Tue, 5 Mar 2024 14:28:17 +0530 Subject: [PATCH 07/43] feat: implement auth at change password [11] --- src/modules/common/models/user.model.ts | 5 +++++ .../identity/controllers/user.controller.ts | 18 +++++++++++++++++- .../identity/payloads/resetPassword.payload.ts | 18 ++++++++++++++++-- .../identity/repositories/user.repository.ts | 12 ++++++++++++ src/modules/identity/services/user.service.ts | 15 ++++++++++++--- 5 files changed, 62 insertions(+), 6 deletions(-) diff --git a/src/modules/common/models/user.model.ts b/src/modules/common/models/user.model.ts index df299fc5..75758a17 100644 --- a/src/modules/common/models/user.model.ts +++ b/src/modules/common/models/user.model.ts @@ -2,6 +2,7 @@ import { Type } from "class-transformer"; import { ArrayMaxSize, IsArray, + IsBoolean, IsDate, IsEmail, IsMongoId, @@ -83,6 +84,10 @@ export class User { @IsDate() @IsOptional() verificationCodeTimeStamp?: Date; + + @IsBoolean() + @IsOptional() + isVerificationCodeActive?: boolean; } export class UserDto { diff --git a/src/modules/identity/controllers/user.controller.ts b/src/modules/identity/controllers/user.controller.ts index e4866b7b..2e7e0d2d 100644 --- a/src/modules/identity/controllers/user.controller.ts +++ b/src/modules/identity/controllers/user.controller.ts @@ -31,6 +31,7 @@ import { import { RefreshTokenGuard } from "@src/modules/common/guards/refresh-token.guard"; import { RefreshTokenRequest } from "./auth.controller"; import { JwtAuthGuard } from "@src/modules/common/guards/jwt-auth.guard"; +import { ConfigService } from "@nestjs/config"; /** * User Controller */ @@ -38,7 +39,10 @@ import { JwtAuthGuard } from "@src/modules/common/guards/jwt-auth.guard"; @ApiTags("user") @Controller("api/user") export class UserController { - constructor(private readonly userService: UserService) {} + constructor( + private readonly userService: UserService, + private readonly configService: ConfigService, + ) {} @Post() @ApiOperation({ @@ -196,9 +200,13 @@ export class UserController { @Res() res: FastifyReply, @Body() verifyEmailPayload: VerifyEmailPayload, ) { + const expireTime = this.configService.get( + "app.emailValidationCodeExpirationTime", + ); await this.userService.verifyVerificationCode( verifyEmailPayload.email, verifyEmailPayload.verificationCode, + expireTime, ); const responseData = new ApiResponseService( "Email Verified Successfully", @@ -217,6 +225,14 @@ export class UserController { @Res() res: FastifyReply, @Body() updatePasswordPayload: UpdatePasswordPayload, ) { + const expireTime = this.configService.get( + "app.emailValidationCodeExpirationTime", + ); + await this.userService.verifyVerificationCode( + updatePasswordPayload.email, + updatePasswordPayload.verificationCode, + expireTime * 2, + ); await this.userService.updatePassword( updatePasswordPayload.email, updatePasswordPayload.newPassword, diff --git a/src/modules/identity/payloads/resetPassword.payload.ts b/src/modules/identity/payloads/resetPassword.payload.ts index c1561b82..487d81b6 100644 --- a/src/modules/identity/payloads/resetPassword.payload.ts +++ b/src/modules/identity/payloads/resetPassword.payload.ts @@ -1,5 +1,5 @@ import { ApiProperty } from "@nestjs/swagger"; -import { IsEmail, IsNotEmpty, MinLength } from "class-validator"; +import { IsEmail, IsNotEmpty, Matches, MinLength } from "class-validator"; export class ResetPasswordPayload { @ApiProperty({ @@ -49,7 +49,21 @@ export class UpdatePasswordPayload { required: true, example: "newPassword", }) - @MinLength(8) @IsNotEmpty() + @Matches(/(?=.*[0-9])/, { + message: "password must contain at least one digit.", + }) + @Matches(/(?=.*[!@#$%^&*])/, { + message: "password must contain at least one special character (!@#$%^&*).", + }) + @MinLength(8) newPassword: string; + + @ApiProperty({ + required: true, + example: "ABC123", + }) + @MinLength(6) + @IsNotEmpty() + verificationCode: string; } diff --git a/src/modules/identity/repositories/user.repository.ts b/src/modules/identity/repositories/user.repository.ts index 855b8c89..c71994d1 100644 --- a/src/modules/identity/repositories/user.repository.ts +++ b/src/modules/identity/repositories/user.repository.ts @@ -223,6 +223,18 @@ export class UserRepository { $set: { verificationCode, verificationCodeTimeStamp: new Date(), + isVerificationCodeActive: true, + }, + }, + ); + } + + async expireVerificationCode(email: string): Promise { + await this.db.collection(Collections.USER).findOneAndUpdate( + { email }, + { + $set: { + isVerificationCodeActive: false, }, }, ); diff --git a/src/modules/identity/services/user.service.ts b/src/modules/identity/services/user.service.ts index 7f7c975b..3d912c04 100644 --- a/src/modules/identity/services/user.service.ts +++ b/src/modules/identity/services/user.service.ts @@ -269,14 +269,15 @@ export class UserService { async verifyVerificationCode( email: string, verificationCode: string, + expireTime: number, ): Promise { const user = await this.getUserByEmail(email); + if (!user?.isVerificationCodeActive) { + throw new UnauthorizedException(ErrorMessages.Unauthorized); + } if (user?.verificationCode !== verificationCode.toUpperCase()) { throw new UnauthorizedException(ErrorMessages.Unauthorized); } - const expireTime = this.configService.get( - "app.emailValidationCodeExpirationTime", - ); if ( (Date.now() - user.verificationCodeTimeStamp.getTime()) / 1000 > expireTime @@ -285,6 +286,12 @@ export class UserService { } return; } + + async expireVerificationCode(email: string): Promise { + await this.userRepository.expireVerificationCode(email); + return; + } + async updatePassword(email: string, password: string): Promise { const user = await this.getUserByEmailAndPass(email, password); @@ -292,8 +299,10 @@ export class UserService { throw new UnauthorizedException(ErrorMessages.PasswordExist); } await this.userRepository.updatePassword(email, password); + await this.expireVerificationCode(email); return; } + generateEmailVerificationCode(): string { return (Math.random() + 1).toString(36).substring(2, 8); } From 05d95e89c668f9afbb268153a996678dce3434cc Mon Sep 17 00:00:00 2001 From: Nayan Lakhwani Date: Tue, 5 Mar 2024 14:44:24 +0530 Subject: [PATCH 08/43] feat: oapi3 transform completed[] --- a.json | 1716 ----------------- a2.json | 83 - b.json | 83 - .../services/helper/oapi.transformer.ts | 492 +++++ .../services/helper/oapi2.transformer.ts | 272 --- .../common/services/helper/parser.helper.ts | 2 +- src/modules/common/services/parser.service.ts | 85 +- test.js | 2 - 8 files changed, 499 insertions(+), 2236 deletions(-) delete mode 100644 a.json delete mode 100644 a2.json delete mode 100644 b.json create mode 100644 src/modules/common/services/helper/oapi.transformer.ts delete mode 100644 src/modules/common/services/helper/oapi2.transformer.ts delete mode 100644 test.js diff --git a/a.json b/a.json deleted file mode 100644 index 8e5c5f8a..00000000 --- a/a.json +++ /dev/null @@ -1,1716 +0,0 @@ -{ - "swagger": "2.0", - "info": { - "description": "This is a sample server Petstore server. You can find out more about Swagger at [http://swagger.io](http://swagger.io) or on [irc.freenode.net, #swagger](http://swagger.io/irc/). For this sample, you can use the api key `special-key` to test the authorization filters.", - "version": "1.0.6", - "title": "Swagger Petstore", - "termsOfService": "http://swagger.io/terms/", - "contact": { - "email": "apiteam@swagger.io" - }, - "license": { - "name": "Apache 2.0", - "url": "http://www.apache.org/licenses/LICENSE-2.0.html" - } - }, - "host": "petstore.swagger.io", - "basePath": "/v2", - "tags": { - "0": { - "name": "pet", - "description": "Everything about your Pets", - "externalDocs": { - "description": "Find out more", - "url": "http://swagger.io" - } - }, - "1": { - "name": "store", - "description": "Access to Petstore orders" - }, - "2": { - "name": "user", - "description": "Operations about user", - "externalDocs": { - "description": "Find out more about our store", - "url": "http://swagger.io" - } - } - }, - "schemes": { - "0": "https", - "1": "http" - }, - "paths": { - "/pet/{petId}/uploadImage": { - "post": { - "tags": { - "0": "pet" - }, - "summary": "uploads an image", - "description": "", - "operationId": "uploadFile", - "consumes": { - "0": "multipart/form-data" - }, - "produces": { - "0": "application/json" - }, - "parameters": { - "0": { - "name": "petId", - "in": "path", - "description": "ID of pet to update", - "required": true, - "type": "integer", - "format": "int64" - }, - "1": { - "name": "additionalMetadata", - "in": "formData", - "description": "Additional data to pass to server", - "required": false, - "type": "string" - }, - "2": { - "name": "file", - "in": "formData", - "description": "file to upload", - "required": false, - "type": "file" - } - }, - "responses": { - "200": { - "description": "successful operation", - "schema": { - "type": "object", - "properties": { - "code": { - "type": "integer", - "format": "int32" - }, - "type": { - "type": "string" - }, - "message": { - "type": "string" - } - } - } - } - }, - "security": { - "0": { - "petstore_auth": { - "0": "write:pets", - "1": "read:pets" - } - } - } - } - }, - "/pet": { - "post": { - "tags": { - "0": "pet" - }, - "summary": "Add a new pet to the store", - "description": "", - "operationId": "addPet", - "consumes": { - "0": "application/json", - "1": "application/xml" - }, - "produces": { - "0": "application/json", - "1": "application/xml" - }, - "parameters": { - "0": { - "in": "body", - "name": "body", - "description": "Pet object that needs to be added to the store", - "required": true, - "schema": { - "type": "object", - "required": { - "0": "name", - "1": "photoUrls" - }, - "properties": { - "id": { - "type": "integer", - "format": "int64" - }, - "category": { - "type": "object", - "properties": { - "id": { - "type": "integer", - "format": "int64" - }, - "name": { - "type": "string" - } - }, - "xml": { - "name": "Category" - } - }, - "name": { - "type": "string", - "example": "doggie" - }, - "photoUrls": { - "type": "array", - "xml": { - "wrapped": true - }, - "items": { - "type": "string", - "xml": { - "name": "photoUrl" - } - } - }, - "tags": { - "type": "array", - "xml": { - "wrapped": true - }, - "items": { - "type": "object", - "properties": { - "id": { - "type": "integer", - "format": "int64" - }, - "name": { - "type": "string" - } - }, - "xml": { - "name": "Tag" - } - } - }, - "status": { - "type": "string", - "description": "pet status in the store", - "enum": { - "0": "available", - "1": "pending", - "2": "sold" - } - } - }, - "xml": { - "name": "Pet" - } - } - } - }, - "responses": { - "405": { - "description": "Invalid input" - } - }, - "security": { - "0": { - "petstore_auth": { - "0": "write:pets", - "1": "read:pets" - } - } - } - }, - "put": { - "tags": { - "0": "pet" - }, - "summary": "Update an existing pet", - "description": "", - "operationId": "updatePet", - "consumes": { - "0": "application/json", - "1": "application/xml" - }, - "produces": { - "0": "application/json", - "1": "application/xml" - }, - "parameters": { - "0": { - "in": "body", - "name": "body", - "description": "Pet object that needs to be added to the store", - "required": true, - "schema": { - "type": "object", - "required": { - "0": "name", - "1": "photoUrls" - }, - "properties": { - "id": { - "type": "integer", - "format": "int64" - }, - "category": { - "type": "object", - "properties": { - "id": { - "type": "integer", - "format": "int64" - }, - "name": { - "type": "string" - } - }, - "xml": { - "name": "Category" - } - }, - "name": { - "type": "string", - "example": "doggie" - }, - "photoUrls": { - "type": "array", - "xml": { - "wrapped": true - }, - "items": { - "type": "string", - "xml": { - "name": "photoUrl" - } - } - }, - "tags": { - "type": "array", - "xml": { - "wrapped": true - }, - "items": { - "type": "object", - "properties": { - "id": { - "type": "integer", - "format": "int64" - }, - "name": { - "type": "string" - } - }, - "xml": { - "name": "Tag" - } - } - }, - "status": { - "type": "string", - "description": "pet status in the store", - "enum": { - "0": "available", - "1": "pending", - "2": "sold" - } - } - }, - "xml": { - "name": "Pet" - } - } - } - }, - "responses": { - "400": { - "description": "Invalid ID supplied" - }, - "404": { - "description": "Pet not found" - }, - "405": { - "description": "Validation exception" - } - }, - "security": { - "0": { - "petstore_auth": { - "0": "write:pets", - "1": "read:pets" - } - } - } - } - }, - "/pet/findByStatus": { - "get": { - "tags": { - "0": "pet" - }, - "summary": "Finds Pets by status", - "description": "Multiple status values can be provided with comma separated strings", - "operationId": "findPetsByStatus", - "produces": { - "0": "application/json", - "1": "application/xml" - }, - "parameters": { - "0": { - "name": "status", - "in": "query", - "description": "Status values that need to be considered for filter", - "required": true, - "type": "array", - "items": { - "type": "string", - "enum": { - "0": "available", - "1": "pending", - "2": "sold" - }, - "default": "available" - }, - "collectionFormat": "multi" - } - }, - "responses": { - "200": { - "description": "successful operation", - "schema": { - "type": "array", - "items": { - "type": "object", - "required": { - "0": "name", - "1": "photoUrls" - }, - "properties": { - "id": { - "type": "integer", - "format": "int64" - }, - "category": { - "type": "object", - "properties": { - "id": { - "type": "integer", - "format": "int64" - }, - "name": { - "type": "string" - } - }, - "xml": { - "name": "Category" - } - }, - "name": { - "type": "string", - "example": "doggie" - }, - "photoUrls": { - "type": "array", - "xml": { - "wrapped": true - }, - "items": { - "type": "string", - "xml": { - "name": "photoUrl" - } - } - }, - "tags": { - "type": "array", - "xml": { - "wrapped": true - }, - "items": { - "type": "object", - "properties": { - "id": { - "type": "integer", - "format": "int64" - }, - "name": { - "type": "string" - } - }, - "xml": { - "name": "Tag" - } - } - }, - "status": { - "type": "string", - "description": "pet status in the store", - "enum": { - "0": "available", - "1": "pending", - "2": "sold" - } - } - }, - "xml": { - "name": "Pet" - } - } - } - }, - "400": { - "description": "Invalid status value" - } - }, - "security": { - "0": { - "petstore_auth": { - "0": "write:pets", - "1": "read:pets" - } - } - } - } - }, - "/pet/findByTags": { - "get": { - "tags": { - "0": "pet" - }, - "summary": "Finds Pets by tags", - "description": "Multiple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing.", - "operationId": "findPetsByTags", - "produces": { - "0": "application/json", - "1": "application/xml" - }, - "parameters": { - "0": { - "name": "tags", - "in": "query", - "description": "Tags to filter by", - "required": true, - "type": "array", - "items": { - "type": "string" - }, - "collectionFormat": "multi" - } - }, - "responses": { - "200": { - "description": "successful operation", - "schema": { - "type": "array", - "items": { - "type": "object", - "required": { - "0": "name", - "1": "photoUrls" - }, - "properties": { - "id": { - "type": "integer", - "format": "int64" - }, - "category": { - "type": "object", - "properties": { - "id": { - "type": "integer", - "format": "int64" - }, - "name": { - "type": "string" - } - }, - "xml": { - "name": "Category" - } - }, - "name": { - "type": "string", - "example": "doggie" - }, - "photoUrls": { - "type": "array", - "xml": { - "wrapped": true - }, - "items": { - "type": "string", - "xml": { - "name": "photoUrl" - } - } - }, - "tags": { - "type": "array", - "xml": { - "wrapped": true - }, - "items": { - "type": "object", - "properties": { - "id": { - "type": "integer", - "format": "int64" - }, - "name": { - "type": "string" - } - }, - "xml": { - "name": "Tag" - } - } - }, - "status": { - "type": "string", - "description": "pet status in the store", - "enum": { - "0": "available", - "1": "pending", - "2": "sold" - } - } - }, - "xml": { - "name": "Pet" - } - } - } - }, - "400": { - "description": "Invalid tag value" - } - }, - "security": { - "0": { - "petstore_auth": { - "0": "write:pets", - "1": "read:pets" - } - } - }, - "deprecated": true - } - }, - "/pet/{petId}": { - "get": { - "tags": { - "0": "pet" - }, - "summary": "Find pet by ID", - "description": "Returns a single pet", - "operationId": "getPetById", - "produces": { - "0": "application/json", - "1": "application/xml" - }, - "parameters": { - "0": { - "name": "petId", - "in": "path", - "description": "ID of pet to return", - "required": true, - "type": "integer", - "format": "int64" - } - }, - "responses": { - "200": { - "description": "successful operation", - "schema": { - "type": "object", - "required": { - "0": "name", - "1": "photoUrls" - }, - "properties": { - "id": { - "type": "integer", - "format": "int64" - }, - "category": { - "type": "object", - "properties": { - "id": { - "type": "integer", - "format": "int64" - }, - "name": { - "type": "string" - } - }, - "xml": { - "name": "Category" - } - }, - "name": { - "type": "string", - "example": "doggie" - }, - "photoUrls": { - "type": "array", - "xml": { - "wrapped": true - }, - "items": { - "type": "string", - "xml": { - "name": "photoUrl" - } - } - }, - "tags": { - "type": "array", - "xml": { - "wrapped": true - }, - "items": { - "type": "object", - "properties": { - "id": { - "type": "integer", - "format": "int64" - }, - "name": { - "type": "string" - } - }, - "xml": { - "name": "Tag" - } - } - }, - "status": { - "type": "string", - "description": "pet status in the store", - "enum": { - "0": "available", - "1": "pending", - "2": "sold" - } - } - }, - "xml": { - "name": "Pet" - } - } - }, - "400": { - "description": "Invalid ID supplied" - }, - "404": { - "description": "Pet not found" - } - }, - "security": { - "0": { - "api_key": {} - } - } - }, - "post": { - "tags": { - "0": "pet" - }, - "summary": "Updates a pet in the store with form data", - "description": "", - "operationId": "updatePetWithForm", - "consumes": { - "0": "application/x-www-form-urlencoded" - }, - "produces": { - "0": "application/json", - "1": "application/xml" - }, - "parameters": { - "0": { - "name": "petId", - "in": "path", - "description": "ID of pet that needs to be updated", - "required": true, - "type": "integer", - "format": "int64" - }, - "1": { - "name": "name", - "in": "formData", - "description": "Updated name of the pet", - "required": false, - "type": "string" - }, - "2": { - "name": "status", - "in": "formData", - "description": "Updated status of the pet", - "required": false, - "type": "string" - } - }, - "responses": { - "405": { - "description": "Invalid input" - } - }, - "security": { - "0": { - "petstore_auth": { - "0": "write:pets", - "1": "read:pets" - } - } - } - }, - "delete": { - "tags": { - "0": "pet" - }, - "summary": "Deletes a pet", - "description": "", - "operationId": "deletePet", - "produces": { - "0": "application/json", - "1": "application/xml" - }, - "parameters": { - "0": { - "name": "api_key", - "in": "header", - "required": false, - "type": "string" - }, - "1": { - "name": "petId", - "in": "path", - "description": "Pet id to delete", - "required": true, - "type": "integer", - "format": "int64" - } - }, - "responses": { - "400": { - "description": "Invalid ID supplied" - }, - "404": { - "description": "Pet not found" - } - }, - "security": { - "0": { - "petstore_auth": { - "0": "write:pets", - "1": "read:pets" - } - } - } - } - }, - "/store/order": { - "post": { - "tags": { - "0": "store" - }, - "summary": "Place an order for a pet", - "description": "", - "operationId": "placeOrder", - "consumes": { - "0": "application/json" - }, - "produces": { - "0": "application/json", - "1": "application/xml" - }, - "parameters": { - "0": { - "in": "body", - "name": "body", - "description": "order placed for purchasing the pet", - "required": true, - "schema": { - "type": "object", - "properties": { - "id": { - "type": "integer", - "format": "int64" - }, - "petId": { - "type": "integer", - "format": "int64" - }, - "quantity": { - "type": "integer", - "format": "int32" - }, - "shipDate": { - "type": "string", - "format": "date-time" - }, - "status": { - "type": "string", - "description": "Order Status", - "enum": { - "0": "placed", - "1": "approved", - "2": "delivered" - } - }, - "complete": { - "type": "boolean" - } - }, - "xml": { - "name": "Order" - } - } - } - }, - "responses": { - "200": { - "description": "successful operation", - "schema": { - "type": "object", - "properties": { - "id": { - "type": "integer", - "format": "int64" - }, - "petId": { - "type": "integer", - "format": "int64" - }, - "quantity": { - "type": "integer", - "format": "int32" - }, - "shipDate": { - "type": "string", - "format": "date-time" - }, - "status": { - "type": "string", - "description": "Order Status", - "enum": { - "0": "placed", - "1": "approved", - "2": "delivered" - } - }, - "complete": { - "type": "boolean" - } - }, - "xml": { - "name": "Order" - } - } - }, - "400": { - "description": "Invalid Order" - } - } - } - }, - "/store/order/{orderId}": { - "get": { - "tags": { - "0": "store" - }, - "summary": "Find purchase order by ID", - "description": "For valid response try integer IDs with value >= 1 and <= 10. Other values will generated exceptions", - "operationId": "getOrderById", - "produces": { - "0": "application/json", - "1": "application/xml" - }, - "parameters": { - "0": { - "name": "orderId", - "in": "path", - "description": "ID of pet that needs to be fetched", - "required": true, - "type": "integer", - "maximum": 10, - "minimum": 1, - "format": "int64" - } - }, - "responses": { - "200": { - "description": "successful operation", - "schema": { - "type": "object", - "properties": { - "id": { - "type": "integer", - "format": "int64" - }, - "petId": { - "type": "integer", - "format": "int64" - }, - "quantity": { - "type": "integer", - "format": "int32" - }, - "shipDate": { - "type": "string", - "format": "date-time" - }, - "status": { - "type": "string", - "description": "Order Status", - "enum": { - "0": "placed", - "1": "approved", - "2": "delivered" - } - }, - "complete": { - "type": "boolean" - } - }, - "xml": { - "name": "Order" - } - } - }, - "400": { - "description": "Invalid ID supplied" - }, - "404": { - "description": "Order not found" - } - } - }, - "delete": { - "tags": { - "0": "store" - }, - "summary": "Delete purchase order by ID", - "description": "For valid response try integer IDs with positive integer value. Negative or non-integer values will generate API errors", - "operationId": "deleteOrder", - "produces": { - "0": "application/json", - "1": "application/xml" - }, - "parameters": { - "0": { - "name": "orderId", - "in": "path", - "description": "ID of the order that needs to be deleted", - "required": true, - "type": "integer", - "minimum": 1, - "format": "int64" - } - }, - "responses": { - "400": { - "description": "Invalid ID supplied" - }, - "404": { - "description": "Order not found" - } - } - } - }, - "/store/inventory": { - "get": { - "tags": { - "0": "store" - }, - "summary": "Returns pet inventories by status", - "description": "Returns a map of status codes to quantities", - "operationId": "getInventory", - "produces": { - "0": "application/json" - }, - "parameters": {}, - "responses": { - "200": { - "description": "successful operation", - "schema": { - "type": "object", - "additionalProperties": { - "type": "integer", - "format": "int32" - } - } - } - }, - "security": { - "0": { - "api_key": {} - } - } - } - }, - "/user/createWithArray": { - "post": { - "tags": { - "0": "user" - }, - "summary": "Creates list of users with given input array", - "description": "", - "operationId": "createUsersWithArrayInput", - "consumes": { - "0": "application/json" - }, - "produces": { - "0": "application/json", - "1": "application/xml" - }, - "parameters": { - "0": { - "in": "body", - "name": "body", - "description": "List of user object", - "required": true, - "schema": { - "type": "array", - "items": { - "type": "object", - "properties": { - "id": { - "type": "integer", - "format": "int64" - }, - "username": { - "type": "string" - }, - "firstName": { - "type": "string" - }, - "lastName": { - "type": "string" - }, - "email": { - "type": "string" - }, - "password": { - "type": "string" - }, - "phone": { - "type": "string" - }, - "userStatus": { - "type": "integer", - "format": "int32", - "description": "User Status" - } - }, - "xml": { - "name": "User" - } - } - } - } - }, - "responses": { - "default": { - "description": "successful operation" - } - } - } - }, - "/user/createWithList": { - "post": { - "tags": { - "0": "user" - }, - "summary": "Creates list of users with given input array", - "description": "", - "operationId": "createUsersWithListInput", - "consumes": { - "0": "application/json" - }, - "produces": { - "0": "application/json", - "1": "application/xml" - }, - "parameters": { - "0": { - "in": "body", - "name": "body", - "description": "List of user object", - "required": true, - "schema": { - "type": "array", - "items": { - "type": "object", - "properties": { - "id": { - "type": "integer", - "format": "int64" - }, - "username": { - "type": "string" - }, - "firstName": { - "type": "string" - }, - "lastName": { - "type": "string" - }, - "email": { - "type": "string" - }, - "password": { - "type": "string" - }, - "phone": { - "type": "string" - }, - "userStatus": { - "type": "integer", - "format": "int32", - "description": "User Status" - } - }, - "xml": { - "name": "User" - } - } - } - } - }, - "responses": { - "default": { - "description": "successful operation" - } - } - } - }, - "/user/{username}": { - "get": { - "tags": { - "0": "user" - }, - "summary": "Get user by user name", - "description": "", - "operationId": "getUserByName", - "produces": { - "0": "application/json", - "1": "application/xml" - }, - "parameters": { - "0": { - "name": "username", - "in": "path", - "description": "The name that needs to be fetched. Use user1 for testing. ", - "required": true, - "type": "string" - } - }, - "responses": { - "200": { - "description": "successful operation", - "schema": { - "type": "object", - "properties": { - "id": { - "type": "integer", - "format": "int64" - }, - "username": { - "type": "string" - }, - "firstName": { - "type": "string" - }, - "lastName": { - "type": "string" - }, - "email": { - "type": "string" - }, - "password": { - "type": "string" - }, - "phone": { - "type": "string" - }, - "userStatus": { - "type": "integer", - "format": "int32", - "description": "User Status" - } - }, - "xml": { - "name": "User" - } - } - }, - "400": { - "description": "Invalid username supplied" - }, - "404": { - "description": "User not found" - } - } - }, - "put": { - "tags": { - "0": "user" - }, - "summary": "Updated user", - "description": "This can only be done by the logged in user.", - "operationId": "updateUser", - "consumes": { - "0": "application/json" - }, - "produces": { - "0": "application/json", - "1": "application/xml" - }, - "parameters": { - "0": { - "name": "username", - "in": "path", - "description": "name that need to be updated", - "required": true, - "type": "string" - }, - "1": { - "in": "body", - "name": "body", - "description": "Updated user object", - "required": true, - "schema": { - "type": "object", - "properties": { - "id": { - "type": "integer", - "format": "int64" - }, - "username": { - "type": "string" - }, - "firstName": { - "type": "string" - }, - "lastName": { - "type": "string" - }, - "email": { - "type": "string" - }, - "password": { - "type": "string" - }, - "phone": { - "type": "string" - }, - "userStatus": { - "type": "integer", - "format": "int32", - "description": "User Status" - } - }, - "xml": { - "name": "User" - } - } - } - }, - "responses": { - "400": { - "description": "Invalid user supplied" - }, - "404": { - "description": "User not found" - } - } - }, - "delete": { - "tags": { - "0": "user" - }, - "summary": "Delete user", - "description": "This can only be done by the logged in user.", - "operationId": "deleteUser", - "produces": { - "0": "application/json", - "1": "application/xml" - }, - "parameters": { - "0": { - "name": "username", - "in": "path", - "description": "The name that needs to be deleted", - "required": true, - "type": "string" - } - }, - "responses": { - "400": { - "description": "Invalid username supplied" - }, - "404": { - "description": "User not found" - } - } - } - }, - "/user/login": { - "get": { - "tags": { - "0": "user" - }, - "summary": "Logs user into the system", - "description": "", - "operationId": "loginUser", - "produces": { - "0": "application/json", - "1": "application/xml" - }, - "parameters": { - "0": { - "name": "username", - "in": "query", - "description": "The user name for login", - "required": true, - "type": "string" - }, - "1": { - "name": "password", - "in": "query", - "description": "The password for login in clear text", - "required": true, - "type": "string" - } - }, - "responses": { - "200": { - "description": "successful operation", - "headers": { - "X-Expires-After": { - "type": "string", - "format": "date-time", - "description": "date in UTC when token expires" - }, - "X-Rate-Limit": { - "type": "integer", - "format": "int32", - "description": "calls per hour allowed by the user" - } - }, - "schema": { - "type": "string" - } - }, - "400": { - "description": "Invalid username/password supplied" - } - } - } - }, - "/user/logout": { - "get": { - "tags": { - "0": "user" - }, - "summary": "Logs out current logged in user session", - "description": "", - "operationId": "logoutUser", - "produces": { - "0": "application/json", - "1": "application/xml" - }, - "parameters": {}, - "responses": { - "default": { - "description": "successful operation" - } - } - } - }, - "/user": { - "post": { - "tags": { - "0": "user" - }, - "summary": "Create user", - "description": "This can only be done by the logged in user.", - "operationId": "createUser", - "consumes": { - "0": "application/json" - }, - "produces": { - "0": "application/json", - "1": "application/xml" - }, - "parameters": { - "0": { - "in": "body", - "name": "body", - "description": "Created user object", - "required": true, - "schema": { - "type": "object", - "properties": { - "id": { - "type": "integer", - "format": "int64" - }, - "username": { - "type": "string" - }, - "firstName": { - "type": "string" - }, - "lastName": { - "type": "string" - }, - "email": { - "type": "string" - }, - "password": { - "type": "string" - }, - "phone": { - "type": "string" - }, - "userStatus": { - "type": "integer", - "format": "int32", - "description": "User Status" - } - }, - "xml": { - "name": "User" - } - } - } - }, - "responses": { - "default": { - "description": "successful operation" - } - } - } - } - }, - "securityDefinitions": { - "api_key": { - "type": "apiKey", - "name": "api_key", - "in": "header" - }, - "petstore_auth": { - "type": "oauth2", - "authorizationUrl": "https://petstore.swagger.io/oauth/authorize", - "flow": "implicit", - "scopes": { - "read:pets": "read your pets", - "write:pets": "modify pets in your account" - } - } - }, - "definitions": { - "ApiResponse": { - "type": "object", - "properties": { - "code": { - "type": "integer", - "format": "int32" - }, - "type": { - "type": "string" - }, - "message": { - "type": "string" - } - } - }, - "Category": { - "type": "object", - "properties": { - "id": { - "type": "integer", - "format": "int64" - }, - "name": { - "type": "string" - } - }, - "xml": { - "name": "Category" - } - }, - "Pet": { - "type": "object", - "required": { - "0": "name", - "1": "photoUrls" - }, - "properties": { - "id": { - "type": "integer", - "format": "int64" - }, - "category": { - "type": "object", - "properties": { - "id": { - "type": "integer", - "format": "int64" - }, - "name": { - "type": "string" - } - }, - "xml": { - "name": "Category" - } - }, - "name": { - "type": "string", - "example": "doggie" - }, - "photoUrls": { - "type": "array", - "xml": { - "wrapped": true - }, - "items": { - "type": "string", - "xml": { - "name": "photoUrl" - } - } - }, - "tags": { - "type": "array", - "xml": { - "wrapped": true - }, - "items": { - "type": "object", - "properties": { - "id": { - "type": "integer", - "format": "int64" - }, - "name": { - "type": "string" - } - }, - "xml": { - "name": "Tag" - } - } - }, - "status": { - "type": "string", - "description": "pet status in the store", - "enum": { - "0": "available", - "1": "pending", - "2": "sold" - } - } - }, - "xml": { - "name": "Pet" - } - }, - "Tag": { - "type": "object", - "properties": { - "id": { - "type": "integer", - "format": "int64" - }, - "name": { - "type": "string" - } - }, - "xml": { - "name": "Tag" - } - }, - "Order": { - "type": "object", - "properties": { - "id": { - "type": "integer", - "format": "int64" - }, - "petId": { - "type": "integer", - "format": "int64" - }, - "quantity": { - "type": "integer", - "format": "int32" - }, - "shipDate": { - "type": "string", - "format": "date-time" - }, - "status": { - "type": "string", - "description": "Order Status", - "enum": { - "0": "placed", - "1": "approved", - "2": "delivered" - } - }, - "complete": { - "type": "boolean" - } - }, - "xml": { - "name": "Order" - } - }, - "User": { - "type": "object", - "properties": { - "id": { - "type": "integer", - "format": "int64" - }, - "username": { - "type": "string" - }, - "firstName": { - "type": "string" - }, - "lastName": { - "type": "string" - }, - "email": { - "type": "string" - }, - "password": { - "type": "string" - }, - "phone": { - "type": "string" - }, - "userStatus": { - "type": "integer", - "format": "int32", - "description": "User Status" - } - }, - "xml": { - "name": "User" - } - } - }, - "externalDocs": { - "description": "Find out more about Swagger", - "url": "http://swagger.io" - } -} diff --git a/a2.json b/a2.json deleted file mode 100644 index 6c97031e..00000000 --- a/a2.json +++ /dev/null @@ -1,83 +0,0 @@ -{ - "id": "0bad133f-1cfd-47c2-b33c-94279a413397", - "name": "Auth", - "description": "", - "type": "REQUEST", - "request": { - "method": "GET", - "url": "?queryParamKey=queryParamValue", - "body": { - "raw": "{\n\t\"jsonKey\": \"jsonValue\"\n}", - "urlencoded": [ - { - "key": "", - "value": "", - "checked": false - } - ], - "formdata": { - "text": [ - { - "key": "formDataTextKey", - "value": "formDataTextValue", - "checked": true - }, - { - "key": "", - "value": "", - "checked": false - } - ], - "file": [ - { - "key": "formDataFileKey", - "value": "/Users/nayanlakhwani/Downloads/giphy.gif", - "checked": true, - "base": "#@#/Users/nayanlakhwani/Downloads/giphy.gif" - }, - { - "key": "", - "value": "", - "checked": false - } - ] - } - }, - "headers": [ - { - "key": "headerKey", - "value": "headerValue", - "checked": true - }, - { - "key": "", - "value": "", - "checked": false - } - ], - "queryParams": [ - { - "key": "queryParamKey", - "value": "queryParamValue", - "checked": true - }, - { - "key": "", - "value": "", - "checked": false - } - ], - "auth": { - "bearerToken": "bearerToken", - "basicAuth": { - "username": "basicAuthUsername", - "password": "basicAuthPassword" - }, - "apiKey": { - "authKey": "apiKey", - "authValue": "apikeyValue", - "addTo": "Header" - } - } - } -} diff --git a/b.json b/b.json deleted file mode 100644 index 6c97031e..00000000 --- a/b.json +++ /dev/null @@ -1,83 +0,0 @@ -{ - "id": "0bad133f-1cfd-47c2-b33c-94279a413397", - "name": "Auth", - "description": "", - "type": "REQUEST", - "request": { - "method": "GET", - "url": "?queryParamKey=queryParamValue", - "body": { - "raw": "{\n\t\"jsonKey\": \"jsonValue\"\n}", - "urlencoded": [ - { - "key": "", - "value": "", - "checked": false - } - ], - "formdata": { - "text": [ - { - "key": "formDataTextKey", - "value": "formDataTextValue", - "checked": true - }, - { - "key": "", - "value": "", - "checked": false - } - ], - "file": [ - { - "key": "formDataFileKey", - "value": "/Users/nayanlakhwani/Downloads/giphy.gif", - "checked": true, - "base": "#@#/Users/nayanlakhwani/Downloads/giphy.gif" - }, - { - "key": "", - "value": "", - "checked": false - } - ] - } - }, - "headers": [ - { - "key": "headerKey", - "value": "headerValue", - "checked": true - }, - { - "key": "", - "value": "", - "checked": false - } - ], - "queryParams": [ - { - "key": "queryParamKey", - "value": "queryParamValue", - "checked": true - }, - { - "key": "", - "value": "", - "checked": false - } - ], - "auth": { - "bearerToken": "bearerToken", - "basicAuth": { - "username": "basicAuthUsername", - "password": "basicAuthPassword" - }, - "apiKey": { - "authKey": "apiKey", - "authValue": "apikeyValue", - "addTo": "Header" - } - } - } -} diff --git a/src/modules/common/services/helper/oapi.transformer.ts b/src/modules/common/services/helper/oapi.transformer.ts new file mode 100644 index 00000000..86e2d3c0 --- /dev/null +++ b/src/modules/common/services/helper/oapi.transformer.ts @@ -0,0 +1,492 @@ +// @ts-nocheck +import { v4 as uuidv4 } from "uuid"; +import { ItemTypeEnum, SourceTypeEnum } from "../../models/collection.model"; + +export function createCollectionItems(openApiDocument, user) { + const collectionItems = []; + + if (openApiDocument.components) { + for (const [pathName, pathObject] of Object.entries( + openApiDocument.paths, + )) { + const request = transformPathV3( + pathName, + pathObject, + openApiDocument.components.securitySchemes, + ); + collectionItems.push({ + id: uuidv4(), + name: request.name, + tag: request.tag, + type: ItemTypeEnum.REQUEST, + description: request.description, + operationId: request.operationId, + source: SourceTypeEnum.SPEC, + request: request.request, + isDeleted: false, + createdBy: user.name, + updatedBy: user.name, + createdAt: new Date(), + updatedAt: new Date(), + }); + } + } else if (openApiDocument.definitions) { + //Get all collection items + for (const [pathName, pathObject] of Object.entries( + openApiDocument.paths, + )) { + const request = transformPath( + pathName, + pathObject, + openApiDocument.securityDefinitions, + ); + collectionItems.push({ + id: uuidv4(), + name: request.name, + tag: request.tag, + type: ItemTypeEnum.REQUEST, + description: request.description, + operationId: request.operationId, + source: SourceTypeEnum.SPEC, + request: request.request, + isDeleted: false, + createdBy: user.name, + updatedBy: user.name, + createdAt: new Date(), + updatedAt: new Date(), + }); + } + } + + const baseUrl = getBaseUrl(openApiDocument); + + //Assigning requests to folders according to their tag + const folderMap = new Map(); + for (const item of collectionItems) { + item.request.url = baseUrl + item.request.url; + let tagDescription = ""; + for (const tag of Object.values(openApiDocument?.tags)) { + if (tag.name === item.tag) { + tagDescription = tag.description; + } + } + let folderObj = folderMap.get(item.tag); + if (!folderObj) { + folderObj = {}; + folderObj.name = item.tag; + folderObj.description = tagDescription; + folderObj.isDeleted = false; + folderObj.type = ItemTypeEnum.FOLDER; + folderObj.id = uuidv4(); + folderObj.items = []; + } + delete item.tag; + folderObj.items.push(item); + folderMap.set(folderObj.name, folderObj); + } + return folderMap; +} + +function transformPath(pathName: string, pathObject: any, security: any) { + const transformedObject = {} as any; + const method = Object.keys(pathObject)[0].toUpperCase(); // Assuming the first key is the HTTP method + pathObject = Object.values(pathObject)[0]; + transformedObject.tag = pathObject.tags ? pathObject.tags[0] : "default"; + + transformedObject.name = pathName; + transformedObject.description = + pathObject.summary || pathObject.description || ""; // Use summary or description if available + transformedObject.operationId = pathObject.operationId; + + transformedObject.request = {}; + + transformedObject.request.method = method; + + // Extract URL path and query parameters + const urlParts = pathName.split("/").filter((p) => p != ""); + let url = ""; + const queryParams = [] as any; + for (let i = 0; i < urlParts.length; i++) { + if (urlParts[i].startsWith("{")) { + url += "/{" + urlParts[i].slice(1, -1) + "}"; + } else { + url += "/" + urlParts[i]; + } + if (i + 1 < urlParts.length && urlParts[i + 1].includes("=")) { + const queryParam = urlParts[i + 1].split("="); + queryParams.push({ + key: queryParam[0], + value: queryParam[1], + checked: true, + }); + i++; + } + } + transformedObject.request.url = url; + transformedObject.request.queryParams = queryParams; + transformedObject.request.pathParams = []; + + // Handle request body based on schema + transformedObject.request.body = {}; + let consumes: any = null; + if (pathObject.consumes) { + consumes = Object.values(pathObject.consumes) || []; + if (consumes.includes("application/json")) { + transformedObject.request.body.raw = ""; // Placeholder for raw JSON body + transformedObject.request.selectedRequestBodyType = "application/json"; + } else if (consumes.includes("application/x-www-form-urlencoded")) { + transformedObject.request.body.urlencoded = []; // Array for form-urlencoded data + transformedObject.request.selectedRequestBodyType = + "application/x-www-form-urlencoded"; + } else if (consumes.includes("multipart/form-data")) { + transformedObject.request.body.formdata = {}; // Text and file data for multipart form data + transformedObject.request.selectedRequestBodyType = "multipart/form-data"; + } + } + + // Handle headers based on schema + transformedObject.request.headers = []; + + // Handle authentication based on schema + transformedObject.request.auth = {}; + + if (security.api_key) { + transformedObject.request.auth = { + apiKey: { + authKey: security.api_key.name, + authValue: "", + addTo: "", + }, + }; + if (security.api_key.in === "header") { + transformedObject.request.headers.push({ + key: security.api_key.name, + value: "", + checked: false, + }); + transformedObject.request.auth.apiKey.addTo = "Header"; + } else if (security.api_key.in === "query") { + transformedObject.request.queryParams.push({ + key: security.api_key.name, + value: "", + checked: false, + }); + transformedObject.request.auth.apiKey.addTo = "Query Parameters"; + } + } + + // Parse request body parameters + const parameters = pathObject.parameters || []; + for (const param of Object.values(parameters)) { + const paramIn = param.in; + const paramName = param.name; + const paramValue = param.example || getExampleValue(param.type); // Assuming example value is representative + + switch (paramIn) { + case "body": + if (consumes && consumes.includes("application/json")) { + const schema = param.schema; + if (schema && schema.type === "object") { + const properties = schema.properties || {}; + const bodyObject = {}; + for (const [propertyName, property] of Object.entries(properties)) { + const exampleType = property.type; + const exampleValue = property.example; // Use example if available + bodyObject[propertyName] = + exampleValue || + buildExampleValue(property) || + getExampleValue(exampleType); + } + transformedObject.request.body.raw = JSON.stringify(bodyObject); + } + } + break; + case "header": + transformedObject.request.headers.push({ + key: paramName, + value: paramValue, + checked: true, + }); + break; + case "query": + transformedObject.request.queryParams.push({ + key: paramName, + value: paramValue, + checked: false, + }); + break; + case "path": + transformedObject.request.pathParams.push({ + key: paramName, + value: paramValue, + checked: false, + }); + break; + case "formData": + if ( + consumes && + consumes.includes("application/x-www-form-urlencoded") + ) { + transformedObject.request.body.urlencoded.push({ + key: paramName, + value: paramValue, + checked: false, + }); + } else if (consumes && consumes.includes("multipart/form-data")) { + if (param.type === "file") { + transformedObject.request.body.formdata.file = []; + transformedObject.request.body.formdata.file.push({ + key: paramName, + value: paramValue, + checked: false, + base: "#@#" + paramValue, + }); + } else { + transformedObject.request.body.formdata.text = []; + transformedObject.request.body.formdata.text.push({ + key: paramName, + value: paramValue, + checked: false, + }); + } + } + } + } + + return transformedObject; +} + +function transformPathV3(pathName: string, pathObject: any, security: any) { + const transformedObject = {} as any; + const method = Object.keys(pathObject)[0].toUpperCase(); + pathObject = Object.values(pathObject)[0]; + transformedObject.tag = pathObject.tags ? pathObject.tags[0] : "default"; + transformedObject.name = pathName; + transformedObject.description = + pathObject.summary || pathObject.description || ""; + transformedObject.operationId = pathObject.operationId; + transformedObject.request = {}; + transformedObject.request.method = method; + + // Extract URL path and query parameters + const urlParts = pathName.split("/").filter((p) => p != ""); + let url = ""; + const queryParams = [] as any; + for (let i = 0; i < urlParts.length; i++) { + if (urlParts[i].startsWith("{")) { + url += "/{" + urlParts[i].slice(1, -1) + "}"; + } else { + url += "/" + urlParts[i]; + } + if (i + 1 < urlParts.length && urlParts[i + 1].includes("=")) { + const queryParam = urlParts[i + 1].split("="); + queryParams.push({ + key: queryParam[0], + value: queryParam[1], + checked: true, + }); + i++; + } + } + transformedObject.request.url = url; + transformedObject.request.queryParams = queryParams; + transformedObject.request.pathParams = []; + + // Handle request body based on schema + transformedObject.request.body = {}; + transformedObject.request.body.raw = ""; + transformedObject.request.body.formdata = {}; + transformedObject.request.body.formdata.file = []; + transformedObject.request.body.formdata.text = []; + transformedObject.request.body.urlencoded = []; + + const content = pathObject?.requestBody?.content; + if (content) { + const contentKeys = Object.keys(pathObject.requestBody.content) || []; + for (const key of contentKeys) { + if (key === "application/json") { + const schema = content[key].schema; + if (schema && schema.type === "object") { + const properties = schema.properties || {}; + const bodyObject = {}; + for (const [propertyName, property] of Object.entries(properties)) { + const exampleType = property.type; + const exampleValue = property.example; // Use example if available + bodyObject[propertyName] = + exampleValue || + buildExampleValue(property) || + getExampleValue(exampleType); + } + transformedObject.request.body.raw = JSON.stringify(bodyObject); + } + transformedObject.request.selectedRequestBodyType = "application/json"; + } + if (key === "application/x-www-form-urlencoded") { + const schema = content[key].schema; + if (schema && schema.type === "object") { + const properties = schema.properties || {}; + for (const [propertyName, property] of Object.entries(properties)) { + const exampleType = property.type; + const exampleValue = property.example; // Use example if available + transformedObject.request.body.urlencoded.push({ + key: propertyName, + value: + exampleValue || + buildExampleValue(property) || + getExampleValue(exampleType), + checked: false, + }); + } + } + transformedObject.request.selectedRequestBodyType = + "application/x-www-form-urlencoded"; + } + if (key === "application/octet-stream") { + transformedObject.request.body.formdata.file = []; + transformedObject.request.body.formdata.file.push({ + key: "file", + value: "", + checked: false, + base: "#@#", + }); + transformedObject.request.selectedRequestBodyType = + "multipart/form-data"; + } + if (key === "multipart/form-data") { + const schema = content[key].schema; + if (schema && schema.type === "object") { + const properties = schema.properties || {}; + for (const [propertyName, property] of Object.entries(properties)) { + if (property.type === "string" || property.type === "object") { + if (property.format === "binary") { + transformedObject.request.body.formdata.file.push({ + key: propertyName, + value: "", + checked: false, + base: "#@#" + "", + }); + } else { + transformedObject.request.body.formdata.text.push({ + key: propertyName, + value: getExampleValue(property.format), + checked: false, + base: "#@#" + "", + }); + } + } + } + } + transformedObject.request.selectedRequestBodyType = + "multipart/form-data"; + } + } + } + + // Handle headers based on schema + transformedObject.request.headers = []; + + // Handle authentication based on schema + transformedObject.request.auth = {}; + + if (security.api_key) { + transformedObject.request.auth = { + apiKey: { + authKey: security.api_key.name, + authValue: "", + addTo: "", + }, + }; + if (security.api_key.in === "header") { + transformedObject.request.headers.push({ + key: security.api_key.name, + value: "", + checked: false, + }); + transformedObject.request.auth.apiKey.addTo = "Header"; + } else if (security.api_key.in === "query") { + transformedObject.request.queryParams.push({ + key: security.api_key.name, + value: "", + checked: false, + }); + transformedObject.request.auth.apiKey.addTo = "Query Parameters"; + } + } + + // Parse request body parameters + const parameters = pathObject.parameters || []; + for (const param of Object.values(parameters)) { + const paramIn = param.in; + const paramName = param.name; + const paramValue = param.example || getExampleValue(param.type); + + switch (paramIn) { + case "header": + transformedObject.request.headers.push({ + key: paramName, + value: paramValue, + checked: true, + }); + break; + case "query": + transformedObject.request.queryParams.push({ + key: paramName, + value: paramValue, + checked: false, + }); + break; + case "path": + transformedObject.request.pathParams.push({ + key: paramName, + value: paramValue, + checked: false, + }); + break; + } + } + + return transformedObject; +} + +function getExampleValue(exampleType) { + switch (exampleType) { + case "string": + return ""; // Or a default string value + case "number": + return 0; // Or a default number value + case "integer": + return 0; // Or a default number value + case "boolean": + return false; // Or a default boolean value + case "array": + return []; // Empty array + case "object": + return {}; // Empty object + default: + return ""; // Or a generic default value + } +} + +function buildExampleValue(property) { + if (property.type === "object") { + const nestedProperties = property.properties || {}; + const nestedObject = {}; + for (const [nestedPropertyName, nestedProperty] of Object.entries( + nestedProperties, + )) { + nestedObject[nestedPropertyName] = buildExampleValue(nestedProperty); + } + return nestedObject; + } else { + return property.example || getExampleValue(property.type); + } +} + +function getBaseUrl(openApiDocument) { + const basePath = openApiDocument.basePath ? openApiDocument.basePath : ""; + if (openApiDocument.host) { + return "https://" + openApiDocument.host + basePath; + } else { + return "http://localhost:{{PORT}}" + basePath; + } +} diff --git a/src/modules/common/services/helper/oapi2.transformer.ts b/src/modules/common/services/helper/oapi2.transformer.ts deleted file mode 100644 index 4b54b391..00000000 --- a/src/modules/common/services/helper/oapi2.transformer.ts +++ /dev/null @@ -1,272 +0,0 @@ -// @ts-nocheck -import { v4 as uuidv4 } from "uuid"; -import { ItemTypeEnum, SourceTypeEnum } from "../../models/collection.model"; - -export function createCollectionItems(openApiDocument, user) { - let collectionItems = []; - - //Get all collection items - for (const [pathName, pathObject] of Object.entries(openApiDocument.paths)) { - let request = transformPath( - pathName, - pathObject, - openApiDocument.securityDefinitions, - ); - collectionItems.push({ - id: uuidv4(), - name: request.name, - tag: request.tag, - type: ItemTypeEnum.REQUEST, - description: request.description, - operationId: request.operationId, - source: SourceTypeEnum.SPEC, - request: request.request, - isDeleted: false, - createdBy: user.name, - updatedBy: user.name, - createdAt: new Date(), - updatedAt: new Date(), - }); - } - - const baseUrl = getBaseUrl(openApiDocument); - - //Assigning requests to folders according to their tag - const folderMap = new Map(); - for (const item of collectionItems) { - item.request.url = baseUrl + item.request.url; - let tagDescription = ""; - for (const tag of Object.values(openApiDocument?.tags)) { - if (tag.name === item.tag) { - tagDescription = tag.description; - } - } - let folderObj = folderMap.get(item.tag); - if (!folderObj) { - folderObj = {}; - folderObj.name = item.tag; - folderObj.description = tagDescription; - folderObj.isDeleted = false; - folderObj.type = ItemTypeEnum.FOLDER; - folderObj.id = uuidv4(); - folderObj.items = []; - } - delete item.tag; - folderObj.items.push(item); - folderMap.set(folderObj.name, folderObj); - } - return folderMap; -} - -function transformPath(pathName: string, pathObject: any, security: any) { - const transformedObject = {} as any; - const method = Object.keys(pathObject)[0].toUpperCase(); // Assuming the first key is the HTTP method - pathObject = Object.values(pathObject)[0]; - transformedObject.tag = pathObject.tags ? pathObject.tags[0] : "default"; - - transformedObject.name = pathName; - transformedObject.description = - pathObject.summary || pathObject.description || ""; // Use summary or description if available - transformedObject.operationId = pathObject.operationId; - - transformedObject.request = {}; - - transformedObject.request.method = method; - - // Extract URL path and query parameters - const urlParts = pathName.split("/").filter((p) => p != ""); - let url = ""; - const queryParams = [] as any; - for (let i = 0; i < urlParts.length; i++) { - if (urlParts[i].startsWith("{")) { - url += "/{" + urlParts[i].slice(1, -1) + "}"; // Replace path parameter with placeholder - } else { - url += "/" + urlParts[i]; - } - if (i + 1 < urlParts.length && urlParts[i + 1].includes("=")) { - const queryParam = urlParts[i + 1].split("="); - queryParams.push({ - key: queryParam[0], - value: queryParam[1], - checked: true, - }); - i++; // Skip the next part as it's already processed as query param - } - } - transformedObject.request.url = url; - transformedObject.request.queryParams = queryParams; - - transformedObject.request.pathParams = []; - - // Handle request body based on schema - transformedObject.request.body = {}; - let consumes: any = null; - if (pathObject.consumes) { - consumes = Object.values(pathObject.consumes) || []; - if (consumes.includes("application/json")) { - transformedObject.request.body.raw = ""; // Placeholder for raw JSON body - transformedObject.request.selectedRequestBodyType = "application/json"; - } else if (consumes.includes("application/x-www-form-urlencoded")) { - transformedObject.request.body.urlencoded = []; // Array for form-urlencoded data - transformedObject.request.selectedRequestBodyType = - "application/x-www-form-urlencoded"; - } else if (consumes.includes("multipart/form-data")) { - transformedObject.request.body.formdata = {}; // Text and file data for multipart form data - transformedObject.request.selectedRequestBodyType = "multipart/form-data"; - } - } - - // Handle headers based on schema - transformedObject.request.headers = []; - - // Handle authentication based on schema - transformedObject.request.auth = {}; - - if (security.api_key) { - transformedObject.request.auth = { - apiKey: { - authKey: security.api_key.name, - authValue: "", - addTo: "", - }, - }; - if (security.api_key.in === "header") { - transformedObject.request.headers.push({ - key: security.api_key.name, - value: "", - checked: false, - }); - transformedObject.request.auth.apiKey.addTo = "Header"; - } else if (security.api_key.in === "query") { - transformedObject.request.queryParams.push({ - key: security.api_key.name, - value: "", - checked: false, - }); - transformedObject.request.auth.apiKey.addTo = "Query Parameters"; - } - } - - // Parse request body parameters - const parameters = pathObject.parameters || []; - for (const param of Object.values(parameters)) { - const paramIn = param.in; - const paramName = param.name; - const paramValue = param.example || getExampleValue(param.type); // Assuming example value is representative - - switch (paramIn) { - case "body": - if (consumes && consumes.includes("application/json")) { - const schema = param.schema; - if (schema && schema.type === "object") { - const properties = schema.properties || {}; - const bodyObject = {}; - for (const [propertyName, property] of Object.entries(properties)) { - const exampleType = property.type; - const exampleValue = property.example; // Use example if available - bodyObject[propertyName] = - exampleValue || - buildExampleValue(property) || - getExampleValue(exampleType); - } - transformedObject.request.body.raw = JSON.stringify(bodyObject); - } - } - break; - case "header": - transformedObject.request.headers.push({ - key: paramName, - value: paramValue, - checked: true, - }); - break; - case "query": - transformedObject.request.queryParams.push({ - key: paramName, - value: paramValue, - checked: false, - }); - break; - case "path": - transformedObject.request.pathParams.push({ - key: paramName, - value: paramValue, - checked: false, - }); - break; - case "formData": - if ( - consumes && - consumes.includes("application/x-www-form-urlencoded") - ) { - transformedObject.request.body.urlencoded.push({ - key: paramName, - value: paramValue, - checked: false, - }); - } else if (consumes && consumes.includes("multipart/form-data")) { - if (param.type === "file") { - transformedObject.request.body.formdata.file = []; - transformedObject.request.body.formdata.file.push({ - key: paramName, - value: paramValue, - checked: false, - base: "#@#" + paramValue, - }); - } else { - transformedObject.request.body.formdata.text = []; - transformedObject.request.body.formdata.text.push({ - key: paramName, - value: paramValue, - checked: false, - }); - } - } - } - } - - return transformedObject; -} - -function getExampleValue(exampleType) { - switch (exampleType) { - case "string": - return ""; // Or a default string value - case "number": - return 0; // Or a default number value - case "integer": - return 0; // Or a default number value - case "boolean": - return false; // Or a default boolean value - case "array": - return []; // Empty array - case "object": - return {}; // Empty object - default: - return ""; // Or a generic default value - } -} - -function buildExampleValue(property) { - if (property.type === "object") { - const nestedProperties = property.properties || {}; - const nestedObject = {}; - for (const [nestedPropertyName, nestedProperty] of Object.entries( - nestedProperties, - )) { - nestedObject[nestedPropertyName] = buildExampleValue(nestedProperty); - } - return nestedObject; - } else { - return property.example || getExampleValue(property.type); - } -} - -function getBaseUrl(openApiDocument) { - const basePath = openApiDocument.basePath ? openApiDocument.basePath : ""; - if (openApiDocument.host) { - return "https://" + openApiDocument.host + basePath; - } else { - return "http://localhost:{{PORT}}" + basePath; - } -} \ No newline at end of file diff --git a/src/modules/common/services/helper/parser.helper.ts b/src/modules/common/services/helper/parser.helper.ts index 7039d130..038dca8a 100644 --- a/src/modules/common/services/helper/parser.helper.ts +++ b/src/modules/common/services/helper/parser.helper.ts @@ -1,4 +1,4 @@ -export function resolveAllComponentRefs(spec: any) { +export function resolveAllRefs(spec: any) { if (!spec) return spec; if (typeof spec === "object") { diff --git a/src/modules/common/services/parser.service.ts b/src/modules/common/services/parser.service.ts index 980e8152..2b77cb47 100644 --- a/src/modules/common/services/parser.service.ts +++ b/src/modules/common/services/parser.service.ts @@ -1,23 +1,18 @@ import SwaggerParser from "@apidevtools/swagger-parser"; import { - BodyModeEnum, Collection, CollectionItem, ItemTypeEnum, - RequestBody, - RequestMetaData, SourceTypeEnum, } from "../models/collection.model"; -import { OpenAPI303, ParameterObject } from "../models/openapi303.model"; -import { HTTPMethods } from "fastify"; +import { OpenAPI303 } from "../models/openapi303.model"; import { Injectable } from "@nestjs/common"; import { ContextService } from "./context.service"; -import { v4 as uuidv4 } from "uuid"; import { CollectionService } from "@src/modules/workspace/services/collection.service"; import { WithId } from "mongodb"; -import { resolveAllComponentRefs } from "./helper/parser.helper"; +import { resolveAllRefs } from "./helper/parser.helper"; import { OpenAPI20 } from "../models/openapi20.model"; -import { createCollectionItems } from "./helper/oapi2.transformer"; +import { createCollectionItems } from "./helper/oapi.transformer"; @Injectable() export class ParserService { @@ -38,75 +33,15 @@ export class ParserService { let openApiDocument = (await SwaggerParser.parse(file)) as | OpenAPI303 | OpenAPI20; - const baseUrl = this.getBaseUrl(openApiDocument); let existingCollection: WithId | null = null; let folderObjMap = new Map(); const user = await this.contextService.get("user"); if (openApiDocument.hasOwnProperty("components")) { - openApiDocument = resolveAllComponentRefs(openApiDocument) as OpenAPI303; - for (const [key, value] of Object.entries(openApiDocument.paths)) { - //key will be endpoints /put and values will its apis post ,put etc - for (const [innerKey, innerValue] of Object.entries(value)) { - //key will be api methods and values will it's desc - const requestObj: CollectionItem = {} as CollectionItem; - requestObj.name = key; - requestObj.description = innerValue.description; - requestObj.type = ItemTypeEnum.REQUEST; - requestObj.source = SourceTypeEnum.SPEC; - requestObj.id = uuidv4(); - requestObj.isDeleted = false; - requestObj.request = {} as RequestMetaData; - requestObj.request.method = innerKey.toUpperCase() as HTTPMethods; - requestObj.request.operationId = innerValue.operationId; - requestObj.request.url = baseUrl + key; - if (innerValue.parameters?.length) { - requestObj.request.queryParams = innerValue.parameters.filter( - (param: ParameterObject) => param.in === "query", - ); - requestObj.request.pathParams = innerValue.parameters.filter( - (param: ParameterObject) => param.in === "path", - ); - requestObj.request.headers = innerValue.parameters.filter( - (param: ParameterObject) => param.in === "header", - ); - } - if (innerValue.requestBody) { - requestObj.request.body = []; - const bodyTypes = innerValue.requestBody.content; - for (const [type, schema] of Object.entries(bodyTypes)) { - const body: RequestBody = {} as RequestBody; - body.type = Object.values(BodyModeEnum).find( - (enumMember) => enumMember === type, - ) as BodyModeEnum; - body.schema = (schema as any).schema; - requestObj.request.body.push(body); - } - } - //Add to a folder - const tag = innerValue.tags ? innerValue.tags[0] : "default"; - const tagArr = - openApiDocument?.tags?.length > 0 && - openApiDocument.tags.filter((tagObj) => { - return tagObj.name === tag; - }); - let folderObj: CollectionItem = folderObjMap.get(tag); - if (!folderObj) { - folderObj = {} as CollectionItem; - folderObj.name = tag; - folderObj.description = tagArr ? tagArr[0].description : ""; - folderObj.isDeleted = false; - folderObj.type = ItemTypeEnum.FOLDER; - folderObj.id = uuidv4(); - folderObj.items = []; - } - folderObj.items.push(requestObj); - folderObjMap.set(folderObj.name, folderObj); - } - } + openApiDocument = resolveAllRefs(openApiDocument) as OpenAPI303; } else if (openApiDocument.hasOwnProperty("definitions")) { - openApiDocument = resolveAllComponentRefs(openApiDocument) as OpenAPI20; - folderObjMap = createCollectionItems(openApiDocument, user); + openApiDocument = resolveAllRefs(openApiDocument) as OpenAPI20; } + folderObjMap = createCollectionItems(openApiDocument, user); const itemObject = Object.fromEntries(folderObjMap); let items: CollectionItem[] = []; let totalRequests = 0; @@ -285,12 +220,4 @@ export class ParserService { return mergedArray; } - getBaseUrl(openApiDocument: any): string { - const basePath = openApiDocument.basePath ? openApiDocument.basePath : ""; - if (openApiDocument.host) { - return "https://" + openApiDocument.host + basePath; - } else { - return "http://localhost:{{PORT}}" + basePath; - } - } } diff --git a/test.js b/test.js deleted file mode 100644 index 24f5e47c..00000000 --- a/test.js +++ /dev/null @@ -1,2 +0,0 @@ - -console.log(util.inspect(transformedPaths, false, null, true)); From e81f604ec7fb22002b47f6e775bc7a49818de560 Mon Sep 17 00:00:00 2001 From: Md Asif Raza Date: Tue, 5 Mar 2024 15:53:22 +0530 Subject: [PATCH 09/43] fix: refresh verification code for change password [11] --- src/modules/identity/controllers/user.controller.ts | 6 +++++- src/modules/identity/services/user.service.ts | 6 ++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/modules/identity/controllers/user.controller.ts b/src/modules/identity/controllers/user.controller.ts index 2e7e0d2d..f530c6f4 100644 --- a/src/modules/identity/controllers/user.controller.ts +++ b/src/modules/identity/controllers/user.controller.ts @@ -208,9 +208,13 @@ export class UserController { verifyEmailPayload.verificationCode, expireTime, ); + const data = await this.userService.refreshVerificationCode( + verifyEmailPayload.email, + ); const responseData = new ApiResponseService( "Email Verified Successfully", HttpStatusCode.OK, + data, ); return res.status(responseData.httpStatusCode).send(responseData); } @@ -231,7 +235,7 @@ export class UserController { await this.userService.verifyVerificationCode( updatePasswordPayload.email, updatePasswordPayload.verificationCode, - expireTime * 2, + expireTime, ); await this.userService.updatePassword( updatePasswordPayload.email, diff --git a/src/modules/identity/services/user.service.ts b/src/modules/identity/services/user.service.ts index 3d912c04..794acdfb 100644 --- a/src/modules/identity/services/user.service.ts +++ b/src/modules/identity/services/user.service.ts @@ -292,6 +292,12 @@ export class UserService { return; } + async refreshVerificationCode(email: string): Promise { + const verificationCode = this.generateEmailVerificationCode().toUpperCase(); + await this.userRepository.updateVerificationCode(email, verificationCode); + return verificationCode; + } + async updatePassword(email: string, password: string): Promise { const user = await this.getUserByEmailAndPass(email, password); From 3e726eba035b88d6d820d01eed8b0af453e908e8 Mon Sep 17 00:00:00 2001 From: Nayan Lakhwani Date: Thu, 7 Mar 2024 00:47:23 -0500 Subject: [PATCH 10/43] feat: curl parse init[] --- package.json | 1 + pnpm-lock.yaml | 191 +++++++++++++++++- src/modules/app/app.service.ts | 13 ++ .../services/helper/oapi2.transformer.ts | 6 +- tsconfig.json | 14 +- 5 files changed, 214 insertions(+), 11 deletions(-) diff --git a/package.json b/package.json index fab04bb4..4dd5470c 100644 --- a/package.json +++ b/package.json @@ -50,6 +50,7 @@ "class-transformer": "0.5.1", "class-transformer-validator": "^0.9.1", "class-validator": "^0.14.0", + "curlconverter": "^4.9.0", "dotenv": "16.0.1", "eslint-config-prettier": "^9.0.0", "eslint-plugin-prettier": "^5.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index eedc0820..0a3ab476 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,4 +1,4 @@ -lockfileVersion: '6.0' +lockfileVersion: '6.1' settings: autoInstallPeers: true @@ -80,6 +80,9 @@ dependencies: class-validator: specifier: ^0.14.0 version: 0.14.0 + curlconverter: + specifier: ^4.9.0 + version: 4.9.0 dotenv: specifier: 16.0.1 version: 16.0.1 @@ -1711,6 +1714,14 @@ packages: '@jridgewell/trace-mapping': 0.3.9 dev: true + /@curlconverter/tree-sitter-bash@0.0.3: + resolution: {integrity: sha512-w3SfZ5uuGyQKMqCpGhOa7+ptr53zBi+gjE87OSUwozF9Vt6SwToxWlCAifI1rOyUg5156FYfODQ0m2Er0Ys9Bw==} + requiresBuild: true + dependencies: + nan: 2.19.0 + prebuild-install: 7.1.2 + dev: false + /@eslint-community/eslint-utils@4.4.0(eslint@8.47.0): resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -3359,7 +3370,6 @@ packages: buffer: 5.7.1 inherits: 2.0.4 readable-stream: 3.6.2 - dev: true /blueimp-md5@2.19.0: resolution: {integrity: sha512-DRQrD6gJyy8FbiE4s+bDoXS9hiW3Vbx5uCdwvcCf3zLHL+Iv7LtGHLpr+GZV8rHG8tK766FGYBwRbu8pELTt+w==} @@ -3471,7 +3481,6 @@ packages: dependencies: base64-js: 1.5.1 ieee754: 1.2.1 - dev: true /buffer@6.0.3: resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} @@ -3600,6 +3609,10 @@ packages: fsevents: 2.3.2 dev: true + /chownr@1.1.4: + resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==} + dev: false + /ci-info@3.8.0: resolution: {integrity: sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw==} engines: {node: '>=8'} @@ -3855,6 +3868,18 @@ packages: engines: {node: '>= 6'} dev: true + /curlconverter@4.9.0: + resolution: {integrity: sha512-cJy7LjfA3LaOyBe+fFQAPND54sbsxSMiL4pPOLDvE8NTAshyeyU02tj/63m6RpSPQZVBlYntou+PhCo/G4CrOg==} + hasBin: true + dependencies: + '@curlconverter/tree-sitter-bash': 0.0.3 + jsesc: 3.0.2 + lossless-json: 2.0.11 + tree-sitter: 0.20.6 + web-tree-sitter: 0.20.8 + yamljs: 0.3.0 + dev: false + /d@1.0.1: resolution: {integrity: sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==} dependencies: @@ -3914,6 +3939,13 @@ packages: engines: {node: '>=0.10.0'} dev: false + /decompress-response@6.0.0: + resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} + engines: {node: '>=10'} + dependencies: + mimic-response: 3.1.0 + dev: false + /dedent@1.5.1: resolution: {integrity: sha512-+LxW+KLWxu3HW3M2w2ympwtqPrqYRzU8fqi6Fhd18fBALe15blJPI/I4+UHveMVG6lJqB4JNd4UG0S5cnVHwIg==} peerDependencies: @@ -3941,6 +3973,11 @@ packages: regexp.prototype.flags: 1.5.0 dev: true + /deep-extend@0.6.0: + resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} + engines: {node: '>=4.0.0'} + dev: false + /deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} @@ -4009,6 +4046,11 @@ packages: engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} dev: true + /detect-libc@2.0.2: + resolution: {integrity: sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==} + engines: {node: '>=8'} + dev: false + /detect-newline@3.1.0: resolution: {integrity: sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==} engines: {node: '>=8'} @@ -4497,6 +4539,11 @@ packages: engines: {node: '>= 0.8.0'} dev: true + /expand-template@2.0.3: + resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==} + engines: {node: '>=6'} + dev: false + /expect@29.6.2: resolution: {integrity: sha512-iAErsLxJ8C+S02QbLAwgSGSezLQK+XXRDt8IuFXFpwCNw2ECmzZSmjKcCaFVp5VRMk+WAvz6h6jokzEzBFZEuA==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -4771,6 +4818,10 @@ packages: resolution: {integrity: sha512-twe20eF1OxVxp/ML/kq2p1uc6KvFK/+vs8WjEbeKmV2He22MKm7YF2ANIt+EOqhJ5L3K/SuuPhk0hWQDjOM23g==} dev: true + /fs-constants@1.0.0: + resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} + dev: false + /fs-extra@10.1.0: resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==} engines: {node: '>=12'} @@ -4848,6 +4899,10 @@ packages: resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} engines: {node: '>=10'} + /github-from-package@0.0.0: + resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==} + dev: false + /glob-parent@5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} @@ -5159,6 +5214,10 @@ packages: /inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + /ini@1.3.8: + resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} + dev: false + /inside@1.0.0: resolution: {integrity: sha512-tvFwvS4g7q6iDot/4FjtWFHwwpv6TVvEumbTdLQilk1F07ojakbXPQcvf3kMAlyNDpzKRzn+d33O3RuXODuxZQ==} deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. @@ -5823,6 +5882,12 @@ packages: hasBin: true dev: true + /jsesc@3.0.2: + resolution: {integrity: sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==} + engines: {node: '>=6'} + hasBin: true + dev: false + /json-parse-even-better-errors@2.3.1: resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} dev: true @@ -6054,6 +6119,10 @@ packages: resolution: {integrity: sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==} dev: false + /lossless-json@2.0.11: + resolution: {integrity: sha512-BP0vn+NGYvzDielvBZaFain/wgeJ1hTvURCqtKvhr1SCPePdaaTanmmcplrHfEJSJOUql7hk4FHwToNJjWRY3g==} + dev: false + /loupe@2.3.6: resolution: {integrity: sha512-RaPMZKiMy8/JruncMU5Bt6na1eftNoo++R4Y+N2FrxkDVTrGvcyzFTsaGif4QTeKESheMGegbhw6iUAq+5A8zA==} dependencies: @@ -6192,6 +6261,11 @@ packages: resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==} engines: {node: '>=12'} + /mimic-response@3.1.0: + resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} + engines: {node: '>=10'} + dev: false + /minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} dependencies: @@ -6219,6 +6293,10 @@ packages: engines: {node: '>=16 || 14 >=14.17'} dev: false + /mkdirp-classic@0.5.3: + resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==} + dev: false + /mkdirp@1.0.4: resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} engines: {node: '>=10'} @@ -6289,6 +6367,14 @@ packages: /ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + /nan@2.19.0: + resolution: {integrity: sha512-nO1xXxfh/RWNxfd/XPfbIfFk5vgLsAxUR9y5O0cHMJu/AW9U95JLXqthYHjEp+8gQ5p96K9jUp8nbVOxCdRbtw==} + dev: false + + /napi-build-utils@1.0.2: + resolution: {integrity: sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==} + dev: false + /natural-compare-lite@1.4.0: resolution: {integrity: sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==} dev: false @@ -6331,6 +6417,13 @@ packages: resolution: {integrity: sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==} dev: true + /node-abi@3.56.0: + resolution: {integrity: sha512-fZjdhDOeRcaS+rcpve7XuwHBmktS1nS1gzgghwKUQQ8nTy2FdSDr6ZT8k6YhvlJeHmmQMYiT/IH9hfco5zeW2Q==} + engines: {node: '>=10'} + dependencies: + semver: 7.5.4 + dev: false + /node-fetch@2.6.12: resolution: {integrity: sha512-C/fGU2E8ToujUivIO0H+tpQ6HWo4eEmchoPIoXtxCrVghxdKq+QOHqEZW7tuP3KlV3bC8FRMO5nMCC7Zm1VP6g==} engines: {node: 4.x || >=6.0.0} @@ -6840,6 +6933,25 @@ packages: resolution: {integrity: sha512-k+YsbhpA9e+EFfKjTCH3VW6aoKlyNYI6NYdTfDL4CIvFnvsuO84ttonmZE7rc+v23SLTH8XX+5w/Ak9v0xGY4g==} dev: true + /prebuild-install@7.1.2: + resolution: {integrity: sha512-UnNke3IQb6sgarcZIDU3gbMeTp/9SSU1DAIkil7PrqG1vZlBtY5msYccSKSHDqa3hNg436IXK+SNImReuA1wEQ==} + engines: {node: '>=10'} + hasBin: true + dependencies: + detect-libc: 2.0.2 + expand-template: 2.0.3 + github-from-package: 0.0.0 + minimist: 1.2.8 + mkdirp-classic: 0.5.3 + napi-build-utils: 1.0.2 + node-abi: 3.56.0 + pump: 3.0.0 + rc: 1.2.8 + simple-get: 4.0.1 + tar-fs: 2.1.1 + tunnel-agent: 0.6.0 + dev: false + /prelude-ls@1.1.2: resolution: {integrity: sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==} engines: {node: '>= 0.8.0'} @@ -6961,6 +7073,16 @@ packages: engines: {node: '>= 0.6'} dev: true + /rc@1.2.8: + resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} + hasBin: true + dependencies: + deep-extend: 0.6.0 + ini: 1.3.8 + minimist: 1.2.8 + strip-json-comments: 2.0.1 + dev: false + /react-is@18.2.0: resolution: {integrity: sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==} dev: true @@ -7332,6 +7454,18 @@ packages: engines: {node: '>=14'} dev: false + /simple-concat@1.0.1: + resolution: {integrity: sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==} + dev: false + + /simple-get@4.0.1: + resolution: {integrity: sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==} + dependencies: + decompress-response: 6.0.0 + once: 1.4.0 + simple-concat: 1.0.1 + dev: false + /simple-update-notifier@2.0.0: resolution: {integrity: sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==} engines: {node: '>=10'} @@ -7554,6 +7688,11 @@ packages: resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==} engines: {node: '>=12'} + /strip-json-comments@2.0.1: + resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} + engines: {node: '>=0.10.0'} + dev: false + /strip-json-comments@3.1.1: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} @@ -7626,6 +7765,26 @@ packages: tslib: 2.6.1 dev: false + /tar-fs@2.1.1: + resolution: {integrity: sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==} + dependencies: + chownr: 1.1.4 + mkdirp-classic: 0.5.3 + pump: 3.0.0 + tar-stream: 2.2.0 + dev: false + + /tar-stream@2.2.0: + resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} + engines: {node: '>=6'} + dependencies: + bl: 4.1.0 + end-of-stream: 1.4.4 + fs-constants: 1.0.0 + inherits: 2.0.4 + readable-stream: 3.6.2 + dev: false + /test-exclude@6.0.0: resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==} engines: {node: '>=8'} @@ -7714,6 +7873,14 @@ packages: resolution: {integrity: sha512-/y956gpUo9ZNCb99YjxG7OaslxZWHfCHAUUfshwqOXmxUIvqLjVO581BT+gM59+QV9tFe6/CGG53tsA1Y7RSdg==} dev: true + /tree-sitter@0.20.6: + resolution: {integrity: sha512-GxJodajVpfgb3UREzzIbtA1hyRnTxVbWVXrbC6sk4xTMH5ERMBJk9HJNq4c8jOJeUaIOmLcwg+t6mez/PDvGqg==} + requiresBuild: true + dependencies: + nan: 2.19.0 + prebuild-install: 7.1.2 + dev: false + /ts-api-utils@1.0.1(typescript@4.7.4): resolution: {integrity: sha512-lC/RGlPmwdrIBFTX59wwNzqh7aR2otPNPR/5brHZm/XKFYKsfqxihXUe9pU3JI+3vGkl+vyCoNNnPhJn3aLK1A==} engines: {node: '>=16.13.0'} @@ -7812,6 +7979,12 @@ packages: /tslib@2.6.1: resolution: {integrity: sha512-t0hLfiEKfMUoqhG+U1oid7Pva4bbDPHYfJNiB7BiIjRkj1pyC++4N3huJfqY6aRH6VTB0rvtzQwjM4K6qpfOig==} + /tunnel-agent@0.6.0: + resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} + dependencies: + safe-buffer: 5.2.1 + dev: false + /type-check@0.3.2: resolution: {integrity: sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==} engines: {node: '>= 0.8.0'} @@ -8033,6 +8206,10 @@ packages: engines: {node: '>= 8'} dev: true + /web-tree-sitter@0.20.8: + resolution: {integrity: sha512-weOVgZ3aAARgdnb220GqYuh7+rZU0Ka9k9yfKtGAzEYMa6GgiCzW9JjQRJyCJakvibQW+dfjJdihjInKuuCAUQ==} + dev: false + /webidl-conversions@3.0.1: resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} @@ -8178,6 +8355,14 @@ packages: resolution: {integrity: sha512-2eHWfjaoXgTBC2jNM1LRef62VQa0umtvRiDSk6HSzW7RvS5YtkabJrwYLLEKWBc8a5U2PTSCs+dJjUTJdlHsWQ==} engines: {node: '>= 14'} + /yamljs@0.3.0: + resolution: {integrity: sha512-C/FsVVhht4iPQYXOInoxUM/1ELSf9EsgKH34FofQOp6hwCPrW4vG4w5++TED3xRUo8gD7l0P1J1dLlDYzODsTQ==} + hasBin: true + dependencies: + argparse: 1.0.10 + glob: 7.2.3 + dev: false + /yargs-parser@18.1.3: resolution: {integrity: sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==} engines: {node: '>=6'} diff --git a/src/modules/app/app.service.ts b/src/modules/app/app.service.ts index 104a4bef..3f42625d 100644 --- a/src/modules/app/app.service.ts +++ b/src/modules/app/app.service.ts @@ -15,6 +15,19 @@ export class AppService { constructor(private config: ConfigService) {} getUpdaterDetails(currentVersion: string): UpdaterJsonResponsePayload { + import("curlconverter") + .then((curlconverter) => { + // Now you can use the imported module + const { toJsonString } = curlconverter; + const a = toJsonString(`curl `); + console.log("APPPPPPP =====> ", a); + // Use the module functionality here + }) + .catch((error) => { + // Handle any errors that occur during the import + console.error("Error importing 'curlconverter':", error); + }); + if ( this.config.get("updater.updateAvailable") === "true" && currentVersion < this.config.get("updater.appVersion") diff --git a/src/modules/common/services/helper/oapi2.transformer.ts b/src/modules/common/services/helper/oapi2.transformer.ts index 4b54b391..383b4d2e 100644 --- a/src/modules/common/services/helper/oapi2.transformer.ts +++ b/src/modules/common/services/helper/oapi2.transformer.ts @@ -3,11 +3,11 @@ import { v4 as uuidv4 } from "uuid"; import { ItemTypeEnum, SourceTypeEnum } from "../../models/collection.model"; export function createCollectionItems(openApiDocument, user) { - let collectionItems = []; + const collectionItems = []; //Get all collection items for (const [pathName, pathObject] of Object.entries(openApiDocument.paths)) { - let request = transformPath( + const request = transformPath( pathName, pathObject, openApiDocument.securityDefinitions, @@ -269,4 +269,4 @@ function getBaseUrl(openApiDocument) { } else { return "http://localhost:{{PORT}}" + basePath; } -} \ No newline at end of file +} diff --git a/tsconfig.json b/tsconfig.json index 74e5aff1..92e2a44a 100755 --- a/tsconfig.json +++ b/tsconfig.json @@ -7,7 +7,7 @@ "allowSyntheticDefaultImports": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, - "target": "es2017", + "target": "ESNext", "sourceMap": true, "outDir": "./dist", "baseUrl": "./", @@ -29,7 +29,11 @@ }, "resolveJsonModule": true }, - "exclude": [ - "node_modules" - ] -} + "ts-node": { + "esm": true, + "compilerOptions": { + "module": "nodenext" + } + }, + "exclude": ["node_modules", "./dist/**/*"] +} \ No newline at end of file From 03759cd0e4c7cb17a00ab28ede959b6b03147c57 Mon Sep 17 00:00:00 2001 From: Nayan Lakhwani Date: Thu, 7 Mar 2024 18:34:09 +0530 Subject: [PATCH 11/43] feat: curl request transform[] --- a.json | 1716 ----------------- a2.json | 83 - b.json | 83 - src/modules/app/app.controller.ts | 25 +- src/modules/app/app.service.ts | 223 ++- .../common/models/collection.rxdb.model.ts | 56 + test.js | 2 - 7 files changed, 287 insertions(+), 1901 deletions(-) delete mode 100644 a.json delete mode 100644 a2.json delete mode 100644 b.json create mode 100644 src/modules/common/models/collection.rxdb.model.ts delete mode 100644 test.js diff --git a/a.json b/a.json deleted file mode 100644 index 8e5c5f8a..00000000 --- a/a.json +++ /dev/null @@ -1,1716 +0,0 @@ -{ - "swagger": "2.0", - "info": { - "description": "This is a sample server Petstore server. You can find out more about Swagger at [http://swagger.io](http://swagger.io) or on [irc.freenode.net, #swagger](http://swagger.io/irc/). For this sample, you can use the api key `special-key` to test the authorization filters.", - "version": "1.0.6", - "title": "Swagger Petstore", - "termsOfService": "http://swagger.io/terms/", - "contact": { - "email": "apiteam@swagger.io" - }, - "license": { - "name": "Apache 2.0", - "url": "http://www.apache.org/licenses/LICENSE-2.0.html" - } - }, - "host": "petstore.swagger.io", - "basePath": "/v2", - "tags": { - "0": { - "name": "pet", - "description": "Everything about your Pets", - "externalDocs": { - "description": "Find out more", - "url": "http://swagger.io" - } - }, - "1": { - "name": "store", - "description": "Access to Petstore orders" - }, - "2": { - "name": "user", - "description": "Operations about user", - "externalDocs": { - "description": "Find out more about our store", - "url": "http://swagger.io" - } - } - }, - "schemes": { - "0": "https", - "1": "http" - }, - "paths": { - "/pet/{petId}/uploadImage": { - "post": { - "tags": { - "0": "pet" - }, - "summary": "uploads an image", - "description": "", - "operationId": "uploadFile", - "consumes": { - "0": "multipart/form-data" - }, - "produces": { - "0": "application/json" - }, - "parameters": { - "0": { - "name": "petId", - "in": "path", - "description": "ID of pet to update", - "required": true, - "type": "integer", - "format": "int64" - }, - "1": { - "name": "additionalMetadata", - "in": "formData", - "description": "Additional data to pass to server", - "required": false, - "type": "string" - }, - "2": { - "name": "file", - "in": "formData", - "description": "file to upload", - "required": false, - "type": "file" - } - }, - "responses": { - "200": { - "description": "successful operation", - "schema": { - "type": "object", - "properties": { - "code": { - "type": "integer", - "format": "int32" - }, - "type": { - "type": "string" - }, - "message": { - "type": "string" - } - } - } - } - }, - "security": { - "0": { - "petstore_auth": { - "0": "write:pets", - "1": "read:pets" - } - } - } - } - }, - "/pet": { - "post": { - "tags": { - "0": "pet" - }, - "summary": "Add a new pet to the store", - "description": "", - "operationId": "addPet", - "consumes": { - "0": "application/json", - "1": "application/xml" - }, - "produces": { - "0": "application/json", - "1": "application/xml" - }, - "parameters": { - "0": { - "in": "body", - "name": "body", - "description": "Pet object that needs to be added to the store", - "required": true, - "schema": { - "type": "object", - "required": { - "0": "name", - "1": "photoUrls" - }, - "properties": { - "id": { - "type": "integer", - "format": "int64" - }, - "category": { - "type": "object", - "properties": { - "id": { - "type": "integer", - "format": "int64" - }, - "name": { - "type": "string" - } - }, - "xml": { - "name": "Category" - } - }, - "name": { - "type": "string", - "example": "doggie" - }, - "photoUrls": { - "type": "array", - "xml": { - "wrapped": true - }, - "items": { - "type": "string", - "xml": { - "name": "photoUrl" - } - } - }, - "tags": { - "type": "array", - "xml": { - "wrapped": true - }, - "items": { - "type": "object", - "properties": { - "id": { - "type": "integer", - "format": "int64" - }, - "name": { - "type": "string" - } - }, - "xml": { - "name": "Tag" - } - } - }, - "status": { - "type": "string", - "description": "pet status in the store", - "enum": { - "0": "available", - "1": "pending", - "2": "sold" - } - } - }, - "xml": { - "name": "Pet" - } - } - } - }, - "responses": { - "405": { - "description": "Invalid input" - } - }, - "security": { - "0": { - "petstore_auth": { - "0": "write:pets", - "1": "read:pets" - } - } - } - }, - "put": { - "tags": { - "0": "pet" - }, - "summary": "Update an existing pet", - "description": "", - "operationId": "updatePet", - "consumes": { - "0": "application/json", - "1": "application/xml" - }, - "produces": { - "0": "application/json", - "1": "application/xml" - }, - "parameters": { - "0": { - "in": "body", - "name": "body", - "description": "Pet object that needs to be added to the store", - "required": true, - "schema": { - "type": "object", - "required": { - "0": "name", - "1": "photoUrls" - }, - "properties": { - "id": { - "type": "integer", - "format": "int64" - }, - "category": { - "type": "object", - "properties": { - "id": { - "type": "integer", - "format": "int64" - }, - "name": { - "type": "string" - } - }, - "xml": { - "name": "Category" - } - }, - "name": { - "type": "string", - "example": "doggie" - }, - "photoUrls": { - "type": "array", - "xml": { - "wrapped": true - }, - "items": { - "type": "string", - "xml": { - "name": "photoUrl" - } - } - }, - "tags": { - "type": "array", - "xml": { - "wrapped": true - }, - "items": { - "type": "object", - "properties": { - "id": { - "type": "integer", - "format": "int64" - }, - "name": { - "type": "string" - } - }, - "xml": { - "name": "Tag" - } - } - }, - "status": { - "type": "string", - "description": "pet status in the store", - "enum": { - "0": "available", - "1": "pending", - "2": "sold" - } - } - }, - "xml": { - "name": "Pet" - } - } - } - }, - "responses": { - "400": { - "description": "Invalid ID supplied" - }, - "404": { - "description": "Pet not found" - }, - "405": { - "description": "Validation exception" - } - }, - "security": { - "0": { - "petstore_auth": { - "0": "write:pets", - "1": "read:pets" - } - } - } - } - }, - "/pet/findByStatus": { - "get": { - "tags": { - "0": "pet" - }, - "summary": "Finds Pets by status", - "description": "Multiple status values can be provided with comma separated strings", - "operationId": "findPetsByStatus", - "produces": { - "0": "application/json", - "1": "application/xml" - }, - "parameters": { - "0": { - "name": "status", - "in": "query", - "description": "Status values that need to be considered for filter", - "required": true, - "type": "array", - "items": { - "type": "string", - "enum": { - "0": "available", - "1": "pending", - "2": "sold" - }, - "default": "available" - }, - "collectionFormat": "multi" - } - }, - "responses": { - "200": { - "description": "successful operation", - "schema": { - "type": "array", - "items": { - "type": "object", - "required": { - "0": "name", - "1": "photoUrls" - }, - "properties": { - "id": { - "type": "integer", - "format": "int64" - }, - "category": { - "type": "object", - "properties": { - "id": { - "type": "integer", - "format": "int64" - }, - "name": { - "type": "string" - } - }, - "xml": { - "name": "Category" - } - }, - "name": { - "type": "string", - "example": "doggie" - }, - "photoUrls": { - "type": "array", - "xml": { - "wrapped": true - }, - "items": { - "type": "string", - "xml": { - "name": "photoUrl" - } - } - }, - "tags": { - "type": "array", - "xml": { - "wrapped": true - }, - "items": { - "type": "object", - "properties": { - "id": { - "type": "integer", - "format": "int64" - }, - "name": { - "type": "string" - } - }, - "xml": { - "name": "Tag" - } - } - }, - "status": { - "type": "string", - "description": "pet status in the store", - "enum": { - "0": "available", - "1": "pending", - "2": "sold" - } - } - }, - "xml": { - "name": "Pet" - } - } - } - }, - "400": { - "description": "Invalid status value" - } - }, - "security": { - "0": { - "petstore_auth": { - "0": "write:pets", - "1": "read:pets" - } - } - } - } - }, - "/pet/findByTags": { - "get": { - "tags": { - "0": "pet" - }, - "summary": "Finds Pets by tags", - "description": "Multiple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing.", - "operationId": "findPetsByTags", - "produces": { - "0": "application/json", - "1": "application/xml" - }, - "parameters": { - "0": { - "name": "tags", - "in": "query", - "description": "Tags to filter by", - "required": true, - "type": "array", - "items": { - "type": "string" - }, - "collectionFormat": "multi" - } - }, - "responses": { - "200": { - "description": "successful operation", - "schema": { - "type": "array", - "items": { - "type": "object", - "required": { - "0": "name", - "1": "photoUrls" - }, - "properties": { - "id": { - "type": "integer", - "format": "int64" - }, - "category": { - "type": "object", - "properties": { - "id": { - "type": "integer", - "format": "int64" - }, - "name": { - "type": "string" - } - }, - "xml": { - "name": "Category" - } - }, - "name": { - "type": "string", - "example": "doggie" - }, - "photoUrls": { - "type": "array", - "xml": { - "wrapped": true - }, - "items": { - "type": "string", - "xml": { - "name": "photoUrl" - } - } - }, - "tags": { - "type": "array", - "xml": { - "wrapped": true - }, - "items": { - "type": "object", - "properties": { - "id": { - "type": "integer", - "format": "int64" - }, - "name": { - "type": "string" - } - }, - "xml": { - "name": "Tag" - } - } - }, - "status": { - "type": "string", - "description": "pet status in the store", - "enum": { - "0": "available", - "1": "pending", - "2": "sold" - } - } - }, - "xml": { - "name": "Pet" - } - } - } - }, - "400": { - "description": "Invalid tag value" - } - }, - "security": { - "0": { - "petstore_auth": { - "0": "write:pets", - "1": "read:pets" - } - } - }, - "deprecated": true - } - }, - "/pet/{petId}": { - "get": { - "tags": { - "0": "pet" - }, - "summary": "Find pet by ID", - "description": "Returns a single pet", - "operationId": "getPetById", - "produces": { - "0": "application/json", - "1": "application/xml" - }, - "parameters": { - "0": { - "name": "petId", - "in": "path", - "description": "ID of pet to return", - "required": true, - "type": "integer", - "format": "int64" - } - }, - "responses": { - "200": { - "description": "successful operation", - "schema": { - "type": "object", - "required": { - "0": "name", - "1": "photoUrls" - }, - "properties": { - "id": { - "type": "integer", - "format": "int64" - }, - "category": { - "type": "object", - "properties": { - "id": { - "type": "integer", - "format": "int64" - }, - "name": { - "type": "string" - } - }, - "xml": { - "name": "Category" - } - }, - "name": { - "type": "string", - "example": "doggie" - }, - "photoUrls": { - "type": "array", - "xml": { - "wrapped": true - }, - "items": { - "type": "string", - "xml": { - "name": "photoUrl" - } - } - }, - "tags": { - "type": "array", - "xml": { - "wrapped": true - }, - "items": { - "type": "object", - "properties": { - "id": { - "type": "integer", - "format": "int64" - }, - "name": { - "type": "string" - } - }, - "xml": { - "name": "Tag" - } - } - }, - "status": { - "type": "string", - "description": "pet status in the store", - "enum": { - "0": "available", - "1": "pending", - "2": "sold" - } - } - }, - "xml": { - "name": "Pet" - } - } - }, - "400": { - "description": "Invalid ID supplied" - }, - "404": { - "description": "Pet not found" - } - }, - "security": { - "0": { - "api_key": {} - } - } - }, - "post": { - "tags": { - "0": "pet" - }, - "summary": "Updates a pet in the store with form data", - "description": "", - "operationId": "updatePetWithForm", - "consumes": { - "0": "application/x-www-form-urlencoded" - }, - "produces": { - "0": "application/json", - "1": "application/xml" - }, - "parameters": { - "0": { - "name": "petId", - "in": "path", - "description": "ID of pet that needs to be updated", - "required": true, - "type": "integer", - "format": "int64" - }, - "1": { - "name": "name", - "in": "formData", - "description": "Updated name of the pet", - "required": false, - "type": "string" - }, - "2": { - "name": "status", - "in": "formData", - "description": "Updated status of the pet", - "required": false, - "type": "string" - } - }, - "responses": { - "405": { - "description": "Invalid input" - } - }, - "security": { - "0": { - "petstore_auth": { - "0": "write:pets", - "1": "read:pets" - } - } - } - }, - "delete": { - "tags": { - "0": "pet" - }, - "summary": "Deletes a pet", - "description": "", - "operationId": "deletePet", - "produces": { - "0": "application/json", - "1": "application/xml" - }, - "parameters": { - "0": { - "name": "api_key", - "in": "header", - "required": false, - "type": "string" - }, - "1": { - "name": "petId", - "in": "path", - "description": "Pet id to delete", - "required": true, - "type": "integer", - "format": "int64" - } - }, - "responses": { - "400": { - "description": "Invalid ID supplied" - }, - "404": { - "description": "Pet not found" - } - }, - "security": { - "0": { - "petstore_auth": { - "0": "write:pets", - "1": "read:pets" - } - } - } - } - }, - "/store/order": { - "post": { - "tags": { - "0": "store" - }, - "summary": "Place an order for a pet", - "description": "", - "operationId": "placeOrder", - "consumes": { - "0": "application/json" - }, - "produces": { - "0": "application/json", - "1": "application/xml" - }, - "parameters": { - "0": { - "in": "body", - "name": "body", - "description": "order placed for purchasing the pet", - "required": true, - "schema": { - "type": "object", - "properties": { - "id": { - "type": "integer", - "format": "int64" - }, - "petId": { - "type": "integer", - "format": "int64" - }, - "quantity": { - "type": "integer", - "format": "int32" - }, - "shipDate": { - "type": "string", - "format": "date-time" - }, - "status": { - "type": "string", - "description": "Order Status", - "enum": { - "0": "placed", - "1": "approved", - "2": "delivered" - } - }, - "complete": { - "type": "boolean" - } - }, - "xml": { - "name": "Order" - } - } - } - }, - "responses": { - "200": { - "description": "successful operation", - "schema": { - "type": "object", - "properties": { - "id": { - "type": "integer", - "format": "int64" - }, - "petId": { - "type": "integer", - "format": "int64" - }, - "quantity": { - "type": "integer", - "format": "int32" - }, - "shipDate": { - "type": "string", - "format": "date-time" - }, - "status": { - "type": "string", - "description": "Order Status", - "enum": { - "0": "placed", - "1": "approved", - "2": "delivered" - } - }, - "complete": { - "type": "boolean" - } - }, - "xml": { - "name": "Order" - } - } - }, - "400": { - "description": "Invalid Order" - } - } - } - }, - "/store/order/{orderId}": { - "get": { - "tags": { - "0": "store" - }, - "summary": "Find purchase order by ID", - "description": "For valid response try integer IDs with value >= 1 and <= 10. Other values will generated exceptions", - "operationId": "getOrderById", - "produces": { - "0": "application/json", - "1": "application/xml" - }, - "parameters": { - "0": { - "name": "orderId", - "in": "path", - "description": "ID of pet that needs to be fetched", - "required": true, - "type": "integer", - "maximum": 10, - "minimum": 1, - "format": "int64" - } - }, - "responses": { - "200": { - "description": "successful operation", - "schema": { - "type": "object", - "properties": { - "id": { - "type": "integer", - "format": "int64" - }, - "petId": { - "type": "integer", - "format": "int64" - }, - "quantity": { - "type": "integer", - "format": "int32" - }, - "shipDate": { - "type": "string", - "format": "date-time" - }, - "status": { - "type": "string", - "description": "Order Status", - "enum": { - "0": "placed", - "1": "approved", - "2": "delivered" - } - }, - "complete": { - "type": "boolean" - } - }, - "xml": { - "name": "Order" - } - } - }, - "400": { - "description": "Invalid ID supplied" - }, - "404": { - "description": "Order not found" - } - } - }, - "delete": { - "tags": { - "0": "store" - }, - "summary": "Delete purchase order by ID", - "description": "For valid response try integer IDs with positive integer value. Negative or non-integer values will generate API errors", - "operationId": "deleteOrder", - "produces": { - "0": "application/json", - "1": "application/xml" - }, - "parameters": { - "0": { - "name": "orderId", - "in": "path", - "description": "ID of the order that needs to be deleted", - "required": true, - "type": "integer", - "minimum": 1, - "format": "int64" - } - }, - "responses": { - "400": { - "description": "Invalid ID supplied" - }, - "404": { - "description": "Order not found" - } - } - } - }, - "/store/inventory": { - "get": { - "tags": { - "0": "store" - }, - "summary": "Returns pet inventories by status", - "description": "Returns a map of status codes to quantities", - "operationId": "getInventory", - "produces": { - "0": "application/json" - }, - "parameters": {}, - "responses": { - "200": { - "description": "successful operation", - "schema": { - "type": "object", - "additionalProperties": { - "type": "integer", - "format": "int32" - } - } - } - }, - "security": { - "0": { - "api_key": {} - } - } - } - }, - "/user/createWithArray": { - "post": { - "tags": { - "0": "user" - }, - "summary": "Creates list of users with given input array", - "description": "", - "operationId": "createUsersWithArrayInput", - "consumes": { - "0": "application/json" - }, - "produces": { - "0": "application/json", - "1": "application/xml" - }, - "parameters": { - "0": { - "in": "body", - "name": "body", - "description": "List of user object", - "required": true, - "schema": { - "type": "array", - "items": { - "type": "object", - "properties": { - "id": { - "type": "integer", - "format": "int64" - }, - "username": { - "type": "string" - }, - "firstName": { - "type": "string" - }, - "lastName": { - "type": "string" - }, - "email": { - "type": "string" - }, - "password": { - "type": "string" - }, - "phone": { - "type": "string" - }, - "userStatus": { - "type": "integer", - "format": "int32", - "description": "User Status" - } - }, - "xml": { - "name": "User" - } - } - } - } - }, - "responses": { - "default": { - "description": "successful operation" - } - } - } - }, - "/user/createWithList": { - "post": { - "tags": { - "0": "user" - }, - "summary": "Creates list of users with given input array", - "description": "", - "operationId": "createUsersWithListInput", - "consumes": { - "0": "application/json" - }, - "produces": { - "0": "application/json", - "1": "application/xml" - }, - "parameters": { - "0": { - "in": "body", - "name": "body", - "description": "List of user object", - "required": true, - "schema": { - "type": "array", - "items": { - "type": "object", - "properties": { - "id": { - "type": "integer", - "format": "int64" - }, - "username": { - "type": "string" - }, - "firstName": { - "type": "string" - }, - "lastName": { - "type": "string" - }, - "email": { - "type": "string" - }, - "password": { - "type": "string" - }, - "phone": { - "type": "string" - }, - "userStatus": { - "type": "integer", - "format": "int32", - "description": "User Status" - } - }, - "xml": { - "name": "User" - } - } - } - } - }, - "responses": { - "default": { - "description": "successful operation" - } - } - } - }, - "/user/{username}": { - "get": { - "tags": { - "0": "user" - }, - "summary": "Get user by user name", - "description": "", - "operationId": "getUserByName", - "produces": { - "0": "application/json", - "1": "application/xml" - }, - "parameters": { - "0": { - "name": "username", - "in": "path", - "description": "The name that needs to be fetched. Use user1 for testing. ", - "required": true, - "type": "string" - } - }, - "responses": { - "200": { - "description": "successful operation", - "schema": { - "type": "object", - "properties": { - "id": { - "type": "integer", - "format": "int64" - }, - "username": { - "type": "string" - }, - "firstName": { - "type": "string" - }, - "lastName": { - "type": "string" - }, - "email": { - "type": "string" - }, - "password": { - "type": "string" - }, - "phone": { - "type": "string" - }, - "userStatus": { - "type": "integer", - "format": "int32", - "description": "User Status" - } - }, - "xml": { - "name": "User" - } - } - }, - "400": { - "description": "Invalid username supplied" - }, - "404": { - "description": "User not found" - } - } - }, - "put": { - "tags": { - "0": "user" - }, - "summary": "Updated user", - "description": "This can only be done by the logged in user.", - "operationId": "updateUser", - "consumes": { - "0": "application/json" - }, - "produces": { - "0": "application/json", - "1": "application/xml" - }, - "parameters": { - "0": { - "name": "username", - "in": "path", - "description": "name that need to be updated", - "required": true, - "type": "string" - }, - "1": { - "in": "body", - "name": "body", - "description": "Updated user object", - "required": true, - "schema": { - "type": "object", - "properties": { - "id": { - "type": "integer", - "format": "int64" - }, - "username": { - "type": "string" - }, - "firstName": { - "type": "string" - }, - "lastName": { - "type": "string" - }, - "email": { - "type": "string" - }, - "password": { - "type": "string" - }, - "phone": { - "type": "string" - }, - "userStatus": { - "type": "integer", - "format": "int32", - "description": "User Status" - } - }, - "xml": { - "name": "User" - } - } - } - }, - "responses": { - "400": { - "description": "Invalid user supplied" - }, - "404": { - "description": "User not found" - } - } - }, - "delete": { - "tags": { - "0": "user" - }, - "summary": "Delete user", - "description": "This can only be done by the logged in user.", - "operationId": "deleteUser", - "produces": { - "0": "application/json", - "1": "application/xml" - }, - "parameters": { - "0": { - "name": "username", - "in": "path", - "description": "The name that needs to be deleted", - "required": true, - "type": "string" - } - }, - "responses": { - "400": { - "description": "Invalid username supplied" - }, - "404": { - "description": "User not found" - } - } - } - }, - "/user/login": { - "get": { - "tags": { - "0": "user" - }, - "summary": "Logs user into the system", - "description": "", - "operationId": "loginUser", - "produces": { - "0": "application/json", - "1": "application/xml" - }, - "parameters": { - "0": { - "name": "username", - "in": "query", - "description": "The user name for login", - "required": true, - "type": "string" - }, - "1": { - "name": "password", - "in": "query", - "description": "The password for login in clear text", - "required": true, - "type": "string" - } - }, - "responses": { - "200": { - "description": "successful operation", - "headers": { - "X-Expires-After": { - "type": "string", - "format": "date-time", - "description": "date in UTC when token expires" - }, - "X-Rate-Limit": { - "type": "integer", - "format": "int32", - "description": "calls per hour allowed by the user" - } - }, - "schema": { - "type": "string" - } - }, - "400": { - "description": "Invalid username/password supplied" - } - } - } - }, - "/user/logout": { - "get": { - "tags": { - "0": "user" - }, - "summary": "Logs out current logged in user session", - "description": "", - "operationId": "logoutUser", - "produces": { - "0": "application/json", - "1": "application/xml" - }, - "parameters": {}, - "responses": { - "default": { - "description": "successful operation" - } - } - } - }, - "/user": { - "post": { - "tags": { - "0": "user" - }, - "summary": "Create user", - "description": "This can only be done by the logged in user.", - "operationId": "createUser", - "consumes": { - "0": "application/json" - }, - "produces": { - "0": "application/json", - "1": "application/xml" - }, - "parameters": { - "0": { - "in": "body", - "name": "body", - "description": "Created user object", - "required": true, - "schema": { - "type": "object", - "properties": { - "id": { - "type": "integer", - "format": "int64" - }, - "username": { - "type": "string" - }, - "firstName": { - "type": "string" - }, - "lastName": { - "type": "string" - }, - "email": { - "type": "string" - }, - "password": { - "type": "string" - }, - "phone": { - "type": "string" - }, - "userStatus": { - "type": "integer", - "format": "int32", - "description": "User Status" - } - }, - "xml": { - "name": "User" - } - } - } - }, - "responses": { - "default": { - "description": "successful operation" - } - } - } - } - }, - "securityDefinitions": { - "api_key": { - "type": "apiKey", - "name": "api_key", - "in": "header" - }, - "petstore_auth": { - "type": "oauth2", - "authorizationUrl": "https://petstore.swagger.io/oauth/authorize", - "flow": "implicit", - "scopes": { - "read:pets": "read your pets", - "write:pets": "modify pets in your account" - } - } - }, - "definitions": { - "ApiResponse": { - "type": "object", - "properties": { - "code": { - "type": "integer", - "format": "int32" - }, - "type": { - "type": "string" - }, - "message": { - "type": "string" - } - } - }, - "Category": { - "type": "object", - "properties": { - "id": { - "type": "integer", - "format": "int64" - }, - "name": { - "type": "string" - } - }, - "xml": { - "name": "Category" - } - }, - "Pet": { - "type": "object", - "required": { - "0": "name", - "1": "photoUrls" - }, - "properties": { - "id": { - "type": "integer", - "format": "int64" - }, - "category": { - "type": "object", - "properties": { - "id": { - "type": "integer", - "format": "int64" - }, - "name": { - "type": "string" - } - }, - "xml": { - "name": "Category" - } - }, - "name": { - "type": "string", - "example": "doggie" - }, - "photoUrls": { - "type": "array", - "xml": { - "wrapped": true - }, - "items": { - "type": "string", - "xml": { - "name": "photoUrl" - } - } - }, - "tags": { - "type": "array", - "xml": { - "wrapped": true - }, - "items": { - "type": "object", - "properties": { - "id": { - "type": "integer", - "format": "int64" - }, - "name": { - "type": "string" - } - }, - "xml": { - "name": "Tag" - } - } - }, - "status": { - "type": "string", - "description": "pet status in the store", - "enum": { - "0": "available", - "1": "pending", - "2": "sold" - } - } - }, - "xml": { - "name": "Pet" - } - }, - "Tag": { - "type": "object", - "properties": { - "id": { - "type": "integer", - "format": "int64" - }, - "name": { - "type": "string" - } - }, - "xml": { - "name": "Tag" - } - }, - "Order": { - "type": "object", - "properties": { - "id": { - "type": "integer", - "format": "int64" - }, - "petId": { - "type": "integer", - "format": "int64" - }, - "quantity": { - "type": "integer", - "format": "int32" - }, - "shipDate": { - "type": "string", - "format": "date-time" - }, - "status": { - "type": "string", - "description": "Order Status", - "enum": { - "0": "placed", - "1": "approved", - "2": "delivered" - } - }, - "complete": { - "type": "boolean" - } - }, - "xml": { - "name": "Order" - } - }, - "User": { - "type": "object", - "properties": { - "id": { - "type": "integer", - "format": "int64" - }, - "username": { - "type": "string" - }, - "firstName": { - "type": "string" - }, - "lastName": { - "type": "string" - }, - "email": { - "type": "string" - }, - "password": { - "type": "string" - }, - "phone": { - "type": "string" - }, - "userStatus": { - "type": "integer", - "format": "int32", - "description": "User Status" - } - }, - "xml": { - "name": "User" - } - } - }, - "externalDocs": { - "description": "Find out more about Swagger", - "url": "http://swagger.io" - } -} diff --git a/a2.json b/a2.json deleted file mode 100644 index 6c97031e..00000000 --- a/a2.json +++ /dev/null @@ -1,83 +0,0 @@ -{ - "id": "0bad133f-1cfd-47c2-b33c-94279a413397", - "name": "Auth", - "description": "", - "type": "REQUEST", - "request": { - "method": "GET", - "url": "?queryParamKey=queryParamValue", - "body": { - "raw": "{\n\t\"jsonKey\": \"jsonValue\"\n}", - "urlencoded": [ - { - "key": "", - "value": "", - "checked": false - } - ], - "formdata": { - "text": [ - { - "key": "formDataTextKey", - "value": "formDataTextValue", - "checked": true - }, - { - "key": "", - "value": "", - "checked": false - } - ], - "file": [ - { - "key": "formDataFileKey", - "value": "/Users/nayanlakhwani/Downloads/giphy.gif", - "checked": true, - "base": "#@#/Users/nayanlakhwani/Downloads/giphy.gif" - }, - { - "key": "", - "value": "", - "checked": false - } - ] - } - }, - "headers": [ - { - "key": "headerKey", - "value": "headerValue", - "checked": true - }, - { - "key": "", - "value": "", - "checked": false - } - ], - "queryParams": [ - { - "key": "queryParamKey", - "value": "queryParamValue", - "checked": true - }, - { - "key": "", - "value": "", - "checked": false - } - ], - "auth": { - "bearerToken": "bearerToken", - "basicAuth": { - "username": "basicAuthUsername", - "password": "basicAuthPassword" - }, - "apiKey": { - "authKey": "apiKey", - "authValue": "apikeyValue", - "addTo": "Header" - } - } - } -} diff --git a/b.json b/b.json deleted file mode 100644 index 6c97031e..00000000 --- a/b.json +++ /dev/null @@ -1,83 +0,0 @@ -{ - "id": "0bad133f-1cfd-47c2-b33c-94279a413397", - "name": "Auth", - "description": "", - "type": "REQUEST", - "request": { - "method": "GET", - "url": "?queryParamKey=queryParamValue", - "body": { - "raw": "{\n\t\"jsonKey\": \"jsonValue\"\n}", - "urlencoded": [ - { - "key": "", - "value": "", - "checked": false - } - ], - "formdata": { - "text": [ - { - "key": "formDataTextKey", - "value": "formDataTextValue", - "checked": true - }, - { - "key": "", - "value": "", - "checked": false - } - ], - "file": [ - { - "key": "formDataFileKey", - "value": "/Users/nayanlakhwani/Downloads/giphy.gif", - "checked": true, - "base": "#@#/Users/nayanlakhwani/Downloads/giphy.gif" - }, - { - "key": "", - "value": "", - "checked": false - } - ] - } - }, - "headers": [ - { - "key": "headerKey", - "value": "headerValue", - "checked": true - }, - { - "key": "", - "value": "", - "checked": false - } - ], - "queryParams": [ - { - "key": "queryParamKey", - "value": "queryParamValue", - "checked": true - }, - { - "key": "", - "value": "", - "checked": false - } - ], - "auth": { - "bearerToken": "bearerToken", - "basicAuth": { - "username": "basicAuthUsername", - "password": "basicAuthPassword" - }, - "apiKey": { - "authKey": "apiKey", - "authValue": "apikeyValue", - "addTo": "Header" - } - } - } -} diff --git a/src/modules/app/app.controller.ts b/src/modules/app/app.controller.ts index 5358e9d1..b8a261c8 100644 --- a/src/modules/app/app.controller.ts +++ b/src/modules/app/app.controller.ts @@ -1,6 +1,6 @@ -import { Controller, Get, Param, Res } from "@nestjs/common"; -import { ApiOperation, ApiResponse } from "@nestjs/swagger"; -import { FastifyReply } from "fastify"; +import { Controller, Get, Param, Post, Req, Res } from "@nestjs/common"; +import { ApiHeader, ApiOperation, ApiResponse } from "@nestjs/swagger"; +import { FastifyReply, FastifyRequest } from "fastify"; import { AppService } from "./app.service"; /** @@ -29,4 +29,23 @@ export class AppController { ); return res.status(statusCode).send(data); } + + @Post("curl") + @ApiHeader({ + name: "curl", + description: "Pass in the curl command.", + allowEmptyValue: false, + }) + @ApiOperation({ + summary: "Parse Curl", + description: "Parses the provided curl into Sparrow api request schema", + }) + @ApiResponse({ + status: 200, + description: "Curl parsed successfully", + }) + async parseCurl(@Res() res: FastifyReply, @Req() req: FastifyRequest) { + const parsedRequestData = await this.appService.parseCurl(req); + return res.status(200).send(parsedRequestData); + } } diff --git a/src/modules/app/app.service.ts b/src/modules/app/app.service.ts index 3f42625d..210f1b91 100644 --- a/src/modules/app/app.service.ts +++ b/src/modules/app/app.service.ts @@ -1,33 +1,34 @@ -import { Injectable } from "@nestjs/common"; +import { BadRequestException, Injectable } from "@nestjs/common"; import { ConfigService } from "@nestjs/config"; import { HttpStatusCode } from "../common/enum/httpStatusCode.enum"; import { UpdaterJsonResponsePayload } from "./payloads/updaterJson.payload"; +import { FastifyRequest } from "fastify"; +import { ItemTypeEnum } from "../common/models/collection.model"; +import { + AddTo, + TransformedRequest, +} from "../common/models/collection.rxdb.model"; /** * Application Service */ @Injectable() export class AppService { + private curlconverterPromise: any = null; /** * Constructor * @param {ConfigService} config configuration service */ constructor(private config: ConfigService) {} - getUpdaterDetails(currentVersion: string): UpdaterJsonResponsePayload { - import("curlconverter") - .then((curlconverter) => { - // Now you can use the imported module - const { toJsonString } = curlconverter; - const a = toJsonString(`curl `); - console.log("APPPPPPP =====> ", a); - // Use the module functionality here - }) - .catch((error) => { - // Handle any errors that occur during the import - console.error("Error importing 'curlconverter':", error); - }); + importCurlConverter() { + if (!this.curlconverterPromise) { + this.curlconverterPromise = import("curlconverter"); + } + return this.curlconverterPromise; + } + getUpdaterDetails(currentVersion: string): UpdaterJsonResponsePayload { if ( this.config.get("updater.updateAvailable") === "true" && currentVersion < this.config.get("updater.appVersion") @@ -59,4 +60,198 @@ export class AppService { data: null, }; } + + async parseCurl(req: FastifyRequest): Promise { + try { + const curlconverter = await this.importCurlConverter(); + const { toJsonString } = curlconverter; + const curl = req.headers?.curl; + if (!curl || !curl.length) { + throw new Error(); + } + return transformRequest(JSON.parse(toJsonString(curl))); + } catch (error) { + console.error("Error parsing :", error); + throw new BadRequestException("Invalid Curl"); + } + } +} + +function transformRequest(requestObject: any): TransformedRequest { + const transformedObject: TransformedRequest = { + name: requestObject.url || "", + description: "", + type: ItemTypeEnum.REQUEST, + request: { + method: requestObject.method.toUpperCase(), + url: "", + body: { + raw: "", + urlencoded: [], + formdata: { + text: [], + file: [], + }, + }, + headers: [], + queryParams: [], + auth: {}, + }, + }; + + // Handle URL with query parameters + if (requestObject.queries) { + const queryParams = []; + for (const [key, value] of Object.entries(requestObject.queries)) { + queryParams.push({ key, value, checked: true }); + if ( + key.toLowerCase() === "api-key" || + key.toLowerCase() === "x-api-key" + ) { + transformedObject.request.auth.apiKey = { + authKey: key, + authValue: value, + addTo: AddTo.QueryParameter, + }; + } + } + transformedObject.request.url = requestObject.raw_url; + } + + // Handle request body based on Content-Type + if (requestObject.data) { + const contentType = + requestObject.headers["content-type"] || + requestObject.headers["Content-Type"] || + ""; + //"multipart/form-data; boundary=----WebKitFormBoundaryhBHci3a7BLGRCFlH" + if (contentType.startsWith("multipart/form-data")) { + const boundary = contentType.split("boundary=")[1]; + const formDataParts = requestObject.data.split(`--${boundary}\r\n`); + formDataParts.shift(); // Remove the first boundary part + + for (const part of formDataParts) { + const lines = part.trim().split("\r\n"); + const disposition = lines[0]; // Content-Disposition line + if (disposition.includes('name="_method"')) { + // Ignore the _method part (can be handled elsewhere if needed) + continue; + } + const key = disposition.split('name="')[1].split('"')[0]; + let value = ""; + + if (lines.length > 2) { + value = lines.slice(2).join("\r\n").trim(); // Extract value from part content + } + + if (value.includes(boundary)) { + value = ""; + } + + if (disposition.includes('Content-Disposition: form-data; name="')) { + transformedObject.request.body.formdata.text.push({ + key, + value, + checked: true, + }); + } else if ( + disposition.includes('Content-Disposition: form-data; name="file"') && + value.startsWith("/") + ) { + transformedObject.request.body.formdata.file.push({ + key, + value, + checked: true, + base: `#@#${value}`, + }); + } + } + } else if ( + contentType.includes("application/json") || + contentType.includes("text/html") || + contentType.includes("text/xml") + ) { + try { + transformedObject.request.body.raw = JSON.stringify( + requestObject.data, + null, + 2, + ); // Pretty-printed JSON + } catch (error) { + console.warn("Error parsing request body JSON:", error); + transformedObject.request.body.raw = requestObject.data; // Fallback to raw data if parsing fails + } + } else if (contentType.includes("application/x-www-form-urlencoded")) { + // Assuming data is already URL-encoded key-value pairs + for (const [key, value] of new URLSearchParams(requestObject.data)) { + transformedObject.request.body.urlencoded.push({ + key, + value, + checked: true, + }); + } + } else { + // Handle other content types (consider adding warnings or handling as raw data) + console.warn(`Unsupported Content-Type: ${contentType}`); + transformedObject.request.body.raw = requestObject.data; + } + } + + // Handle files from request object (unchanged from previous version) + if (requestObject.files) { + for (const [key, filename] of Object.entries(requestObject.files)) { + transformedObject.request.body.formdata.file.push({ + key, + value: filename, + checked: true, + base: `#@#${filename}`, + }); + } + } + + // Handle headers and populate auth details (unchanged from previous version) + if (requestObject.headers) { + for (const [key, value] of Object.entries(requestObject.headers)) { + transformedObject.request.headers.push({ key, value, checked: true }); + + // Check for Bearer token + if ( + key.toLowerCase() === "authorization" && + typeof value === "string" && + (value.startsWith("bearer ") || value.startsWith("Bearer ")) + ) { + transformedObject.request.auth.bearerToken = value.slice(7).trim(); + } + + // Check for API key + if ( + key.toLowerCase() === "api-key" || + key.toLowerCase() === "x-api-key" + ) { + transformedObject.request.auth.apiKey = { + authKey: key, + authValue: value, + addTo: AddTo.Header, + }; + } + + // Check for Basic Auth (assuming encoded username:password) + if ( + key.toLowerCase() === "authorization" && + typeof value === "string" && + (value.startsWith("basic ") || value.startsWith("Basic ")) + ) { + const decodedValue = Buffer.from(value.slice(6), "base64").toString( + "utf8", + ); + const [username, password] = decodedValue.split(":"); + transformedObject.request.auth.basicAuth = { + username, + password, + }; + } + } + } + + return transformedObject; } diff --git a/src/modules/common/models/collection.rxdb.model.ts b/src/modules/common/models/collection.rxdb.model.ts new file mode 100644 index 00000000..5d106042 --- /dev/null +++ b/src/modules/common/models/collection.rxdb.model.ts @@ -0,0 +1,56 @@ +import { BodyModeEnum, ItemTypeEnum } from "./collection.model"; + +export enum AddTo { + Header = "Header", + QueryParameter = "QueryParameter", +} + +export interface TransformedRequest { + name: string; + description?: string; + type: ItemTypeEnum; + request: { + selectedRequestBodyType?: BodyModeEnum; + method: string; + url: string; + body: { + raw?: string; + urlencoded?: KeyValue[]; + formdata?: FormData; + }; + headers?: KeyValue[]; + queryParams?: KeyValue[]; + auth?: Auth; + }; +} + +interface FormData { + text: KeyValue[]; + file: FormDataFileEntry[]; +} + +interface KeyValue { + key: string; + value: string | unknown; + checked: boolean; +} + +interface FormDataFileEntry { + key: string; + value: string | unknown; + checked: boolean; + base: string; +} + +interface Auth { + bearerToken?: string; + basicAuth?: { + username: string; + password: string; + }; + apiKey?: { + authKey: string; + authValue: string | unknown; + addTo: AddTo; + }; +} diff --git a/test.js b/test.js deleted file mode 100644 index 24f5e47c..00000000 --- a/test.js +++ /dev/null @@ -1,2 +0,0 @@ - -console.log(util.inspect(transformedPaths, false, null, true)); From 5d44b7697e58cd01359740ec626d1804dd64c008 Mon Sep 17 00:00:00 2001 From: Nayan Lakhwani Date: Thu, 7 Mar 2024 19:58:23 +0530 Subject: [PATCH 12/43] feat: add type checking[] --- src/modules/app/app.service.ts | 11 +++++- .../common/models/collection.rxdb.model.ts | 16 ++++++++- src/modules/common/models/openapi20.model.ts | 2 +- .../services/helper/oapi.transformer.ts | 36 ++++++++++++------- 4 files changed, 50 insertions(+), 15 deletions(-) diff --git a/src/modules/app/app.service.ts b/src/modules/app/app.service.ts index 42ca6339..ee68d2a7 100644 --- a/src/modules/app/app.service.ts +++ b/src/modules/app/app.service.ts @@ -7,6 +7,7 @@ import { AuthModeEnum, BodyModeEnum, ItemTypeEnum, + SourceTypeEnum, } from "../common/models/collection.model"; import { AddTo, @@ -81,11 +82,15 @@ export class AppService { } } -function transformRequest(requestObject: any): TransformedRequest { +async function transformRequest( + requestObject: any, +): Promise { + const user = await this.contextService.get("user"); const transformedObject: TransformedRequest = { name: requestObject.url || "", description: "", type: ItemTypeEnum.REQUEST, + source: SourceTypeEnum.USER, request: { method: requestObject.method.toUpperCase(), url: "", @@ -103,6 +108,10 @@ function transformRequest(requestObject: any): TransformedRequest { selectedRequestBodyType: BodyModeEnum["none"], selectedRequestAuthType: AuthModeEnum["No Auth"], }, + createdBy: user.name, + updatedBy: user.name, + createdAt: new Date(), + updatedAt: new Date(), }; // Handle URL with query parameters diff --git a/src/modules/common/models/collection.rxdb.model.ts b/src/modules/common/models/collection.rxdb.model.ts index 9bdbed9c..5aff711d 100644 --- a/src/modules/common/models/collection.rxdb.model.ts +++ b/src/modules/common/models/collection.rxdb.model.ts @@ -1,4 +1,9 @@ -import { AuthModeEnum, BodyModeEnum, ItemTypeEnum } from "./collection.model"; +import { + AuthModeEnum, + BodyModeEnum, + ItemTypeEnum, + SourceTypeEnum, +} from "./collection.model"; export enum AddTo { Header = "Header", @@ -6,6 +11,11 @@ export enum AddTo { } export interface TransformedRequest { + id?: string; + tag?: string; + operationId?: string; + source: SourceTypeEnum; + isDeleted?: boolean; name: string; description?: string; type: ItemTypeEnum; @@ -23,6 +33,10 @@ export interface TransformedRequest { queryParams?: KeyValue[]; auth?: Auth; }; + createdAt: Date; + updatedAt: Date; + createdBy: string; + updatedBy: string; } interface FormData { diff --git a/src/modules/common/models/openapi20.model.ts b/src/modules/common/models/openapi20.model.ts index 6b5abfd0..c83cb63b 100644 --- a/src/modules/common/models/openapi20.model.ts +++ b/src/modules/common/models/openapi20.model.ts @@ -36,7 +36,7 @@ interface LicenseObject { url?: string; } -interface PathsObject { +export interface PathsObject { [path: string]: PathItemObject; } diff --git a/src/modules/common/services/helper/oapi.transformer.ts b/src/modules/common/services/helper/oapi.transformer.ts index 33d9133d..992fe500 100644 --- a/src/modules/common/services/helper/oapi.transformer.ts +++ b/src/modules/common/services/helper/oapi.transformer.ts @@ -1,9 +1,15 @@ // @ts-nocheck import { v4 as uuidv4 } from "uuid"; import { ItemTypeEnum, SourceTypeEnum } from "../../models/collection.model"; +import { OpenAPI20, PathsObject } from "../../models/openapi20.model"; +import { OpenAPI303 } from "../../models/openapi303.model"; +import { TransformedRequest } from "../../models/collection.rxdb.model"; -export function createCollectionItems(openApiDocument, user) { - const collectionItems = []; +export function createCollectionItems( + openApiDocument: OpenAPI20 | OpenAPI303, + user, +) { + const collectionItems: TransformedRequest[] = []; if (openApiDocument.components) { for (const [pathName, pathObject] of Object.entries( @@ -87,16 +93,22 @@ export function createCollectionItems(openApiDocument, user) { return folderMap; } -function transformPath(pathName: string, pathObject: any, security: any) { +function transformPath( + pathName: string, + pathObject: PathsObject, + security: any, +) { const transformedObject = {} as any; const method = Object.keys(pathObject)[0].toUpperCase(); // Assuming the first key is the HTTP method - pathObject = Object.values(pathObject)[0]; - transformedObject.tag = pathObject.tags ? pathObject.tags[0] : "default"; + const pathItemObject = Object.values(pathObject)[0]; + transformedObject.tag = pathItemObject.tags + ? pathItemObject.tags[0] + : "default"; transformedObject.name = pathName; transformedObject.description = - pathObject.summary || pathObject.description || ""; // Use summary or description if available - transformedObject.operationId = pathObject.operationId; + pathItemObject.summary || pathItemObject.description || ""; // Use summary or description if available + transformedObject.operationId = pathItemObject.operationId; transformedObject.request = {}; @@ -129,8 +141,8 @@ function transformPath(pathName: string, pathObject: any, security: any) { // Handle request body based on schema transformedObject.request.body = {}; let consumes: any = null; - if (pathObject.consumes) { - consumes = Object.values(pathObject.consumes) || []; + if (pathItemObject.consumes) { + consumes = Object.values(pathItemObject.consumes) || []; if (consumes.includes("application/json")) { transformedObject.request.body.raw = ""; transformedObject.request.selectedRequestBodyType = "application/json"; @@ -189,7 +201,7 @@ function transformPath(pathName: string, pathObject: any, security: any) { } // Parse request body parameters - const parameters = pathObject.parameters || []; + const parameters = pathItemObject.parameters || []; for (const param of Object.values(parameters)) { const paramIn = param.in; const paramName = param.name; @@ -461,7 +473,7 @@ function transformPathV3(pathName: string, pathObject: any, security: any) { return transformedObject; } -function getExampleValue(exampleType) { +function getExampleValue(exampleType: string) { switch (exampleType) { case "string": return ""; // Or a default string value @@ -495,7 +507,7 @@ function buildExampleValue(property) { } } -function getBaseUrl(openApiDocument) { +function getBaseUrl(openApiDocument: OpenAPI20 | OpenAPI303) { const basePath = openApiDocument.basePath ? openApiDocument.basePath : ""; if (openApiDocument.host) { return "https://" + openApiDocument.host + basePath; From 3fa0bd579437c74cfccf8062b59bd8b788b31915 Mon Sep 17 00:00:00 2001 From: Nayan Lakhwani Date: Fri, 8 Mar 2024 11:33:09 +0530 Subject: [PATCH 13/43] chore: add type check[] --- src/modules/common/models/openapi20.model.ts | 16 +- src/modules/common/models/openapi303.model.ts | 49 +-- .../services/helper/oapi2.transformer.ts | 282 ++++++++++++++++++ ...pi.transformer.ts => oapi3.transformer.ts} | 252 ++-------------- src/modules/common/services/parser.service.ts | 12 +- 5 files changed, 359 insertions(+), 252 deletions(-) create mode 100644 src/modules/common/services/helper/oapi2.transformer.ts rename src/modules/common/services/helper/{oapi.transformer.ts => oapi3.transformer.ts} (54%) diff --git a/src/modules/common/models/openapi20.model.ts b/src/modules/common/models/openapi20.model.ts index c83cb63b..20e4717c 100644 --- a/src/modules/common/models/openapi20.model.ts +++ b/src/modules/common/models/openapi20.model.ts @@ -40,7 +40,7 @@ export interface PathsObject { [path: string]: PathItemObject; } -interface PathItemObject { +export interface PathItemObject { $ref?: string; get?: OperationObject; put?: OperationObject; @@ -52,7 +52,7 @@ interface PathItemObject { parameters?: (ParameterObject | ReferenceObject)[]; } -interface OperationObject { +export interface OperationObject { tags?: string[]; summary?: string; description?: string; @@ -67,7 +67,9 @@ interface OperationObject { security?: SecurityRequirementObject[]; } -interface ParameterObject { +export interface ParameterObject { + type: string; + example: string; name: string; in: string; description?: string; @@ -93,7 +95,7 @@ interface SchemaObject { format?: string; items?: SchemaObject; properties?: { - [name: string]: SchemaObject; + [name: string]: SchemaRefObject; }; required?: string[]; enum?: any[]; @@ -142,3 +144,9 @@ interface ExternalDocumentationObject { description?: string; url: string; } + +export interface SchemaRefObject extends SchemaObject, ReferenceObject { + example: any; +} + +export interface ParameterRefObject extends ParameterObject, ReferenceObject {} diff --git a/src/modules/common/models/openapi303.model.ts b/src/modules/common/models/openapi303.model.ts index fafe69eb..1f583f9e 100644 --- a/src/modules/common/models/openapi303.model.ts +++ b/src/modules/common/models/openapi303.model.ts @@ -69,7 +69,7 @@ interface ServerVariableObject { description?: string; } -interface PathItemObject { +export interface PathItemObject { $ref?: string; summary?: string; description?: string; @@ -82,17 +82,17 @@ interface PathItemObject { patch?: OperationObject; trace?: OperationObject; servers?: ServerObject[]; - parameters?: (ParameterObject | ReferenceObject)[]; + parameters?: ParameterRefObject[]; } -interface OperationObject { +export interface OperationObject { tags?: string[]; summary?: string; description?: string; externalDocs?: ExternalDocumentationObject; operationId?: string; - parameters?: (ParameterObject | ReferenceObject)[]; - requestBody?: RequestBodyObject | ReferenceObject; + parameters?: ParameterRefObject[]; + requestBody?: RequestRefObject; responses: { [statusCode: string]: ResponseObject | ReferenceObject; }; @@ -117,9 +117,10 @@ export interface ParameterObject { deprecated?: boolean; allowEmptyValue?: boolean; style?: string; + type?: string; explode?: boolean; allowReserved?: boolean; - schema?: SchemaObject | ReferenceObject; + schema?: Schema3RefObject; example?: any; examples?: { [exampleName: string]: ExampleObject | ReferenceObject; @@ -129,7 +130,7 @@ export interface ParameterObject { }; } -interface RequestBodyObject { +export interface RequestBodyObject { description?: string; content: { [mediaType: string]: MediaTypeObject; @@ -137,6 +138,14 @@ interface RequestBodyObject { required?: boolean; } +export interface RequestRefObject extends RequestBodyObject, ReferenceObject {} + +export interface Schema3RefObject extends SchemaObject, ReferenceObject { + example: any; +} + +export interface ParameterRefObject extends ParameterObject, ReferenceObject {} + interface ResponseObject { description: string; headers?: { @@ -151,7 +160,7 @@ interface ResponseObject { } interface MediaTypeObject { - schema?: SchemaObject | ReferenceObject; + schema?: Schema3RefObject; example?: any; examples?: { [exampleName: string]: ExampleObject | ReferenceObject; @@ -193,25 +202,25 @@ export interface SchemaObject { maxProperties?: number; minProperties?: number; required?: string[]; - additionalProperties?: boolean | SchemaObject | ReferenceObject; - items?: SchemaObject | ReferenceObject; - allOf?: (SchemaObject | ReferenceObject)[]; - oneOf?: (SchemaObject | ReferenceObject)[]; - anyOf?: (SchemaObject | ReferenceObject)[]; - not?: SchemaObject | ReferenceObject; + additionalProperties?: boolean | Schema3RefObject; + items?: Schema3RefObject; + allOf?: Schema3RefObject[]; + oneOf?: Schema3RefObject[]; + anyOf?: Schema3RefObject[]; + not?: Schema3RefObject; properties?: { - [propertyName: string]: SchemaObject | ReferenceObject; + [propertyName: string]: Schema3RefObject; }; dependencies?: { [propertyName: string]: SchemaObject | string[]; }; - propertyNames?: SchemaObject | ReferenceObject; + propertyNames?: Schema3RefObject; const?: any; contentMediaType?: string; contentEncoding?: string; - if?: SchemaObject | ReferenceObject; - then?: SchemaObject | ReferenceObject; - else?: SchemaObject | ReferenceObject; + if?: Schema3RefObject; + then?: Schema3RefObject; + else?: Schema3RefObject; } interface ExampleObject { @@ -248,7 +257,7 @@ interface HeaderObject { style?: string; explode?: boolean; allowReserved?: boolean; - schema?: SchemaObject | ReferenceObject; + schema?: Schema3RefObject; example?: any; examples?: { [exampleName: string]: ExampleObject | ReferenceObject; diff --git a/src/modules/common/services/helper/oapi2.transformer.ts b/src/modules/common/services/helper/oapi2.transformer.ts new file mode 100644 index 00000000..9371a922 --- /dev/null +++ b/src/modules/common/services/helper/oapi2.transformer.ts @@ -0,0 +1,282 @@ +import { v4 as uuidv4 } from "uuid"; +import { ItemTypeEnum, SourceTypeEnum } from "../../models/collection.model"; +import { + OpenAPI20, + OperationObject, + ParameterObject, + PathItemObject, +} from "../../models/openapi20.model"; +import { TransformedRequest } from "../../models/collection.rxdb.model"; +import { WithId } from "mongodb"; +import { User } from "../../models/user.model"; +import { buildExampleValue, getBaseUrl } from "./oapi3.transformer"; + +export function createCollectionItems( + openApiDocument: OpenAPI20, + user: WithId, +) { + const collectionItems: TransformedRequest[] = []; + if (openApiDocument.definitions) { + //Get all collection items + for (const [pathName, pathObject] of Object.entries( + openApiDocument.paths, + )) { + const request = transformPath( + pathName, + pathObject, + openApiDocument.securityDefinitions, + ); + collectionItems.push({ + id: uuidv4(), + name: request.name, + tag: request.tag, + type: ItemTypeEnum.REQUEST, + description: request.description, + operationId: request.operationId, + source: SourceTypeEnum.SPEC, + request: request.request, + isDeleted: false, + createdBy: user.name, + updatedBy: user.name, + createdAt: new Date(), + updatedAt: new Date(), + }); + } + } + + const baseUrl = getBaseUrl(openApiDocument); + + //Assigning requests to folders according to their tag + const folderMap = new Map(); + for (const item of collectionItems) { + item.request.url = baseUrl + item.request.url; + let tagDescription = ""; + for (const tag of Object.values(openApiDocument?.tags)) { + if (tag.name === item.tag) { + tagDescription = tag.description; + } + } + let folderObj = folderMap.get(item.tag); + if (!folderObj) { + folderObj = {}; + folderObj.name = item.tag; + folderObj.description = tagDescription; + folderObj.isDeleted = false; + folderObj.type = ItemTypeEnum.FOLDER; + folderObj.id = uuidv4(); + folderObj.items = []; + } + delete item.tag; + folderObj.items.push(item); + folderMap.set(folderObj.name, folderObj); + } + return folderMap; +} + +function transformPath( + pathName: string, + pathObject: PathItemObject, + security: any, +) { + const transformedObject = {} as any; + const method = Object.keys(pathObject)[0].toUpperCase(); // Assuming the first key is the HTTP method + const pathItemObject: OperationObject = Object.values(pathObject)[0]; + transformedObject.tag = pathItemObject.tags + ? pathItemObject.tags[0] + : "default"; + + transformedObject.name = pathName; + transformedObject.description = + pathItemObject.summary || pathItemObject.description || ""; // Use summary or description if available + transformedObject.operationId = pathItemObject.operationId; + + transformedObject.request = {}; + + transformedObject.request.method = method; + + // Extract URL path and query parameters + const urlParts = pathName.split("/").filter((p) => p != ""); + let url = ""; + const queryParams = [] as any; + for (let i = 0; i < urlParts.length; i++) { + if (urlParts[i].startsWith("{")) { + url += "/{" + urlParts[i].slice(1, -1) + "}"; + } else { + url += "/" + urlParts[i]; + } + if (i + 1 < urlParts.length && urlParts[i + 1].includes("=")) { + const queryParam = urlParts[i + 1].split("="); + queryParams.push({ + key: queryParam[0], + value: queryParam[1], + checked: true, + }); + i++; + } + } + transformedObject.request.url = url; + transformedObject.request.queryParams = queryParams; + transformedObject.request.pathParams = []; + + // Handle request body based on schema + transformedObject.request.body = {}; + + let consumes: any = null; + if (pathItemObject.consumes) { + consumes = Object.values(pathItemObject.consumes) || []; + if (consumes.includes("application/json")) { + transformedObject.request.body.raw = ""; + transformedObject.request.selectedRequestBodyType = "application/json"; + } else if (consumes.includes("application/javascript")) { + transformedObject.request.body.raw = ""; + transformedObject.request.selectedRequestBodyType = + "application/javascript"; + } else if (consumes.includes("text/html")) { + transformedObject.request.body.raw = ""; + transformedObject.request.selectedRequestBodyType = "text/html"; + } else if ( + consumes.includes("application/xml") || + consumes.includes("text/xml") + ) { + transformedObject.request.body.raw = ""; + transformedObject.request.selectedRequestBodyType = "application/xml"; + } else if (consumes.includes("application/x-www-form-urlencoded")) { + transformedObject.request.body.urlencoded = []; + transformedObject.request.selectedRequestBodyType = + "application/x-www-form-urlencoded"; + } else if (consumes.includes("multipart/form-data")) { + transformedObject.request.body.formdata = {}; + transformedObject.request.selectedRequestBodyType = "multipart/form-data"; + } + } + + // Handle headers based on schema + transformedObject.request.headers = []; + + // Handle authentication based on schema + transformedObject.request.auth = {}; + + if (security.api_key) { + transformedObject.request.auth = { + apiKey: { + authKey: security.api_key.name, + authValue: "", + addTo: "", + }, + }; + if (security.api_key.in === "header") { + transformedObject.request.headers.push({ + key: security.api_key.name, + value: "", + checked: false, + }); + transformedObject.request.auth.apiKey.addTo = "Header"; + } else if (security.api_key.in === "query") { + transformedObject.request.queryParams.push({ + key: security.api_key.name, + value: "", + checked: false, + }); + transformedObject.request.auth.apiKey.addTo = "Query Parameters"; + } + } + + // Parse request body parameters + const parameters = pathItemObject.parameters || []; + for (const param of Object.values(parameters) as ParameterObject[]) { + const paramIn = param.in; + const paramName = param.name; + const paramValue = param.example || getExampleValue(param.type); // Assuming example value is representative + + switch (paramIn) { + case "body": + if (consumes && consumes.includes("application/json")) { + const schema = param.schema; + if (schema && schema.type === "object") { + const properties = schema.properties || {}; + const bodyObject: any = {}; + for (const [propertyName, property] of Object.entries(properties)) { + const exampleType = property.type; + const exampleValue = property.example; + bodyObject[propertyName] = + exampleValue || + buildExampleValue(property) || + getExampleValue(exampleType); + } + transformedObject.request.body.raw = JSON.stringify(bodyObject); + } + } + break; + case "header": + transformedObject.request.headers.push({ + key: paramName, + value: paramValue, + checked: true, + }); + break; + case "query": + transformedObject.request.queryParams.push({ + key: paramName, + value: paramValue, + checked: false, + }); + break; + case "path": + transformedObject.request.pathParams.push({ + key: paramName, + value: paramValue, + checked: false, + }); + break; + case "formData": + if ( + consumes && + consumes.includes("application/x-www-form-urlencoded") + ) { + transformedObject.request.body.urlencoded.push({ + key: paramName, + value: paramValue, + checked: false, + }); + } else if (consumes && consumes.includes("multipart/form-data")) { + if (param.type === "file") { + transformedObject.request.body.formdata.file = []; + transformedObject.request.body.formdata.file.push({ + key: paramName, + value: paramValue, + checked: false, + base: "#@#" + paramValue, + }); + } else { + transformedObject.request.body.formdata.text = []; + transformedObject.request.body.formdata.text.push({ + key: paramName, + value: paramValue, + checked: false, + }); + } + } + } + } + + return transformedObject; +} + +function getExampleValue(exampleType: string) { + switch (exampleType) { + case "string": + return ""; // Or a default string value + case "number": + return 0; // Or a default number value + case "integer": + return 0; // Or a default number value + case "boolean": + return false; // Or a default boolean value + case "array": + return []; // Empty array + case "object": + return {}; // Empty object + default: + return ""; // Or a generic default value + } +} diff --git a/src/modules/common/services/helper/oapi.transformer.ts b/src/modules/common/services/helper/oapi3.transformer.ts similarity index 54% rename from src/modules/common/services/helper/oapi.transformer.ts rename to src/modules/common/services/helper/oapi3.transformer.ts index 992fe500..a7cb13d5 100644 --- a/src/modules/common/services/helper/oapi.transformer.ts +++ b/src/modules/common/services/helper/oapi3.transformer.ts @@ -1,13 +1,19 @@ -// @ts-nocheck import { v4 as uuidv4 } from "uuid"; import { ItemTypeEnum, SourceTypeEnum } from "../../models/collection.model"; -import { OpenAPI20, PathsObject } from "../../models/openapi20.model"; -import { OpenAPI303 } from "../../models/openapi303.model"; +import { OpenAPI20, SchemaRefObject } from "../../models/openapi20.model"; +import { + OpenAPI303, + OperationObject, + PathItemObject, + Schema3RefObject, +} from "../../models/openapi303.model"; import { TransformedRequest } from "../../models/collection.rxdb.model"; +import { WithId } from "mongodb"; +import { User } from "../../models/user.model"; export function createCollectionItems( - openApiDocument: OpenAPI20 | OpenAPI303, - user, + openApiDocument: OpenAPI303, + user: WithId, ) { const collectionItems: TransformedRequest[] = []; @@ -36,32 +42,6 @@ export function createCollectionItems( updatedAt: new Date(), }); } - } else if (openApiDocument.definitions) { - //Get all collection items - for (const [pathName, pathObject] of Object.entries( - openApiDocument.paths, - )) { - const request = transformPath( - pathName, - pathObject, - openApiDocument.securityDefinitions, - ); - collectionItems.push({ - id: uuidv4(), - name: request.name, - tag: request.tag, - type: ItemTypeEnum.REQUEST, - description: request.description, - operationId: request.operationId, - source: SourceTypeEnum.SPEC, - request: request.request, - isDeleted: false, - createdBy: user.name, - updatedBy: user.name, - createdAt: new Date(), - updatedAt: new Date(), - }); - } } const baseUrl = getBaseUrl(openApiDocument); @@ -93,203 +73,21 @@ export function createCollectionItems( return folderMap; } -function transformPath( +function transformPathV3( pathName: string, - pathObject: PathsObject, + pathObject: PathItemObject, security: any, ) { const transformedObject = {} as any; - const method = Object.keys(pathObject)[0].toUpperCase(); // Assuming the first key is the HTTP method - const pathItemObject = Object.values(pathObject)[0]; + const method = Object.keys(pathObject)[0].toUpperCase(); + const pathItemObject: OperationObject = Object.values(pathObject)[0]; transformedObject.tag = pathItemObject.tags ? pathItemObject.tags[0] : "default"; - transformedObject.name = pathName; transformedObject.description = - pathItemObject.summary || pathItemObject.description || ""; // Use summary or description if available + pathItemObject.summary || pathItemObject.description || ""; transformedObject.operationId = pathItemObject.operationId; - - transformedObject.request = {}; - - transformedObject.request.method = method; - - // Extract URL path and query parameters - const urlParts = pathName.split("/").filter((p) => p != ""); - let url = ""; - const queryParams = [] as any; - for (let i = 0; i < urlParts.length; i++) { - if (urlParts[i].startsWith("{")) { - url += "/{" + urlParts[i].slice(1, -1) + "}"; - } else { - url += "/" + urlParts[i]; - } - if (i + 1 < urlParts.length && urlParts[i + 1].includes("=")) { - const queryParam = urlParts[i + 1].split("="); - queryParams.push({ - key: queryParam[0], - value: queryParam[1], - checked: true, - }); - i++; - } - } - transformedObject.request.url = url; - transformedObject.request.queryParams = queryParams; - transformedObject.request.pathParams = []; - - // Handle request body based on schema - transformedObject.request.body = {}; - let consumes: any = null; - if (pathItemObject.consumes) { - consumes = Object.values(pathItemObject.consumes) || []; - if (consumes.includes("application/json")) { - transformedObject.request.body.raw = ""; - transformedObject.request.selectedRequestBodyType = "application/json"; - } else if (consumes.includes("application/javascript")) { - transformedObject.request.body.raw = ""; - transformedObject.request.selectedRequestBodyType = - "application/javascript"; - } else if (consumes.includes("text/html")) { - transformedObject.request.body.raw = ""; - transformedObject.request.selectedRequestBodyType = "text/html"; - } else if ( - consumes.includes("application/xml") || - consumes.includes("text/xml") - ) { - transformedObject.request.body.raw = ""; - transformedObject.request.selectedRequestBodyType = "application/xml"; - } else if (consumes.includes("application/x-www-form-urlencoded")) { - transformedObject.request.body.urlencoded = []; - transformedObject.request.selectedRequestBodyType = - "application/x-www-form-urlencoded"; - } else if (consumes.includes("multipart/form-data")) { - transformedObject.request.body.formdata = {}; - transformedObject.request.selectedRequestBodyType = "multipart/form-data"; - } - } - - // Handle headers based on schema - transformedObject.request.headers = []; - - // Handle authentication based on schema - transformedObject.request.auth = {}; - - if (security.api_key) { - transformedObject.request.auth = { - apiKey: { - authKey: security.api_key.name, - authValue: "", - addTo: "", - }, - }; - if (security.api_key.in === "header") { - transformedObject.request.headers.push({ - key: security.api_key.name, - value: "", - checked: false, - }); - transformedObject.request.auth.apiKey.addTo = "Header"; - } else if (security.api_key.in === "query") { - transformedObject.request.queryParams.push({ - key: security.api_key.name, - value: "", - checked: false, - }); - transformedObject.request.auth.apiKey.addTo = "Query Parameters"; - } - } - - // Parse request body parameters - const parameters = pathItemObject.parameters || []; - for (const param of Object.values(parameters)) { - const paramIn = param.in; - const paramName = param.name; - const paramValue = param.example || getExampleValue(param.type); // Assuming example value is representative - - switch (paramIn) { - case "body": - if (consumes && consumes.includes("application/json")) { - const schema = param.schema; - if (schema && schema.type === "object") { - const properties = schema.properties || {}; - const bodyObject = {}; - for (const [propertyName, property] of Object.entries(properties)) { - const exampleType = property.type; - const exampleValue = property.example; // Use example if available - bodyObject[propertyName] = - exampleValue || - buildExampleValue(property) || - getExampleValue(exampleType); - } - transformedObject.request.body.raw = JSON.stringify(bodyObject); - } - } - break; - case "header": - transformedObject.request.headers.push({ - key: paramName, - value: paramValue, - checked: true, - }); - break; - case "query": - transformedObject.request.queryParams.push({ - key: paramName, - value: paramValue, - checked: false, - }); - break; - case "path": - transformedObject.request.pathParams.push({ - key: paramName, - value: paramValue, - checked: false, - }); - break; - case "formData": - if ( - consumes && - consumes.includes("application/x-www-form-urlencoded") - ) { - transformedObject.request.body.urlencoded.push({ - key: paramName, - value: paramValue, - checked: false, - }); - } else if (consumes && consumes.includes("multipart/form-data")) { - if (param.type === "file") { - transformedObject.request.body.formdata.file = []; - transformedObject.request.body.formdata.file.push({ - key: paramName, - value: paramValue, - checked: false, - base: "#@#" + paramValue, - }); - } else { - transformedObject.request.body.formdata.text = []; - transformedObject.request.body.formdata.text.push({ - key: paramName, - value: paramValue, - checked: false, - }); - } - } - } - } - - return transformedObject; -} - -function transformPathV3(pathName: string, pathObject: any, security: any) { - const transformedObject = {} as any; - const method = Object.keys(pathObject)[0].toUpperCase(); - pathObject = Object.values(pathObject)[0]; - transformedObject.tag = pathObject.tags ? pathObject.tags[0] : "default"; - transformedObject.name = pathName; - transformedObject.description = - pathObject.summary || pathObject.description || ""; - transformedObject.operationId = pathObject.operationId; transformedObject.request = {}; transformedObject.request.method = method; @@ -325,18 +123,18 @@ function transformPathV3(pathName: string, pathObject: any, security: any) { transformedObject.request.body.formdata.text = []; transformedObject.request.body.urlencoded = []; - const content = pathObject?.requestBody?.content; + const content = pathItemObject?.requestBody?.content; if (content) { - const contentKeys = Object.keys(pathObject.requestBody.content) || []; + const contentKeys = Object.keys(pathItemObject.requestBody.content) || []; for (const key of contentKeys) { if (key === "application/json") { const schema = content[key].schema; if (schema && schema.type === "object") { const properties = schema.properties || {}; - const bodyObject = {}; + const bodyObject: any = {}; for (const [propertyName, property] of Object.entries(properties)) { const exampleType = property.type; - const exampleValue = property.example; // Use example if available + const exampleValue = property.example; bodyObject[propertyName] = exampleValue || buildExampleValue(property) || @@ -439,7 +237,7 @@ function transformPathV3(pathName: string, pathObject: any, security: any) { } // Parse request body parameters - const parameters = pathObject.parameters || []; + const parameters = pathItemObject.parameters || []; for (const param of Object.values(parameters)) { const paramIn = param.in; const paramName = param.name; @@ -492,10 +290,12 @@ function getExampleValue(exampleType: string) { } } -function buildExampleValue(property) { +export function buildExampleValue( + property: Schema3RefObject | SchemaRefObject, +) { if (property.type === "object") { const nestedProperties = property.properties || {}; - const nestedObject = {}; + const nestedObject: any = {}; for (const [nestedPropertyName, nestedProperty] of Object.entries( nestedProperties, )) { @@ -507,7 +307,7 @@ function buildExampleValue(property) { } } -function getBaseUrl(openApiDocument: OpenAPI20 | OpenAPI303) { +export function getBaseUrl(openApiDocument: OpenAPI20 | OpenAPI303) { const basePath = openApiDocument.basePath ? openApiDocument.basePath : ""; if (openApiDocument.host) { return "https://" + openApiDocument.host + basePath; diff --git a/src/modules/common/services/parser.service.ts b/src/modules/common/services/parser.service.ts index 2b77cb47..98e09c73 100644 --- a/src/modules/common/services/parser.service.ts +++ b/src/modules/common/services/parser.service.ts @@ -12,7 +12,8 @@ import { CollectionService } from "@src/modules/workspace/services/collection.se import { WithId } from "mongodb"; import { resolveAllRefs } from "./helper/parser.helper"; import { OpenAPI20 } from "../models/openapi20.model"; -import { createCollectionItems } from "./helper/oapi.transformer"; +import * as oapi2Transformer from "./helper/oapi2.transformer"; +import * as oapi3Transformer from "./helper/oapi3.transformer"; @Injectable() export class ParserService { @@ -38,10 +39,17 @@ export class ParserService { const user = await this.contextService.get("user"); if (openApiDocument.hasOwnProperty("components")) { openApiDocument = resolveAllRefs(openApiDocument) as OpenAPI303; + folderObjMap = oapi3Transformer.createCollectionItems( + openApiDocument, + user, + ); } else if (openApiDocument.hasOwnProperty("definitions")) { openApiDocument = resolveAllRefs(openApiDocument) as OpenAPI20; + folderObjMap = oapi2Transformer.createCollectionItems( + openApiDocument, + user, + ); } - folderObjMap = createCollectionItems(openApiDocument, user); const itemObject = Object.fromEntries(folderObjMap); let items: CollectionItem[] = []; let totalRequests = 0; From fd98217b5cd54ef5ff7789a7bd44ce2d28dede1b Mon Sep 17 00:00:00 2001 From: Nayan Lakhwani Date: Fri, 8 Mar 2024 12:00:09 +0530 Subject: [PATCH 14/43] feat: oapi transform[] --- src/modules/app/app.controller.ts | 12 +- src/modules/app/app.service.ts | 380 +++++++++--------- .../services/helper/oapi3.transformer.ts | 46 +-- 3 files changed, 224 insertions(+), 214 deletions(-) diff --git a/src/modules/app/app.controller.ts b/src/modules/app/app.controller.ts index b8a261c8..64869a29 100644 --- a/src/modules/app/app.controller.ts +++ b/src/modules/app/app.controller.ts @@ -1,7 +1,16 @@ -import { Controller, Get, Param, Post, Req, Res } from "@nestjs/common"; +import { + Controller, + Get, + Param, + Post, + Req, + Res, + UseGuards, +} from "@nestjs/common"; import { ApiHeader, ApiOperation, ApiResponse } from "@nestjs/swagger"; import { FastifyReply, FastifyRequest } from "fastify"; import { AppService } from "./app.service"; +import { JwtAuthGuard } from "../common/guards/jwt-auth.guard"; /** * App Controller @@ -44,6 +53,7 @@ export class AppController { status: 200, description: "Curl parsed successfully", }) + @UseGuards(JwtAuthGuard) async parseCurl(@Res() res: FastifyReply, @Req() req: FastifyRequest) { const parsedRequestData = await this.appService.parseCurl(req); return res.status(200).send(parsedRequestData); diff --git a/src/modules/app/app.service.ts b/src/modules/app/app.service.ts index ee68d2a7..8480e72c 100644 --- a/src/modules/app/app.service.ts +++ b/src/modules/app/app.service.ts @@ -13,6 +13,7 @@ import { AddTo, TransformedRequest, } from "../common/models/collection.rxdb.model"; +import { ContextService } from "../common/services/context.service"; /** * Application Service @@ -24,7 +25,10 @@ export class AppService { * Constructor * @param {ConfigService} config configuration service */ - constructor(private config: ConfigService) {} + constructor( + private config: ConfigService, + private contextService: ContextService, + ) {} importCurlConverter() { if (!this.curlconverterPromise) { @@ -74,226 +78,226 @@ export class AppService { if (!curl || !curl.length) { throw new Error(); } - return transformRequest(JSON.parse(toJsonString(curl))); + return this.transformRequest(JSON.parse(toJsonString(curl))); } catch (error) { console.error("Error parsing :", error); throw new BadRequestException("Invalid Curl"); } } -} -async function transformRequest( - requestObject: any, -): Promise { - const user = await this.contextService.get("user"); - const transformedObject: TransformedRequest = { - name: requestObject.url || "", - description: "", - type: ItemTypeEnum.REQUEST, - source: SourceTypeEnum.USER, - request: { - method: requestObject.method.toUpperCase(), - url: "", - body: { - raw: "", - urlencoded: [], - formdata: { - text: [], - file: [], + async transformRequest(requestObject: any): Promise { + const user = await this.contextService.get("user"); + const transformedObject: TransformedRequest = { + name: requestObject.url || "", + description: "", + type: ItemTypeEnum.REQUEST, + source: SourceTypeEnum.USER, + request: { + method: requestObject.method.toUpperCase(), + url: "", + body: { + raw: "", + urlencoded: [], + formdata: { + text: [], + file: [], + }, }, + headers: [], + queryParams: [], + auth: {}, + selectedRequestBodyType: BodyModeEnum["none"], + selectedRequestAuthType: AuthModeEnum["No Auth"], }, - headers: [], - queryParams: [], - auth: {}, - selectedRequestBodyType: BodyModeEnum["none"], - selectedRequestAuthType: AuthModeEnum["No Auth"], - }, - createdBy: user.name, - updatedBy: user.name, - createdAt: new Date(), - updatedAt: new Date(), - }; + createdBy: user.name, + updatedBy: user.name, + createdAt: new Date(), + updatedAt: new Date(), + }; - // Handle URL with query parameters - if (requestObject.queries) { - const queryParams = []; - for (const [key, value] of Object.entries(requestObject.queries)) { - queryParams.push({ key, value, checked: true }); - if ( - key.toLowerCase() === "api-key" || - key.toLowerCase() === "x-api-key" - ) { - transformedObject.request.auth.apiKey = { - authKey: key, - authValue: value, - addTo: AddTo.QueryParameter, - }; - transformedObject.request.selectedRequestAuthType = - AuthModeEnum["API Key"]; + // Handle URL with query parameters + if (requestObject.queries) { + const queryParams = []; + for (const [key, value] of Object.entries(requestObject.queries)) { + queryParams.push({ key, value, checked: true }); + if ( + key.toLowerCase() === "api-key" || + key.toLowerCase() === "x-api-key" + ) { + transformedObject.request.auth.apiKey = { + authKey: key, + authValue: value, + addTo: AddTo.QueryParameter, + }; + transformedObject.request.selectedRequestAuthType = + AuthModeEnum["API Key"]; + } } + transformedObject.request.url = requestObject.raw_url; } - transformedObject.request.url = requestObject.raw_url; - } - // Handle request body based on Content-Type - if (requestObject.data) { - const contentType = - requestObject.headers["content-type"] || - requestObject.headers["Content-Type"] || - ""; - //"multipart/form-data; boundary=----WebKitFormBoundaryhBHci3a7BLGRCFlH" - if (contentType.startsWith("multipart/form-data")) { - const boundary = contentType.split("boundary=")[1]; - const formDataParts = requestObject.data.split(`--${boundary}\r\n`); - formDataParts.shift(); // Remove the first boundary part + // Handle request body based on Content-Type + if (requestObject.data) { + const contentType = + requestObject.headers["content-type"] || + requestObject.headers["Content-Type"] || + ""; + //"multipart/form-data; boundary=----WebKitFormBoundaryhBHci3a7BLGRCFlH" + if (contentType.startsWith("multipart/form-data")) { + const boundary = contentType.split("boundary=")[1]; + const formDataParts = requestObject.data.split(`--${boundary}\r\n`); + formDataParts.shift(); // Remove the first boundary part - for (const part of formDataParts) { - const lines = part.trim().split("\r\n"); - const disposition = lines[0]; // Content-Disposition line - if (disposition.includes('name="_method"')) { - // Ignore the _method part (can be handled elsewhere if needed) - continue; - } - const key = disposition.split('name="')[1].split('"')[0]; - let value = ""; + for (const part of formDataParts) { + const lines = part.trim().split("\r\n"); + const disposition = lines[0]; // Content-Disposition line + if (disposition.includes('name="_method"')) { + // Ignore the _method part (can be handled elsewhere if needed) + continue; + } + const key = disposition.split('name="')[1].split('"')[0]; + let value = ""; - if (lines.length > 2) { - value = lines.slice(2).join("\r\n").trim(); // Extract value from part content - } + if (lines.length > 2) { + value = lines.slice(2).join("\r\n").trim(); // Extract value from part content + } - if (value.includes(boundary)) { - value = ""; - } + if (value.includes(boundary)) { + value = ""; + } - if (disposition.includes('Content-Disposition: form-data; name="')) { - transformedObject.request.body.formdata.text.push({ - key, - value, - checked: true, - }); - } else if ( - disposition.includes('Content-Disposition: form-data; name="file"') && - value.startsWith("/") - ) { - transformedObject.request.body.formdata.file.push({ + if (disposition.includes('Content-Disposition: form-data; name="')) { + transformedObject.request.body.formdata.text.push({ + key, + value, + checked: true, + }); + } else if ( + disposition.includes( + 'Content-Disposition: form-data; name="file"', + ) && + value.startsWith("/") + ) { + transformedObject.request.body.formdata.file.push({ + key, + value, + checked: true, + base: `#@#${value}`, + }); + } + } + transformedObject.request.selectedRequestBodyType = + BodyModeEnum["multipart/form-data"]; + } else if (contentType.includes("application/json")) { + try { + transformedObject.request.body.raw = JSON.stringify( + requestObject.data, + null, + 2, + ); + } catch (error) { + console.warn("Error parsing request body JSON:", error); + transformedObject.request.body.raw = requestObject.data; + } + transformedObject.request.selectedRequestBodyType = + BodyModeEnum["application/json"]; + } else if (contentType.includes("application/javascript")) { + transformedObject.request.body.raw = ""; + transformedObject.request.selectedRequestBodyType = + BodyModeEnum["application/javascript"]; + } else if (contentType.includes("text/html")) { + transformedObject.request.body.raw = ""; + transformedObject.request.selectedRequestBodyType = + BodyModeEnum["text/html"]; + } else if ( + contentType.includes("application/xml") || + contentType.includes("text/xml") + ) { + transformedObject.request.body.raw = ""; + transformedObject.request.selectedRequestBodyType = + BodyModeEnum["application/xml"]; + } else if (contentType.includes("application/x-www-form-urlencoded")) { + // Assuming data is already URL-encoded key-value pairs + for (const [key, value] of new URLSearchParams(requestObject.data)) { + transformedObject.request.body.urlencoded.push({ key, value, checked: true, - base: `#@#${value}`, }); } - } - transformedObject.request.selectedRequestBodyType = - BodyModeEnum["multipart/form-data"]; - } else if (contentType.includes("application/json")) { - try { - transformedObject.request.body.raw = JSON.stringify( - requestObject.data, - null, - 2, - ); - } catch (error) { - console.warn("Error parsing request body JSON:", error); + transformedObject.request.selectedRequestBodyType = + BodyModeEnum["application/x-www-form-urlencoded"]; + } else { + // Handle other content types (consider adding warnings or handling as raw data) + console.warn(`Unsupported Content-Type: ${contentType}`); transformedObject.request.body.raw = requestObject.data; + transformedObject.request.selectedRequestBodyType = + BodyModeEnum["text/plain"]; } - transformedObject.request.selectedRequestBodyType = - BodyModeEnum["application/json"]; - } else if (contentType.includes("application/javascript")) { - transformedObject.request.body.raw = ""; - transformedObject.request.selectedRequestBodyType = - BodyModeEnum["application/javascript"]; - } else if (contentType.includes("text/html")) { - transformedObject.request.body.raw = ""; - transformedObject.request.selectedRequestBodyType = - BodyModeEnum["text/html"]; - } else if ( - contentType.includes("application/xml") || - contentType.includes("text/xml") - ) { - transformedObject.request.body.raw = ""; - transformedObject.request.selectedRequestBodyType = - BodyModeEnum["application/xml"]; - } else if (contentType.includes("application/x-www-form-urlencoded")) { - // Assuming data is already URL-encoded key-value pairs - for (const [key, value] of new URLSearchParams(requestObject.data)) { - transformedObject.request.body.urlencoded.push({ + } + + // Handle files from request object (unchanged from previous version) + if (requestObject.files) { + for (const [key, filename] of Object.entries(requestObject.files)) { + transformedObject.request.body.formdata.file.push({ key, - value, + value: filename, checked: true, + base: `#@#${filename}`, }); } - transformedObject.request.selectedRequestBodyType = - BodyModeEnum["application/x-www-form-urlencoded"]; - } else { - // Handle other content types (consider adding warnings or handling as raw data) - console.warn(`Unsupported Content-Type: ${contentType}`); - transformedObject.request.body.raw = requestObject.data; - transformedObject.request.selectedRequestBodyType = - BodyModeEnum["text/plain"]; - } - } - - // Handle files from request object (unchanged from previous version) - if (requestObject.files) { - for (const [key, filename] of Object.entries(requestObject.files)) { - transformedObject.request.body.formdata.file.push({ - key, - value: filename, - checked: true, - base: `#@#${filename}`, - }); } - } - // Handle headers and populate auth details (unchanged from previous version) - if (requestObject.headers) { - for (const [key, value] of Object.entries(requestObject.headers)) { - transformedObject.request.headers.push({ key, value, checked: true }); + // Handle headers and populate auth details (unchanged from previous version) + if (requestObject.headers) { + for (const [key, value] of Object.entries(requestObject.headers)) { + transformedObject.request.headers.push({ key, value, checked: true }); - // Check for Bearer token - if ( - key.toLowerCase() === "authorization" && - typeof value === "string" && - (value.startsWith("bearer ") || value.startsWith("Bearer ")) - ) { - transformedObject.request.auth.bearerToken = value.slice(7).trim(); - transformedObject.request.selectedRequestAuthType = - AuthModeEnum["Bearer Token"]; - } + // Check for Bearer token + if ( + key.toLowerCase() === "authorization" && + typeof value === "string" && + (value.startsWith("bearer ") || value.startsWith("Bearer ")) + ) { + transformedObject.request.auth.bearerToken = value.slice(7).trim(); + transformedObject.request.selectedRequestAuthType = + AuthModeEnum["Bearer Token"]; + } - // Check for API key - if ( - key.toLowerCase() === "api-key" || - key.toLowerCase() === "x-api-key" - ) { - transformedObject.request.auth.apiKey = { - authKey: key, - authValue: value, - addTo: AddTo.Header, - }; - transformedObject.request.selectedRequestAuthType = - AuthModeEnum["API Key"]; - } + // Check for API key + if ( + key.toLowerCase() === "api-key" || + key.toLowerCase() === "x-api-key" + ) { + transformedObject.request.auth.apiKey = { + authKey: key, + authValue: value, + addTo: AddTo.Header, + }; + transformedObject.request.selectedRequestAuthType = + AuthModeEnum["API Key"]; + } - // Check for Basic Auth (assuming encoded username:password) - if ( - key.toLowerCase() === "authorization" && - typeof value === "string" && - (value.startsWith("basic ") || value.startsWith("Basic ")) - ) { - const decodedValue = Buffer.from(value.slice(6), "base64").toString( - "utf8", - ); - const [username, password] = decodedValue.split(":"); - transformedObject.request.auth.basicAuth = { - username, - password, - }; - transformedObject.request.selectedRequestAuthType = - AuthModeEnum["Basic Auth"]; + // Check for Basic Auth (assuming encoded username:password) + if ( + key.toLowerCase() === "authorization" && + typeof value === "string" && + (value.startsWith("basic ") || value.startsWith("Basic ")) + ) { + const decodedValue = Buffer.from(value.slice(6), "base64").toString( + "utf8", + ); + const [username, password] = decodedValue.split(":"); + transformedObject.request.auth.basicAuth = { + username, + password, + }; + transformedObject.request.selectedRequestAuthType = + AuthModeEnum["Basic Auth"]; + } } } - } - return transformedObject; + return transformedObject; + } } diff --git a/src/modules/common/services/helper/oapi3.transformer.ts b/src/modules/common/services/helper/oapi3.transformer.ts index a7cb13d5..b8292dde 100644 --- a/src/modules/common/services/helper/oapi3.transformer.ts +++ b/src/modules/common/services/helper/oapi3.transformer.ts @@ -17,31 +17,27 @@ export function createCollectionItems( ) { const collectionItems: TransformedRequest[] = []; - if (openApiDocument.components) { - for (const [pathName, pathObject] of Object.entries( - openApiDocument.paths, - )) { - const request = transformPathV3( - pathName, - pathObject, - openApiDocument.components.securitySchemes, - ); - collectionItems.push({ - id: uuidv4(), - name: request.name, - tag: request.tag, - type: ItemTypeEnum.REQUEST, - description: request.description, - operationId: request.operationId, - source: SourceTypeEnum.SPEC, - request: request.request, - isDeleted: false, - createdBy: user.name, - updatedBy: user.name, - createdAt: new Date(), - updatedAt: new Date(), - }); - } + for (const [pathName, pathObject] of Object.entries(openApiDocument.paths)) { + const request = transformPathV3( + pathName, + pathObject, + openApiDocument.components.securitySchemes, + ); + collectionItems.push({ + id: uuidv4(), + name: request.name, + tag: request.tag, + type: ItemTypeEnum.REQUEST, + description: request.description, + operationId: request.operationId, + source: SourceTypeEnum.SPEC, + request: request.request, + isDeleted: false, + createdBy: user.name, + updatedBy: user.name, + createdAt: new Date(), + updatedAt: new Date(), + }); } const baseUrl = getBaseUrl(openApiDocument); From 364ebbbff58fd7b68bcb1089579f1551c200dd5d Mon Sep 17 00:00:00 2001 From: Nayan Lakhwani Date: Fri, 8 Mar 2024 14:25:05 +0530 Subject: [PATCH 15/43] fix: oapi transform edge case handled[] --- src/modules/app/app.controller.ts | 8 +- src/modules/app/app.service.ts | 56 ++++-- src/modules/common/models/collection.model.ts | 5 + .../common/models/collection.rxdb.model.ts | 2 +- .../services/helper/oapi2.transformer.ts | 162 ++++++++++-------- .../services/helper/oapi3.transformer.ts | 144 ++++++++++------ src/modules/common/services/parser.service.ts | 67 ++------ 7 files changed, 260 insertions(+), 184 deletions(-) diff --git a/src/modules/app/app.controller.ts b/src/modules/app/app.controller.ts index 64869a29..d4d98289 100644 --- a/src/modules/app/app.controller.ts +++ b/src/modules/app/app.controller.ts @@ -7,7 +7,12 @@ import { Res, UseGuards, } from "@nestjs/common"; -import { ApiHeader, ApiOperation, ApiResponse } from "@nestjs/swagger"; +import { + ApiBearerAuth, + ApiHeader, + ApiOperation, + ApiResponse, +} from "@nestjs/swagger"; import { FastifyReply, FastifyRequest } from "fastify"; import { AppService } from "./app.service"; import { JwtAuthGuard } from "../common/guards/jwt-auth.guard"; @@ -15,6 +20,7 @@ import { JwtAuthGuard } from "../common/guards/jwt-auth.guard"; /** * App Controller */ +@ApiBearerAuth() @Controller() export class AppController { constructor(private appService: AppService) {} diff --git a/src/modules/app/app.service.ts b/src/modules/app/app.service.ts index 8480e72c..08ba7984 100644 --- a/src/modules/app/app.service.ts +++ b/src/modules/app/app.service.ts @@ -87,6 +87,17 @@ export class AppService { async transformRequest(requestObject: any): Promise { const user = await this.contextService.get("user"); + const keyValueDefaultObj = { + key: "", + value: "", + checked: false, + }; + const formDataFileDefaultObj = { + key: "", + value: "", + checked: false, + base: "", + }; const transformedObject: TransformedRequest = { name: requestObject.url || "", description: "", @@ -105,7 +116,18 @@ export class AppService { }, headers: [], queryParams: [], - auth: {}, + auth: { + bearerToken: "", + basicAuth: { + username: "", + password: "", + }, + apiKey: { + authKey: "", + authValue: "", + addTo: AddTo.Header, + }, + }, selectedRequestBodyType: BodyModeEnum["none"], selectedRequestAuthType: AuthModeEnum["No Auth"], }, @@ -134,6 +156,7 @@ export class AppService { } } transformedObject.request.url = requestObject.raw_url; + transformedObject.request.queryParams = queryParams; } // Handle request body based on Content-Type @@ -142,7 +165,6 @@ export class AppService { requestObject.headers["content-type"] || requestObject.headers["Content-Type"] || ""; - //"multipart/form-data; boundary=----WebKitFormBoundaryhBHci3a7BLGRCFlH" if (contentType.startsWith("multipart/form-data")) { const boundary = contentType.split("boundary=")[1]; const formDataParts = requestObject.data.split(`--${boundary}\r\n`); @@ -152,7 +174,7 @@ export class AppService { const lines = part.trim().split("\r\n"); const disposition = lines[0]; // Content-Disposition line if (disposition.includes('name="_method"')) { - // Ignore the _method part (can be handled elsewhere if needed) + // Ignore the _method part continue; } const key = disposition.split('name="')[1].split('"')[0]; @@ -202,22 +224,18 @@ export class AppService { transformedObject.request.selectedRequestBodyType = BodyModeEnum["application/json"]; } else if (contentType.includes("application/javascript")) { - transformedObject.request.body.raw = ""; transformedObject.request.selectedRequestBodyType = BodyModeEnum["application/javascript"]; } else if (contentType.includes("text/html")) { - transformedObject.request.body.raw = ""; transformedObject.request.selectedRequestBodyType = BodyModeEnum["text/html"]; } else if ( contentType.includes("application/xml") || contentType.includes("text/xml") ) { - transformedObject.request.body.raw = ""; transformedObject.request.selectedRequestBodyType = BodyModeEnum["application/xml"]; } else if (contentType.includes("application/x-www-form-urlencoded")) { - // Assuming data is already URL-encoded key-value pairs for (const [key, value] of new URLSearchParams(requestObject.data)) { transformedObject.request.body.urlencoded.push({ key, @@ -228,7 +246,6 @@ export class AppService { transformedObject.request.selectedRequestBodyType = BodyModeEnum["application/x-www-form-urlencoded"]; } else { - // Handle other content types (consider adding warnings or handling as raw data) console.warn(`Unsupported Content-Type: ${contentType}`); transformedObject.request.body.raw = requestObject.data; transformedObject.request.selectedRequestBodyType = @@ -236,7 +253,7 @@ export class AppService { } } - // Handle files from request object (unchanged from previous version) + // Handle files from request object if (requestObject.files) { for (const [key, filename] of Object.entries(requestObject.files)) { transformedObject.request.body.formdata.file.push({ @@ -248,7 +265,7 @@ export class AppService { } } - // Handle headers and populate auth details (unchanged from previous version) + // Handle headers and populate auth details if (requestObject.headers) { for (const [key, value] of Object.entries(requestObject.headers)) { transformedObject.request.headers.push({ key, value, checked: true }); @@ -278,7 +295,7 @@ export class AppService { AuthModeEnum["API Key"]; } - // Check for Basic Auth (assuming encoded username:password) + // Check for Basic Auth if ( key.toLowerCase() === "authorization" && typeof value === "string" && @@ -298,6 +315,23 @@ export class AppService { } } + //Assign default values + if (!transformedObject.request.headers.length) { + transformedObject.request.headers.push(keyValueDefaultObj); + } + if (!transformedObject.request.queryParams.length) { + transformedObject.request.queryParams.push(keyValueDefaultObj); + } + if (!transformedObject.request.body.formdata.text.length) { + transformedObject.request.body.formdata.text.push(keyValueDefaultObj); + } + if (!transformedObject.request.body.formdata.file.length) { + transformedObject.request.body.formdata.file.push(formDataFileDefaultObj); + } + if (!transformedObject.request.body.urlencoded.length) { + transformedObject.request.body.urlencoded.push(keyValueDefaultObj); + } + return transformedObject; } } diff --git a/src/modules/common/models/collection.model.ts b/src/modules/common/models/collection.model.ts index fb0bd33a..addc3717 100644 --- a/src/modules/common/models/collection.model.ts +++ b/src/modules/common/models/collection.model.ts @@ -249,6 +249,11 @@ export class Collection { @IsNotEmpty() name: string; + @ApiProperty() + @IsString() + @IsNotEmpty() + description?: string; + @ApiProperty() @IsNumber() @IsNotEmpty() diff --git a/src/modules/common/models/collection.rxdb.model.ts b/src/modules/common/models/collection.rxdb.model.ts index 5aff711d..e4068ced 100644 --- a/src/modules/common/models/collection.rxdb.model.ts +++ b/src/modules/common/models/collection.rxdb.model.ts @@ -7,7 +7,7 @@ import { export enum AddTo { Header = "Header", - QueryParameter = "QueryParameter", + QueryParameter = "Query Parameter", } export interface TransformedRequest { diff --git a/src/modules/common/services/helper/oapi2.transformer.ts b/src/modules/common/services/helper/oapi2.transformer.ts index 9371a922..0816ec20 100644 --- a/src/modules/common/services/helper/oapi2.transformer.ts +++ b/src/modules/common/services/helper/oapi2.transformer.ts @@ -1,15 +1,24 @@ import { v4 as uuidv4 } from "uuid"; -import { ItemTypeEnum, SourceTypeEnum } from "../../models/collection.model"; +import { + AuthModeEnum, + BodyModeEnum, + ItemTypeEnum, + SourceTypeEnum, +} from "../../models/collection.model"; import { OpenAPI20, OperationObject, ParameterObject, PathItemObject, } from "../../models/openapi20.model"; -import { TransformedRequest } from "../../models/collection.rxdb.model"; +import { AddTo, TransformedRequest } from "../../models/collection.rxdb.model"; import { WithId } from "mongodb"; import { User } from "../../models/user.model"; -import { buildExampleValue, getBaseUrl } from "./oapi3.transformer"; +import { + buildExampleValue, + getBaseUrl, + getExampleValue, +} from "./oapi3.transformer"; export function createCollectionItems( openApiDocument: OpenAPI20, @@ -25,6 +34,7 @@ export function createCollectionItems( pathName, pathObject, openApiDocument.securityDefinitions, + user, ); collectionItems.push({ id: uuidv4(), @@ -77,8 +87,57 @@ function transformPath( pathName: string, pathObject: PathItemObject, security: any, + user: WithId, ) { - const transformedObject = {} as any; + const keyValueDefaultObj = { + key: "", + value: "", + checked: false, + }; + const formDataFileDefaultObj = { + key: "", + value: "", + checked: false, + base: "", + }; + const transformedObject: TransformedRequest = { + name: pathName || "", + description: "", + type: ItemTypeEnum.REQUEST, + source: SourceTypeEnum.USER, + request: { + method: "", + url: "", + body: { + raw: "", + urlencoded: [], + formdata: { + text: [], + file: [], + }, + }, + headers: [], + queryParams: [], + auth: { + bearerToken: "", + basicAuth: { + username: "", + password: "", + }, + apiKey: { + authKey: "", + authValue: "", + addTo: AddTo.Header, + }, + }, + selectedRequestBodyType: BodyModeEnum["none"], + selectedRequestAuthType: AuthModeEnum["No Auth"], + }, + createdBy: user.name, + updatedBy: user.name, + createdAt: new Date(), + updatedAt: new Date(), + }; const method = Object.keys(pathObject)[0].toUpperCase(); // Assuming the first key is the HTTP method const pathItemObject: OperationObject = Object.values(pathObject)[0]; transformedObject.tag = pathItemObject.tags @@ -90,14 +149,11 @@ function transformPath( pathItemObject.summary || pathItemObject.description || ""; // Use summary or description if available transformedObject.operationId = pathItemObject.operationId; - transformedObject.request = {}; - transformedObject.request.method = method; // Extract URL path and query parameters const urlParts = pathName.split("/").filter((p) => p != ""); let url = ""; - const queryParams = [] as any; for (let i = 0; i < urlParts.length; i++) { if (urlParts[i].startsWith("{")) { url += "/{" + urlParts[i].slice(1, -1) + "}"; @@ -106,7 +162,7 @@ function transformPath( } if (i + 1 < urlParts.length && urlParts[i + 1].includes("=")) { const queryParam = urlParts[i + 1].split("="); - queryParams.push({ + transformedObject.request.queryParams.push({ key: queryParam[0], value: queryParam[1], checked: true, @@ -115,69 +171,50 @@ function transformPath( } } transformedObject.request.url = url; - transformedObject.request.queryParams = queryParams; - transformedObject.request.pathParams = []; - - // Handle request body based on schema - transformedObject.request.body = {}; let consumes: any = null; if (pathItemObject.consumes) { consumes = Object.values(pathItemObject.consumes) || []; if (consumes.includes("application/json")) { - transformedObject.request.body.raw = ""; - transformedObject.request.selectedRequestBodyType = "application/json"; + transformedObject.request.selectedRequestBodyType = + BodyModeEnum["application/json"]; } else if (consumes.includes("application/javascript")) { - transformedObject.request.body.raw = ""; transformedObject.request.selectedRequestBodyType = - "application/javascript"; + BodyModeEnum["application/javascript"]; } else if (consumes.includes("text/html")) { - transformedObject.request.body.raw = ""; - transformedObject.request.selectedRequestBodyType = "text/html"; + transformedObject.request.selectedRequestBodyType = + BodyModeEnum["text/html"]; } else if ( consumes.includes("application/xml") || consumes.includes("text/xml") ) { - transformedObject.request.body.raw = ""; - transformedObject.request.selectedRequestBodyType = "application/xml"; + transformedObject.request.selectedRequestBodyType = + BodyModeEnum["application/xml"]; } else if (consumes.includes("application/x-www-form-urlencoded")) { - transformedObject.request.body.urlencoded = []; transformedObject.request.selectedRequestBodyType = - "application/x-www-form-urlencoded"; + BodyModeEnum["application/x-www-form-urlencoded"]; } else if (consumes.includes("multipart/form-data")) { - transformedObject.request.body.formdata = {}; - transformedObject.request.selectedRequestBodyType = "multipart/form-data"; + transformedObject.request.selectedRequestBodyType = + BodyModeEnum["multipart/form-data"]; } } - // Handle headers based on schema - transformedObject.request.headers = []; - - // Handle authentication based on schema - transformedObject.request.auth = {}; - if (security.api_key) { - transformedObject.request.auth = { - apiKey: { - authKey: security.api_key.name, - authValue: "", - addTo: "", - }, - }; + transformedObject.request.auth.apiKey.authKey = security.api_key.name; if (security.api_key.in === "header") { transformedObject.request.headers.push({ key: security.api_key.name, value: "", checked: false, }); - transformedObject.request.auth.apiKey.addTo = "Header"; + transformedObject.request.auth.apiKey.addTo = AddTo.Header; } else if (security.api_key.in === "query") { transformedObject.request.queryParams.push({ key: security.api_key.name, value: "", checked: false, }); - transformedObject.request.auth.apiKey.addTo = "Query Parameters"; + transformedObject.request.auth.apiKey.addTo = AddTo.QueryParameter; } } @@ -221,13 +258,6 @@ function transformPath( checked: false, }); break; - case "path": - transformedObject.request.pathParams.push({ - key: paramName, - value: paramValue, - checked: false, - }); - break; case "formData": if ( consumes && @@ -240,7 +270,6 @@ function transformPath( }); } else if (consumes && consumes.includes("multipart/form-data")) { if (param.type === "file") { - transformedObject.request.body.formdata.file = []; transformedObject.request.body.formdata.file.push({ key: paramName, value: paramValue, @@ -248,7 +277,6 @@ function transformPath( base: "#@#" + paramValue, }); } else { - transformedObject.request.body.formdata.text = []; transformedObject.request.body.formdata.text.push({ key: paramName, value: paramValue, @@ -259,24 +287,22 @@ function transformPath( } } - return transformedObject; -} - -function getExampleValue(exampleType: string) { - switch (exampleType) { - case "string": - return ""; // Or a default string value - case "number": - return 0; // Or a default number value - case "integer": - return 0; // Or a default number value - case "boolean": - return false; // Or a default boolean value - case "array": - return []; // Empty array - case "object": - return {}; // Empty object - default: - return ""; // Or a generic default value + //Assign default values + if (!transformedObject.request.headers.length) { + transformedObject.request.headers.push(keyValueDefaultObj); + } + if (!transformedObject.request.queryParams.length) { + transformedObject.request.queryParams.push(keyValueDefaultObj); + } + if (!transformedObject.request.body.formdata.text.length) { + transformedObject.request.body.formdata.text.push(keyValueDefaultObj); + } + if (!transformedObject.request.body.formdata.file.length) { + transformedObject.request.body.formdata.file.push(formDataFileDefaultObj); } + if (!transformedObject.request.body.urlencoded.length) { + transformedObject.request.body.urlencoded.push(keyValueDefaultObj); + } + + return transformedObject; } diff --git a/src/modules/common/services/helper/oapi3.transformer.ts b/src/modules/common/services/helper/oapi3.transformer.ts index b8292dde..75b17117 100644 --- a/src/modules/common/services/helper/oapi3.transformer.ts +++ b/src/modules/common/services/helper/oapi3.transformer.ts @@ -1,5 +1,10 @@ import { v4 as uuidv4 } from "uuid"; -import { ItemTypeEnum, SourceTypeEnum } from "../../models/collection.model"; +import { + AuthModeEnum, + BodyModeEnum, + ItemTypeEnum, + SourceTypeEnum, +} from "../../models/collection.model"; import { OpenAPI20, SchemaRefObject } from "../../models/openapi20.model"; import { OpenAPI303, @@ -7,7 +12,7 @@ import { PathItemObject, Schema3RefObject, } from "../../models/openapi303.model"; -import { TransformedRequest } from "../../models/collection.rxdb.model"; +import { AddTo, TransformedRequest } from "../../models/collection.rxdb.model"; import { WithId } from "mongodb"; import { User } from "../../models/user.model"; @@ -22,6 +27,7 @@ export function createCollectionItems( pathName, pathObject, openApiDocument.components.securitySchemes, + user, ); collectionItems.push({ id: uuidv4(), @@ -73,8 +79,57 @@ function transformPathV3( pathName: string, pathObject: PathItemObject, security: any, + user: WithId, ) { - const transformedObject = {} as any; + const keyValueDefaultObj = { + key: "", + value: "", + checked: false, + }; + const formDataFileDefaultObj = { + key: "", + value: "", + checked: false, + base: "", + }; + const transformedObject: TransformedRequest = { + name: pathName || "", + description: "", + type: ItemTypeEnum.REQUEST, + source: SourceTypeEnum.USER, + request: { + method: "", + url: "", + body: { + raw: "", + urlencoded: [], + formdata: { + text: [], + file: [], + }, + }, + headers: [], + queryParams: [], + auth: { + bearerToken: "", + basicAuth: { + username: "", + password: "", + }, + apiKey: { + authKey: "", + authValue: "", + addTo: AddTo.Header, + }, + }, + selectedRequestBodyType: BodyModeEnum["none"], + selectedRequestAuthType: AuthModeEnum["No Auth"], + }, + createdBy: user.name, + updatedBy: user.name, + createdAt: new Date(), + updatedAt: new Date(), + }; const method = Object.keys(pathObject)[0].toUpperCase(); const pathItemObject: OperationObject = Object.values(pathObject)[0]; transformedObject.tag = pathItemObject.tags @@ -84,13 +139,11 @@ function transformPathV3( transformedObject.description = pathItemObject.summary || pathItemObject.description || ""; transformedObject.operationId = pathItemObject.operationId; - transformedObject.request = {}; transformedObject.request.method = method; // Extract URL path and query parameters const urlParts = pathName.split("/").filter((p) => p != ""); let url = ""; - const queryParams = [] as any; for (let i = 0; i < urlParts.length; i++) { if (urlParts[i].startsWith("{")) { url += "/{" + urlParts[i].slice(1, -1) + "}"; @@ -99,7 +152,7 @@ function transformPathV3( } if (i + 1 < urlParts.length && urlParts[i + 1].includes("=")) { const queryParam = urlParts[i + 1].split("="); - queryParams.push({ + transformedObject.request.queryParams.push({ key: queryParam[0], value: queryParam[1], checked: true, @@ -108,16 +161,6 @@ function transformPathV3( } } transformedObject.request.url = url; - transformedObject.request.queryParams = queryParams; - transformedObject.request.pathParams = []; - - // Handle request body based on schema - transformedObject.request.body = {}; - transformedObject.request.body.raw = ""; - transformedObject.request.body.formdata = {}; - transformedObject.request.body.formdata.file = []; - transformedObject.request.body.formdata.text = []; - transformedObject.request.body.urlencoded = []; const content = pathItemObject?.requestBody?.content; if (content) { @@ -138,7 +181,8 @@ function transformPathV3( } transformedObject.request.body.raw = JSON.stringify(bodyObject); } - transformedObject.request.selectedRequestBodyType = "application/json"; + transformedObject.request.selectedRequestBodyType = + BodyModeEnum["application/json"]; } if (key === "application/x-www-form-urlencoded") { const schema = content[key].schema; @@ -158,10 +202,9 @@ function transformPathV3( } } transformedObject.request.selectedRequestBodyType = - "application/x-www-form-urlencoded"; + BodyModeEnum["application/x-www-form-urlencoded"]; } if (key === "application/octet-stream") { - transformedObject.request.body.formdata.file = []; transformedObject.request.body.formdata.file.push({ key: "file", value: "", @@ -169,7 +212,7 @@ function transformPathV3( base: "#@#", }); transformedObject.request.selectedRequestBodyType = - "multipart/form-data"; + BodyModeEnum["multipart/form-data"]; } if (key === "multipart/form-data") { const schema = content[key].schema; @@ -189,46 +232,33 @@ function transformPathV3( key: propertyName, value: getExampleValue(property.format), checked: false, - base: "#@#" + "", }); } } } } transformedObject.request.selectedRequestBodyType = - "multipart/form-data"; + BodyModeEnum["multipart/form-data"]; } } } - // Handle headers based on schema - transformedObject.request.headers = []; - - // Handle authentication based on schema - transformedObject.request.auth = {}; - if (security.api_key) { - transformedObject.request.auth = { - apiKey: { - authKey: security.api_key.name, - authValue: "", - addTo: "", - }, - }; + transformedObject.request.auth.apiKey.authKey = security.api_key.name; if (security.api_key.in === "header") { transformedObject.request.headers.push({ key: security.api_key.name, value: "", checked: false, }); - transformedObject.request.auth.apiKey.addTo = "Header"; + transformedObject.request.auth.apiKey.addTo = AddTo.Header; } else if (security.api_key.in === "query") { transformedObject.request.queryParams.push({ key: security.api_key.name, value: "", checked: false, }); - transformedObject.request.auth.apiKey.addTo = "Query Parameters"; + transformedObject.request.auth.apiKey.addTo = AddTo.QueryParameter; } } @@ -254,35 +284,45 @@ function transformPathV3( checked: false, }); break; - case "path": - transformedObject.request.pathParams.push({ - key: paramName, - value: paramValue, - checked: false, - }); - break; } } + //Assign default values + if (!transformedObject.request.headers.length) { + transformedObject.request.headers.push(keyValueDefaultObj); + } + if (!transformedObject.request.queryParams.length) { + transformedObject.request.queryParams.push(keyValueDefaultObj); + } + if (!transformedObject.request.body.formdata.text.length) { + transformedObject.request.body.formdata.text.push(keyValueDefaultObj); + } + if (!transformedObject.request.body.formdata.file.length) { + transformedObject.request.body.formdata.file.push(formDataFileDefaultObj); + } + if (!transformedObject.request.body.urlencoded.length) { + transformedObject.request.body.urlencoded.push(keyValueDefaultObj); + } + return transformedObject; } -function getExampleValue(exampleType: string) { +export function getExampleValue(exampleType: string) { switch (exampleType) { case "string": - return ""; // Or a default string value + return ""; case "number": - return 0; // Or a default number value + return 0; case "integer": - return 0; // Or a default number value + return 0; case "boolean": - return false; // Or a default boolean value + return false; case "array": - return []; // Empty array + return []; case "object": - return {}; // Empty object + return {}; default: - return ""; // Or a generic default value + return ""; } } diff --git a/src/modules/common/services/parser.service.ts b/src/modules/common/services/parser.service.ts index 98e09c73..8bdbe1d8 100644 --- a/src/modules/common/services/parser.service.ts +++ b/src/modules/common/services/parser.service.ts @@ -90,40 +90,19 @@ export class ParserService { items = mergedFolderItems; } } - const newItems: CollectionItem[] = []; - for (let x = 0; x < items?.length; x++) { - const itemsObj: CollectionItem = { - name: items[x].name, - description: items[x].description, - id: items[x].id, - type: items[x].type, - isDeleted: items[x].isDeleted, - source: SourceTypeEnum.SPEC, - createdBy: user.name, - updatedBy: user.name, - createdAt: new Date(), - updatedAt: new Date(), - }; - const innerArray: CollectionItem[] = []; - for (let y = 0; y < items[x].items?.length; y++) { - const data = this.handleCircularReference(items[x].items[y]); - innerArray.push(JSON.parse(data)); - } - itemsObj.items = innerArray; - newItems.push(itemsObj); - } const collection: Collection = { name: openApiDocument.info.title, + description: openApiDocument.info.description, totalRequests, - items: newItems, + items: items, uuid: openApiDocument.info.title, - createdBy: user.name, - updatedBy: user.name, activeSync, activeSyncUrl: activeSyncUrl ?? "", createdAt: new Date(), updatedAt: new Date(), + createdBy: user.name, + updatedBy: user.name, }; if (existingCollection) { @@ -138,34 +117,20 @@ export class ParserService { collection: updatedCollection, existingCollection: true, }; - } else { - const newCollection = await this.collectionService.importCollection( - collection, - ); - const collectionDetails = await this.collectionService.getCollection( - newCollection.insertedId.toString(), - ); - collectionDetails; - return { - collection: collectionDetails, - existingCollection: false, - }; } + const newCollection = await this.collectionService.importCollection( + collection, + ); + const collectionDetails = await this.collectionService.getCollection( + newCollection.insertedId.toString(), + ); + collectionDetails; + return { + collection: collectionDetails, + existingCollection: false, + }; } - handleCircularReference(obj: CollectionItem) { - const cache: any = []; - return JSON.stringify(obj, function (key, value) { - if (typeof value === "object" && value !== null) { - if (cache.indexOf(value) !== -1) { - // Circular reference found, replace with undefined - return undefined; - } - // Store value in our collection - cache.push(value); - } - return value; - }); - } + compareAndMerge( existingitems: CollectionItem[], newItems: CollectionItem[], From 0187156a3b4f13d4bb4ef1f94c7b52ba9e0217d0 Mon Sep 17 00:00:00 2001 From: Nayan Lakhwani Date: Fri, 8 Mar 2024 19:39:56 +0530 Subject: [PATCH 16/43] fix: docker build[] --- src/modules/app/app.controller.ts | 6 +++++- src/modules/app/app.service.ts | 7 +++++-- src/modules/app/payloads/app.payload.ts | 3 +++ tsconfig.json | 8 +------- 4 files changed, 14 insertions(+), 10 deletions(-) create mode 100644 src/modules/app/payloads/app.payload.ts diff --git a/src/modules/app/app.controller.ts b/src/modules/app/app.controller.ts index d4d98289..ff8491cb 100644 --- a/src/modules/app/app.controller.ts +++ b/src/modules/app/app.controller.ts @@ -16,6 +16,7 @@ import { import { FastifyReply, FastifyRequest } from "fastify"; import { AppService } from "./app.service"; import { JwtAuthGuard } from "../common/guards/jwt-auth.guard"; +import { ParseCurlBodyPayload } from "./payloads/app.payload"; /** * App Controller @@ -60,7 +61,10 @@ export class AppController { description: "Curl parsed successfully", }) @UseGuards(JwtAuthGuard) - async parseCurl(@Res() res: FastifyReply, @Req() req: FastifyRequest) { + async parseCurl( + @Res() res: FastifyReply, + @Req() req: FastifyRequest<{ Body: ParseCurlBodyPayload }>, + ) { const parsedRequestData = await this.appService.parseCurl(req); return res.status(200).send(parsedRequestData); } diff --git a/src/modules/app/app.service.ts b/src/modules/app/app.service.ts index 08ba7984..7c942652 100644 --- a/src/modules/app/app.service.ts +++ b/src/modules/app/app.service.ts @@ -14,6 +14,7 @@ import { TransformedRequest, } from "../common/models/collection.rxdb.model"; import { ContextService } from "../common/services/context.service"; +import { ParseCurlBodyPayload } from "./payloads/app.payload"; /** * Application Service @@ -70,11 +71,13 @@ export class AppService { }; } - async parseCurl(req: FastifyRequest): Promise { + async parseCurl( + req: FastifyRequest<{ Body: ParseCurlBodyPayload }>, + ): Promise { try { const curlconverter = await this.importCurlConverter(); const { toJsonString } = curlconverter; - const curl = req.headers?.curl; + const curl = req.body?.curl; if (!curl || !curl.length) { throw new Error(); } diff --git a/src/modules/app/payloads/app.payload.ts b/src/modules/app/payloads/app.payload.ts new file mode 100644 index 00000000..86b12328 --- /dev/null +++ b/src/modules/app/payloads/app.payload.ts @@ -0,0 +1,3 @@ +export interface ParseCurlBodyPayload { + curl: string; +} diff --git a/tsconfig.json b/tsconfig.json index 92e2a44a..00bb8253 100755 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,6 +1,6 @@ { "compilerOptions": { - "module": "commonjs", + "module": "nodenext", "declaration": true, "removeComments": true, "noLib": false, @@ -29,11 +29,5 @@ }, "resolveJsonModule": true }, - "ts-node": { - "esm": true, - "compilerOptions": { - "module": "nodenext" - } - }, "exclude": ["node_modules", "./dist/**/*"] } \ No newline at end of file From caef3ebdef16a2314541181ac831625d142d1279 Mon Sep 17 00:00:00 2001 From: Nayan Lakhwani Date: Fri, 8 Mar 2024 19:55:00 +0530 Subject: [PATCH 17/43] fix: docker build[] --- src/modules/app/app.controller.ts | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/modules/app/app.controller.ts b/src/modules/app/app.controller.ts index ff8491cb..f5b52f36 100644 --- a/src/modules/app/app.controller.ts +++ b/src/modules/app/app.controller.ts @@ -9,7 +9,8 @@ import { } from "@nestjs/common"; import { ApiBearerAuth, - ApiHeader, + ApiBody, + ApiConsumes, ApiOperation, ApiResponse, } from "@nestjs/swagger"; @@ -47,11 +48,6 @@ export class AppController { } @Post("curl") - @ApiHeader({ - name: "curl", - description: "Pass in the curl command.", - allowEmptyValue: false, - }) @ApiOperation({ summary: "Parse Curl", description: "Parses the provided curl into Sparrow api request schema", @@ -60,6 +56,17 @@ export class AppController { status: 200, description: "Curl parsed successfully", }) + @ApiConsumes("application/x-www-form-urlencoded") + @ApiBody({ + schema: { + properties: { + curl: { + type: "string", + example: "Use sparrow to hit this request", + }, + }, + }, + }) @UseGuards(JwtAuthGuard) async parseCurl( @Res() res: FastifyReply, From 069b2c6b8475702557f5c7fbb8d19adfad9b1ada Mon Sep 17 00:00:00 2001 From: Astitva877 Date: Sat, 9 Mar 2024 11:35:39 +0530 Subject: [PATCH 18/43] feat: added branch model for import url [] --- .../common/enum/database.collection.enum.ts | 1 + src/modules/common/models/branch.model.ts | 40 ++++++++ src/modules/common/models/collection.model.ts | 24 +++++ src/modules/common/services/parser.service.ts | 96 ++++++++++++++++--- .../controllers/workspace.controller.ts | 1 + .../workspace/payloads/branch.payload.ts | 17 ++++ .../workspace/payloads/collection.payload.ts | 5 + .../repositories/branch.repository.ts | 22 +++++ .../workspace/services/branch.service.ts | 35 +++++++ .../workspace/services/collection.service.ts | 14 ++- src/modules/workspace/workspace.module.ts | 6 ++ 11 files changed, 245 insertions(+), 16 deletions(-) create mode 100644 src/modules/common/models/branch.model.ts create mode 100644 src/modules/workspace/payloads/branch.payload.ts create mode 100644 src/modules/workspace/repositories/branch.repository.ts create mode 100644 src/modules/workspace/services/branch.service.ts diff --git a/src/modules/common/enum/database.collection.enum.ts b/src/modules/common/enum/database.collection.enum.ts index 0cd49f88..32660c74 100644 --- a/src/modules/common/enum/database.collection.enum.ts +++ b/src/modules/common/enum/database.collection.enum.ts @@ -6,4 +6,5 @@ export enum Collections { EARLYACCESS = "earlyaccess", ENVIRONMENT = "environment", FEATURES = "features", + BRANCHES = "branches", } diff --git a/src/modules/common/models/branch.model.ts b/src/modules/common/models/branch.model.ts new file mode 100644 index 00000000..6e456208 --- /dev/null +++ b/src/modules/common/models/branch.model.ts @@ -0,0 +1,40 @@ +import { Type } from "class-transformer"; +import { + IsArray, + IsDate, + IsNotEmpty, + IsOptional, + IsString, + ValidateNested, +} from "class-validator"; +import { ApiProperty } from "@nestjs/swagger"; +import { CollectionItem } from "./collection.model"; + +export class Branch { + @ApiProperty() + @IsString() + @IsNotEmpty() + name: string; + + @ApiProperty({ type: [CollectionItem] }) + @IsArray() + @ValidateNested({ each: true }) + @Type(() => CollectionItem) + items: CollectionItem[]; + + @IsDate() + @IsOptional() + createdAt?: Date; + + @IsDate() + @IsOptional() + updatedAt?: Date; + + @IsString() + @IsOptional() + createdBy?: string; + + @IsString() + @IsOptional() + updatedBy?: string; +} diff --git a/src/modules/common/models/collection.model.ts b/src/modules/common/models/collection.model.ts index addc3717..be09aefb 100644 --- a/src/modules/common/models/collection.model.ts +++ b/src/modules/common/models/collection.model.ts @@ -243,6 +243,18 @@ export class CollectionItem { updatedBy: string; } +export class Branches { + @ApiProperty({ example: "64f878a0293b1e4415866493" }) + @IsString() + @IsNotEmpty() + id: string; + + @ApiProperty() + @IsString() + @IsNotEmpty() + name: string; +} + export class Collection { @ApiProperty() @IsString() @@ -254,6 +266,11 @@ export class Collection { @IsNotEmpty() description?: string; + @ApiProperty() + @IsString() + @IsOptional() + primaryBranch?: string; + @ApiProperty() @IsNumber() @IsNotEmpty() @@ -280,6 +297,13 @@ export class Collection { @IsOptional() activeSyncUrl?: string; + @ApiProperty({ type: [Branches] }) + @IsArray() + @IsOptional() + @ValidateNested({ each: true }) + @Type(() => Branches) + allBranches?: Branches[]; + @IsOptional() @IsDateString() createdAt?: Date; diff --git a/src/modules/common/services/parser.service.ts b/src/modules/common/services/parser.service.ts index 8bdbe1d8..975ca216 100644 --- a/src/modules/common/services/parser.service.ts +++ b/src/modules/common/services/parser.service.ts @@ -14,12 +14,14 @@ import { resolveAllRefs } from "./helper/parser.helper"; import { OpenAPI20 } from "../models/openapi20.model"; import * as oapi2Transformer from "./helper/oapi2.transformer"; import * as oapi3Transformer from "./helper/oapi3.transformer"; +import { BranchService } from "@src/modules/workspace/services/branch.service"; @Injectable() export class ParserService { constructor( private readonly contextService: ContextService, private readonly collectionService: CollectionService, + private readonly branchService: BranchService, ) {} async parse( @@ -27,6 +29,7 @@ export class ParserService { activeSync?: boolean, workspaceId?: string, activeSyncUrl?: string, + primaryBranch?: string, ): Promise<{ collection: WithId; existingCollection: boolean; @@ -62,7 +65,9 @@ export class ParserService { items.map((itemObj) => { totalRequests = totalRequests + itemObj.items?.length; }); - + let branch; + let collection: Collection; + let existingBranch; if (activeSync) { let mergedFolderItems: CollectionItem[] = []; existingCollection = @@ -88,23 +93,84 @@ export class ParserService { mergedFolderItems[x].items = mergedFolderRequests; } items = mergedFolderItems; + existingBranch = await this.collectionService.getActiveSyncedBranch( + existingCollection._id.toString(), + openApiDocument.info.title, + ); + if (existingBranch) { + branch = { + insertedId: existingBranch.id, + name: existingBranch.name, + }; + } else { + branch = await this.branchService.createBranch({ + name: openApiDocument.info.title, + items: items, + }); + } + collection = { + name: openApiDocument.info.title, + description: openApiDocument.info.description, + primaryBranch: primaryBranch ?? "", + totalRequests, + items: items, + allBranches: [ + { + id: branch.insertedId.toString(), + name: openApiDocument.info.title, + }, + ], + uuid: openApiDocument.info.title, + activeSync, + activeSyncUrl: activeSyncUrl ?? "", + createdAt: new Date(), + updatedAt: new Date(), + createdBy: user.name, + updatedBy: user.name, + }; + } else { + branch = await this.branchService.createBranch({ + name: openApiDocument.info.title, + items: items, + }); + collection = { + name: openApiDocument.info.title, + description: openApiDocument.info.description, + primaryBranch: primaryBranch ?? "", + totalRequests, + items: items, + allBranches: [ + { + id: branch.insertedId.toString(), + name: openApiDocument.info.title, + }, + ], + uuid: openApiDocument.info.title, + activeSync, + activeSyncUrl: activeSyncUrl ?? "", + createdAt: new Date(), + updatedAt: new Date(), + createdBy: user.name, + updatedBy: user.name, + }; } + } else { + collection = { + name: openApiDocument.info.title, + description: openApiDocument.info.description, + primaryBranch: primaryBranch ?? "", + totalRequests, + items: items, + uuid: openApiDocument.info.title, + activeSync, + activeSyncUrl: activeSyncUrl ?? "", + createdAt: new Date(), + updatedAt: new Date(), + createdBy: user.name, + updatedBy: user.name, + }; } - const collection: Collection = { - name: openApiDocument.info.title, - description: openApiDocument.info.description, - totalRequests, - items: items, - uuid: openApiDocument.info.title, - activeSync, - activeSyncUrl: activeSyncUrl ?? "", - createdAt: new Date(), - updatedAt: new Date(), - createdBy: user.name, - updatedBy: user.name, - }; - if (existingCollection) { await this.collectionService.updateImportedCollection( existingCollection._id.toString(), diff --git a/src/modules/workspace/controllers/workspace.controller.ts b/src/modules/workspace/controllers/workspace.controller.ts index 72df9bcb..72d9b98c 100644 --- a/src/modules/workspace/controllers/workspace.controller.ts +++ b/src/modules/workspace/controllers/workspace.controller.ts @@ -371,6 +371,7 @@ export class WorkSpaceController { activeSync, workspaceId, importCollectionDto.url, + importCollectionDto?.primaryBranch, ); if (!collectionObj.existingCollection) { await this.workspaceService.addCollectionInWorkSpace(workspaceId, { diff --git a/src/modules/workspace/payloads/branch.payload.ts b/src/modules/workspace/payloads/branch.payload.ts new file mode 100644 index 00000000..3d4629ac --- /dev/null +++ b/src/modules/workspace/payloads/branch.payload.ts @@ -0,0 +1,17 @@ +import { ApiProperty } from "@nestjs/swagger"; +import { CollectionItem } from "@src/modules/common/models/collection.model"; +import { Type } from "class-transformer"; +import { IsArray, IsNotEmpty, IsString, ValidateNested } from "class-validator"; + +export class createBranchDto { + @IsString() + @ApiProperty({ required: true, example: "Branch name" }) + @IsNotEmpty() + name: string; + + @ApiProperty({ type: [CollectionItem] }) + @IsArray() + @ValidateNested({ each: true }) + @Type(() => CollectionItem) + items: CollectionItem[]; +} diff --git a/src/modules/workspace/payloads/collection.payload.ts b/src/modules/workspace/payloads/collection.payload.ts index baec2bc5..607584b7 100644 --- a/src/modules/workspace/payloads/collection.payload.ts +++ b/src/modules/workspace/payloads/collection.payload.ts @@ -125,4 +125,9 @@ export class ImportCollectionDto { }) @IsBoolean() activeSync?: boolean; + + @ApiProperty({ example: "development" }) + @IsString() + @IsOptional() + primaryBranch?: string; } diff --git a/src/modules/workspace/repositories/branch.repository.ts b/src/modules/workspace/repositories/branch.repository.ts new file mode 100644 index 00000000..6dfa4a35 --- /dev/null +++ b/src/modules/workspace/repositories/branch.repository.ts @@ -0,0 +1,22 @@ +import { Inject, Injectable } from "@nestjs/common"; + +import { Db, InsertOneResult } from "mongodb"; + +import { Collections } from "@src/modules/common/enum/database.collection.enum"; +import { ContextService } from "@src/modules/common/services/context.service"; +import { Branch } from "@src/modules/common/models/branch.model"; + +@Injectable() +export class BranchRepository { + constructor( + @Inject("DATABASE_CONNECTION") private db: Db, + private readonly contextService: ContextService, + ) {} + + async addBranch(branch: Branch): Promise> { + const response = await this.db + .collection(Collections.BRANCHES) + .insertOne(branch); + return response; + } +} diff --git a/src/modules/workspace/services/branch.service.ts b/src/modules/workspace/services/branch.service.ts new file mode 100644 index 00000000..a148e0f7 --- /dev/null +++ b/src/modules/workspace/services/branch.service.ts @@ -0,0 +1,35 @@ +import { Injectable } from "@nestjs/common"; +import { InsertOneResult } from "mongodb"; +import { ContextService } from "@src/modules/common/services/context.service"; +import { createBranchDto } from "../payloads/branch.payload"; +import { Branch } from "@src/modules/common/models/branch.model"; +import { BranchRepository } from "../repositories/branch.repository"; + +/** + * Branch Service + */ + +@Injectable() +export class BranchService { + constructor( + private readonly contextService: ContextService, + // private readonly featureRepository: FeatureRepository, + private readonly branchRepository: BranchRepository, + ) {} + + async createBranch( + createBranchDto: createBranchDto, + ): Promise> { + const user = this.contextService.get("user"); + const newBranch: Branch = { + name: createBranchDto.name, + items: createBranchDto.items, + createdBy: user._id, + updatedBy: user._id, + createdAt: new Date(), + updatedAt: new Date(), + }; + const branch = await this.branchRepository.addBranch(newBranch); + return branch; + } +} diff --git a/src/modules/workspace/services/collection.service.ts b/src/modules/workspace/services/collection.service.ts index 6138db33..586d1391 100644 --- a/src/modules/workspace/services/collection.service.ts +++ b/src/modules/workspace/services/collection.service.ts @@ -13,7 +13,10 @@ import { UpdateResult, WithId, } from "mongodb"; -import { Collection } from "@src/modules/common/models/collection.model"; +import { + Branches, + Collection, +} from "@src/modules/common/models/collection.model"; import { ContextService } from "@src/modules/common/services/context.service"; import { ErrorMessages } from "@src/modules/common/enum/error-messages.enum"; import { WorkspaceService } from "./workspace.service"; @@ -80,6 +83,15 @@ export class CollectionService { ); } + async getActiveSyncedBranch(id: string, name: string): Promise { + const collection = await this.getCollection(id); + for (const branch of collection.allBranches) { + if (branch.name === name) { + return branch; + } + } + } + async checkPermission(workspaceId: string, userid: ObjectId): Promise { const workspace = await this.workspaceReposistory.get(workspaceId); const hasPermission = workspace.users.some((user) => { diff --git a/src/modules/workspace/workspace.module.ts b/src/modules/workspace/workspace.module.ts index 5c9bf4ed..f4cde875 100644 --- a/src/modules/workspace/workspace.module.ts +++ b/src/modules/workspace/workspace.module.ts @@ -19,6 +19,8 @@ import { DemoteAdminHandler } from "./handlers/demoteAdmin.handlers"; import { FeatureController } from "./controllers/feature.controller"; import { FeatureService } from "./services/feature.service"; import { FeatureRepository } from "./repositories/feature.repository"; +import { BranchService } from "./services/branch.service"; +import { BranchRepository } from "./repositories/branch.repository"; @Module({ imports: [IdentityModule], providers: [ @@ -37,6 +39,8 @@ import { FeatureRepository } from "./repositories/feature.repository"; EnvironmentRepository, FeatureService, FeatureRepository, + BranchService, + BranchRepository, ], exports: [ CollectionService, @@ -46,6 +50,8 @@ import { FeatureRepository } from "./repositories/feature.repository"; EnvironmentRepository, FeatureService, FeatureRepository, + BranchService, + BranchRepository, ], controllers: [ WorkSpaceController, From c71b28906089c8edafb0109e1f8c7f208513363a Mon Sep 17 00:00:00 2001 From: Nayan Lakhwani Date: Mon, 11 Mar 2024 00:11:07 +0530 Subject: [PATCH 19/43] feat: added branch logic[] --- src/modules/app/app.controller.ts | 63 +++- src/modules/common/models/branch.model.ts | 7 + src/modules/common/models/collection.model.ts | 9 +- .../common/services/helper/parser.helper.ts | 16 +- src/modules/common/services/parser.service.ts | 302 ++++++++++++------ .../controllers/collection.controller.ts | 24 ++ .../controllers/workspace.controller.ts | 1 + .../workspace/payloads/branch.payload.ts | 5 + .../workspace/payloads/collection.payload.ts | 20 +- .../repositories/branch.repository.ts | 37 ++- .../repositories/collection.repository.ts | 24 ++ .../workspace/services/branch.service.ts | 21 +- .../workspace/services/collection.service.ts | 67 +++- 13 files changed, 468 insertions(+), 128 deletions(-) diff --git a/src/modules/app/app.controller.ts b/src/modules/app/app.controller.ts index d4d98289..35c6782f 100644 --- a/src/modules/app/app.controller.ts +++ b/src/modules/app/app.controller.ts @@ -1,6 +1,8 @@ import { + Body, Controller, Get, + HttpStatus, Param, Post, Req, @@ -16,6 +18,11 @@ import { import { FastifyReply, FastifyRequest } from "fastify"; import { AppService } from "./app.service"; import { JwtAuthGuard } from "../common/guards/jwt-auth.guard"; +import { BodyModeEnum } from "../common/models/collection.model"; +import * as yml from "js-yaml"; +import axios from "axios"; +import { ParserService } from "../common/services/parser.service"; +import { ValidateOapiPayload } from "../workspace/payloads/collection.payload"; /** * App Controller @@ -23,7 +30,10 @@ import { JwtAuthGuard } from "../common/guards/jwt-auth.guard"; @ApiBearerAuth() @Controller() export class AppController { - constructor(private appService: AppService) {} + constructor( + private parserService: ParserService, + private appService: AppService, + ) {} @Get("updater/:target/:arch/:currentVersion") @ApiOperation({ @@ -64,4 +74,55 @@ export class AppController { const parsedRequestData = await this.appService.parseCurl(req); return res.status(200).send(parsedRequestData); } + + @Post("/validate/oapi") + @ApiHeader({ + name: "x-oapi-url", + description: "Pass in the curl command.", + allowEmptyValue: false, + }) + @ApiOperation({ + summary: "Validate JSON/YAML/URL OAPI specification", + description: "You can import a collection from jsonObj", + }) + @ApiResponse({ + status: 200, + description: "Provided OAPI is a valid specification.", + }) + @ApiResponse({ status: 400, description: "Provided OAPI is invalid." }) + async validateOAPI( + @Req() request: FastifyRequest, + @Res() res: FastifyReply, + @Body() body?: ValidateOapiPayload, + ) { + try { + let data: any; + const url = request.headers["x-oapi-url"] || null; + if (url) { + const isValidUrl = this.parserService.validateUrlIsALocalhostUrl( + url as string, + ); + if (!isValidUrl) throw new Error(); + const response = await axios.get(url as string); + data = response.data; + } else { + const requestType = request.headers["content-type"]; + if (requestType === BodyModeEnum["application/json"]) { + JSON.parse(body.data); + } else if (requestType === BodyModeEnum["application/yaml"]) { + data = yml.load(body.data); + } else { + throw new Error("Unsupported content type"); + } + } + await this.parserService.validateOapi(data); + return res + .status(HttpStatus.OK) + .send({ valid: true, msg: "Provided OAPI is a valid specification." }); + } catch (error) { + return res + .status(HttpStatus.BAD_REQUEST) + .send({ valid: false, msg: "Provided OAPI is invalid." }); + } + } } diff --git a/src/modules/common/models/branch.model.ts b/src/modules/common/models/branch.model.ts index 6e456208..53657e17 100644 --- a/src/modules/common/models/branch.model.ts +++ b/src/modules/common/models/branch.model.ts @@ -2,6 +2,7 @@ import { Type } from "class-transformer"; import { IsArray, IsDate, + IsMongoId, IsNotEmpty, IsOptional, IsString, @@ -9,6 +10,7 @@ import { } from "class-validator"; import { ApiProperty } from "@nestjs/swagger"; import { CollectionItem } from "./collection.model"; +import { ObjectId } from "mongodb"; export class Branch { @ApiProperty() @@ -16,6 +18,11 @@ export class Branch { @IsNotEmpty() name: string; + @ApiProperty() + @IsMongoId() + @IsNotEmpty() + collectionId: ObjectId; + @ApiProperty({ type: [CollectionItem] }) @IsArray() @ValidateNested({ each: true }) diff --git a/src/modules/common/models/collection.model.ts b/src/modules/common/models/collection.model.ts index be09aefb..a22a5224 100644 --- a/src/modules/common/models/collection.model.ts +++ b/src/modules/common/models/collection.model.ts @@ -24,6 +24,7 @@ export enum BodyModeEnum { "none" = "none", "application/json" = "application/json", "application/xml" = "application/xml", + "application/yaml" = "application/yaml", "application/x-www-form-urlencoded" = "application/x-www-form-urlencoded", "multipart/form-data" = "multipart/form-data", "application/javascript" = "application/javascript", @@ -243,7 +244,7 @@ export class CollectionItem { updatedBy: string; } -export class Branches { +export class CollectionBranch { @ApiProperty({ example: "64f878a0293b1e4415866493" }) @IsString() @IsNotEmpty() @@ -297,12 +298,12 @@ export class Collection { @IsOptional() activeSyncUrl?: string; - @ApiProperty({ type: [Branches] }) + @ApiProperty({ type: [CollectionBranch] }) @IsArray() @IsOptional() @ValidateNested({ each: true }) - @Type(() => Branches) - allBranches?: Branches[]; + @Type(() => CollectionBranch) + branches?: CollectionBranch[]; @IsOptional() @IsDateString() diff --git a/src/modules/common/services/helper/parser.helper.ts b/src/modules/common/services/helper/parser.helper.ts index 038dca8a..26f51c9e 100644 --- a/src/modules/common/services/helper/parser.helper.ts +++ b/src/modules/common/services/helper/parser.helper.ts @@ -6,7 +6,11 @@ export function resolveAllRefs(spec: any) { const resolvedSpec: { [key: string]: any } = {}; for (const key in spec) { if (componentToResolve.schemas) { - resolvedSpec[key] = resolveComponentRef(spec[key], componentToResolve); + resolvedSpec[key] = resolveComponentRef( + spec[key], + componentToResolve, + [], + ); } else { resolvedSpec[key] = resolveDefinitionRef(spec[key], componentToResolve); } @@ -17,7 +21,7 @@ export function resolveAllRefs(spec: any) { return spec; } -function resolveComponentRef(data: any, components: any): any { +function resolveComponentRef(data: any, components: any, cache: any): any { if (!data) return data; if (typeof data === "object") { @@ -30,9 +34,15 @@ function resolveComponentRef(data: any, components: any): any { components.schemas && components.schemas[schemaName] ) { + if (cache.indexOf(schemaName) !== -1) { + // Circular reference found, replace with undefined + return undefined; + } + cache.push(schemaName); return resolveComponentRef( components.schemas[schemaName], components, + cache, ); } else { console.warn(`Reference "${refPath}" not found in components`); @@ -44,7 +54,7 @@ function resolveComponentRef(data: any, components: any): any { } else { const newData: { [key: string]: any } = {}; for (const key in data) { - newData[key] = resolveComponentRef(data[key], components); + newData[key] = resolveComponentRef(data[key], components, cache); } return newData; } diff --git a/src/modules/common/services/parser.service.ts b/src/modules/common/services/parser.service.ts index 975ca216..9aa48a1b 100644 --- a/src/modules/common/services/parser.service.ts +++ b/src/modules/common/services/parser.service.ts @@ -15,6 +15,11 @@ import { OpenAPI20 } from "../models/openapi20.model"; import * as oapi2Transformer from "./helper/oapi2.transformer"; import * as oapi3Transformer from "./helper/oapi3.transformer"; import { BranchService } from "@src/modules/workspace/services/branch.service"; +import { Branch } from "../models/branch.model"; +interface ActiveSyncResponsePayload { + collection: WithId; + existingCollection: boolean; +} @Injectable() export class ParserService { @@ -30,6 +35,7 @@ export class ParserService { workspaceId?: string, activeSyncUrl?: string, primaryBranch?: string, + currentBranch?: string, ): Promise<{ collection: WithId; existingCollection: boolean; @@ -37,7 +43,6 @@ export class ParserService { let openApiDocument = (await SwaggerParser.parse(file)) as | OpenAPI303 | OpenAPI20; - let existingCollection: WithId | null = null; let folderObjMap = new Map(); const user = await this.contextService.get("user"); if (openApiDocument.hasOwnProperty("components")) { @@ -54,7 +59,7 @@ export class ParserService { ); } const itemObject = Object.fromEntries(folderObjMap); - let items: CollectionItem[] = []; + const items: CollectionItem[] = []; let totalRequests = 0; for (const key in itemObject) { if (itemObject.hasOwnProperty(key)) { @@ -65,105 +70,33 @@ export class ParserService { items.map((itemObj) => { totalRequests = totalRequests + itemObj.items?.length; }); - let branch; let collection: Collection; - let existingBranch; + if (activeSync) { - let mergedFolderItems: CollectionItem[] = []; - existingCollection = - await this.collectionService.getActiveSyncedCollection( - openApiDocument.info.title, - workspaceId, - ); - if (existingCollection) { - //check on folder level - mergedFolderItems = this.compareAndMerge( - existingCollection.items, - items, - ); - for (let x = 0; x < existingCollection.items?.length; x++) { - const newItem: CollectionItem[] = items.filter((item) => { - return item.name === existingCollection.items[x].name; - }); - //check on request level - const mergedFolderRequests: CollectionItem[] = this.compareAndMerge( - existingCollection.items[x].items, - newItem[0]?.items || [], - ); - mergedFolderItems[x].items = mergedFolderRequests; - } - items = mergedFolderItems; - existingBranch = await this.collectionService.getActiveSyncedBranch( - existingCollection._id.toString(), - openApiDocument.info.title, - ); - if (existingBranch) { - branch = { - insertedId: existingBranch.id, - name: existingBranch.name, - }; - } else { - branch = await this.branchService.createBranch({ - name: openApiDocument.info.title, - items: items, - }); - } - collection = { - name: openApiDocument.info.title, - description: openApiDocument.info.description, - primaryBranch: primaryBranch ?? "", - totalRequests, - items: items, - allBranches: [ - { - id: branch.insertedId.toString(), - name: openApiDocument.info.title, - }, - ], - uuid: openApiDocument.info.title, - activeSync, - activeSyncUrl: activeSyncUrl ?? "", - createdAt: new Date(), - updatedAt: new Date(), - createdBy: user.name, - updatedBy: user.name, - }; - } else { - branch = await this.branchService.createBranch({ - name: openApiDocument.info.title, - items: items, - }); - collection = { - name: openApiDocument.info.title, - description: openApiDocument.info.description, - primaryBranch: primaryBranch ?? "", - totalRequests, - items: items, - allBranches: [ - { - id: branch.insertedId.toString(), - name: openApiDocument.info.title, - }, - ], - uuid: openApiDocument.info.title, - activeSync, - activeSyncUrl: activeSyncUrl ?? "", - createdAt: new Date(), - updatedAt: new Date(), - createdBy: user.name, - updatedBy: user.name, - }; - } + const { collection, existingCollection } = await this.runActiveSyncFlow( + openApiDocument, + workspaceId, + primaryBranch, + currentBranch, + totalRequests, + activeSyncUrl, + items, + ); + return { + collection, + existingCollection, + }; } else { collection = { name: openApiDocument.info.title, description: openApiDocument.info.description, - primaryBranch: primaryBranch ?? "", + primaryBranch: "", + branches: [], totalRequests, items: items, uuid: openApiDocument.info.title, - activeSync, - activeSyncUrl: activeSyncUrl ?? "", + activeSync: false, + activeSyncUrl: "", createdAt: new Date(), updatedAt: new Date(), createdBy: user.name, @@ -171,32 +104,203 @@ export class ParserService { }; } + const newCollection = await this.collectionService.importCollection( + collection, + ); + const collectionDetails = await this.collectionService.getCollection( + newCollection.insertedId.toString(), + ); + return { + collection: collectionDetails, + existingCollection: false, + }; + } + + async runActiveSyncFlow( + openApiDocument: OpenAPI20 | OpenAPI303, + workspaceId: string, + primaryBranch: string, + currentBranch: string, + totalRequests: number, + activeSyncUrl: string, + items: CollectionItem[], + ): Promise { + const collectionTitle = openApiDocument.info.title; + let mergedFolderItems: CollectionItem[] = []; + const existingCollection = + await this.collectionService.getActiveSyncedCollection( + collectionTitle, + workspaceId, + ); if (existingCollection) { - await this.collectionService.updateImportedCollection( + //Get existing branch or create one + const branch = await this.createOrFetchBranch( + currentBranch, existingCollection._id.toString(), - collection, + workspaceId, + items, + ); + + //Check items on folder level + mergedFolderItems = this.compareAndMerge(branch.items, items); + for (let x = 0; x < branch.items?.length; x++) { + const newItem: CollectionItem[] = items.filter((item) => { + return item.name === branch.items[x].name; + }); + //Check items on request level + const mergedFolderRequests: CollectionItem[] = this.compareAndMerge( + branch.items[x].items, + newItem[0]?.items || [], + ); + mergedFolderItems[x].items = mergedFolderRequests; + } + + this.updateItemsInbranch( + workspaceId, + branch._id.toString(), + mergedFolderItems, ); + + //Update collection Items const updatedCollection = await this.collectionService.getCollection( existingCollection._id.toString(), ); + updatedCollection.items = mergedFolderItems; + + //No need for this as collection will be fetched from branch model + // this.updateItemsInCollection( + // workspaceId, + // existingCollection._id.toString(), + // mergedFolderItems, + // ); + return { collection: updatedCollection, existingCollection: true, }; } - const newCollection = await this.collectionService.importCollection( + const user = await this.contextService.get("user"); + + const collection: Collection = { + name: collectionTitle, + description: openApiDocument.info.description, + primaryBranch: primaryBranch ?? "", + totalRequests, + items: items, + branches: [], + uuid: collectionTitle, + activeSync: true, + activeSyncUrl: activeSyncUrl ?? "", + createdAt: new Date(), + updatedAt: new Date(), + createdBy: user.name, + updatedBy: user.name, + }; + const insertedCollection = await this.collectionService.importCollection( collection, ); - const collectionDetails = await this.collectionService.getCollection( - newCollection.insertedId.toString(), + const collectionId = insertedCollection.insertedId.toString(); + const branch = await this.branchService.createBranch({ + name: currentBranch, + items: items, + collectionId, + }); + + await this.collectionService.updateBranchArray( + collectionId, + { id: branch.insertedId.toString(), name: currentBranch }, + workspaceId, ); - collectionDetails; + return { - collection: collectionDetails, + collection: await this.collectionService.getCollection( + insertedCollection.insertedId.toString(), + ), existingCollection: false, }; } + async createOrFetchBranch( + currentBranch: string, + collectionId: string, + workspaceId: string, + items: CollectionItem[], + ): Promise> { + const existingBranch = await this.collectionService.getActiveSyncedBranch( + collectionId, + currentBranch, + ); + if (existingBranch) { + return existingBranch; + } + const insertedBranch = await this.branchService.createBranch({ + name: currentBranch, + items: items, + collectionId, + }); + const branch = await this.branchService.getBranch( + insertedBranch.insertedId.toString(), + ); + await this.updateBranchInCollection(workspaceId, collectionId, branch); + return branch; + } + + async updateItemsInbranch( + workspaceId: string, + branchId: string, + items: CollectionItem[], + ) { + await this.branchService.updateBranch(workspaceId, branchId, items); + } + + async updateItemsInCollection( + workspaceId: string, + collectionId: string, + items: CollectionItem[], + ) { + await this.collectionService.updateCollection( + collectionId, + { items }, + workspaceId, + ); + } + + async updateBranchInCollection( + workspaceId: string, + collectionId: string, + branch: WithId, + ) { + await this.collectionService.updateBranchArray( + collectionId, + { id: branch._id.toString(), name: branch.name }, + workspaceId, + ); + } + + async validateOapi(data: string): Promise { + try { + (await SwaggerParser.parse(data)) as OpenAPI303 | OpenAPI20; + return; + } catch (err) { + throw new Error("Invalid OAPI."); + } + } + + validateUrlIsALocalhostUrl(url: string): boolean { + const urlObject = new URL(url); // Create a URL object for parsing + + // Check if protocol is http or https (localhost only works with these) + if (!["http:", "https:"].includes(urlObject.protocol)) { + return false; + } + + // Check if hostname is 'localhost' or starts with 127.0.0.1 + return ( + urlObject.hostname === "localhost" || + urlObject.hostname.startsWith("127.0.0.1") + ); + } + compareAndMerge( existingitems: CollectionItem[], newItems: CollectionItem[], diff --git a/src/modules/workspace/controllers/collection.controller.ts b/src/modules/workspace/controllers/collection.controller.ts index 840a16d1..47f1b696 100644 --- a/src/modules/workspace/controllers/collection.controller.ts +++ b/src/modules/workspace/controllers/collection.controller.ts @@ -350,4 +350,28 @@ export class collectionController { ); return res.status(responseData.httpStatusCode).send(responseData); } + + @Get(":collectionId/branch/:branchName") + @ApiOperation({ + summary: "Get collection items as per the branch selected", + description: "Switch branch to get collection of that branch", + }) + @ApiResponse({ status: 201, description: "Branch switched Successfully" }) + @ApiResponse({ status: 400, description: "Failed to switch branch" }) + async switchCollectionBranch( + @Param("collectionId") collectionId: string, + @Param("branchName") branchName: string, + @Res() res: FastifyReply, + ) { + const branch = await this.collectionService.getBranchData( + collectionId, + branchName, + ); + const responseData = new ApiResponseService( + "Branch switched Successfully", + HttpStatusCode.OK, + branch, + ); + return res.status(responseData.httpStatusCode).send(responseData); + } } diff --git a/src/modules/workspace/controllers/workspace.controller.ts b/src/modules/workspace/controllers/workspace.controller.ts index 72d9b98c..e9258637 100644 --- a/src/modules/workspace/controllers/workspace.controller.ts +++ b/src/modules/workspace/controllers/workspace.controller.ts @@ -372,6 +372,7 @@ export class WorkSpaceController { workspaceId, importCollectionDto.url, importCollectionDto?.primaryBranch, + importCollectionDto?.currentBranch, ); if (!collectionObj.existingCollection) { await this.workspaceService.addCollectionInWorkSpace(workspaceId, { diff --git a/src/modules/workspace/payloads/branch.payload.ts b/src/modules/workspace/payloads/branch.payload.ts index 3d4629ac..594d4e22 100644 --- a/src/modules/workspace/payloads/branch.payload.ts +++ b/src/modules/workspace/payloads/branch.payload.ts @@ -9,6 +9,11 @@ export class createBranchDto { @IsNotEmpty() name: string; + @IsString() + @ApiProperty({ required: true, example: "65ed7a82af45cb59f471a983" }) + @IsNotEmpty() + collectionId: string; + @ApiProperty({ type: [CollectionItem] }) @IsArray() @ValidateNested({ each: true }) diff --git a/src/modules/workspace/payloads/collection.payload.ts b/src/modules/workspace/payloads/collection.payload.ts index 607584b7..4979a60d 100644 --- a/src/modules/workspace/payloads/collection.payload.ts +++ b/src/modules/workspace/payloads/collection.payload.ts @@ -8,7 +8,7 @@ import { ValidateNested, IsBoolean, } from "class-validator"; -import { ApiProperty } from "@nestjs/swagger"; +import { ApiProperty, ApiPropertyOptional } from "@nestjs/swagger"; import { CollectionItem, ItemTypeEnum, @@ -130,4 +130,22 @@ export class ImportCollectionDto { @IsString() @IsOptional() primaryBranch?: string; + + @ApiProperty({ example: "feat/onboarding-v2" }) + @IsString() + @IsOptional() + currentBranch?: string; +} + +export class SwitchCollectionBranchDto { + @ApiProperty({ example: "feat/onboarding-v2" }) + @IsString() + @IsNotEmpty() + currentBranch: string; +} + +export class ValidateOapiPayload { + @ApiPropertyOptional() + @IsString() + data?: string; } diff --git a/src/modules/workspace/repositories/branch.repository.ts b/src/modules/workspace/repositories/branch.repository.ts index 6dfa4a35..4e8270cb 100644 --- a/src/modules/workspace/repositories/branch.repository.ts +++ b/src/modules/workspace/repositories/branch.repository.ts @@ -1,10 +1,11 @@ import { Inject, Injectable } from "@nestjs/common"; -import { Db, InsertOneResult } from "mongodb"; +import { Db, InsertOneResult, ObjectId, WithId } from "mongodb"; import { Collections } from "@src/modules/common/enum/database.collection.enum"; import { ContextService } from "@src/modules/common/services/context.service"; import { Branch } from "@src/modules/common/models/branch.model"; +import { CollectionItem } from "@src/modules/common/models/collection.model"; @Injectable() export class BranchRepository { @@ -19,4 +20,38 @@ export class BranchRepository { .insertOne(branch); return response; } + async updateBranch(branchId: string, items: CollectionItem[]): Promise { + const defaultParams = { + updatedAt: new Date(), + updatedBy: this.contextService.get("user")._id, + }; + await this.db.collection(Collections.BRANCHES).updateOne( + { + _id: new Object(branchId), + }, + { + $set: { + items, + ...defaultParams, + }, + }, + ); + } + async getBranch(branchId: string): Promise> { + const response = await this.db + .collection(Collections.BRANCHES) + .findOne({ _id: new ObjectId(branchId) }); + return response; + } + + async getBranchByCollection( + collectionId: string, + branchName: string, + ): Promise> { + const collectionObjectId = new ObjectId(collectionId); + const response = await this.db + .collection(Collections.BRANCHES) + .findOne({ collectionId: collectionObjectId, name: branchName }); + return response; + } } diff --git a/src/modules/workspace/repositories/collection.repository.ts b/src/modules/workspace/repositories/collection.repository.ts index 67a3011f..825fc440 100644 --- a/src/modules/workspace/repositories/collection.repository.ts +++ b/src/modules/workspace/repositories/collection.repository.ts @@ -12,6 +12,7 @@ import { import { Collections } from "@src/modules/common/enum/database.collection.enum"; import { ContextService } from "@src/modules/common/services/context.service"; import { + CollectionBranch, Collection, CollectionItem, ItemTypeEnum, @@ -62,6 +63,29 @@ export class CollectionRepository { ); return data; } + + async updateBranchArray( + id: string, + branch: CollectionBranch, + ): Promise { + const collectionId = new ObjectId(id); + const defaultParams = { + updatedAt: new Date(), + updatedBy: this.contextService.get("user")._id, + }; + const data = await this.db.collection(Collections.COLLECTION).updateOne( + { _id: collectionId }, + { + $push: { + branches: branch, + }, + $set: { + ...defaultParams, + }, + }, + ); + return data; + } async delete(id: string): Promise { const _id = new ObjectId(id); const data = await this.db diff --git a/src/modules/workspace/services/branch.service.ts b/src/modules/workspace/services/branch.service.ts index a148e0f7..0e0c9178 100644 --- a/src/modules/workspace/services/branch.service.ts +++ b/src/modules/workspace/services/branch.service.ts @@ -1,9 +1,11 @@ import { Injectable } from "@nestjs/common"; -import { InsertOneResult } from "mongodb"; +import { InsertOneResult, ObjectId, WithId } from "mongodb"; import { ContextService } from "@src/modules/common/services/context.service"; import { createBranchDto } from "../payloads/branch.payload"; import { Branch } from "@src/modules/common/models/branch.model"; import { BranchRepository } from "../repositories/branch.repository"; +import { CollectionItem } from "@src/modules/common/models/collection.model"; +import { WorkspaceService } from "./workspace.service"; /** * Branch Service @@ -13,8 +15,8 @@ import { BranchRepository } from "../repositories/branch.repository"; export class BranchService { constructor( private readonly contextService: ContextService, - // private readonly featureRepository: FeatureRepository, private readonly branchRepository: BranchRepository, + private readonly workspaceService: WorkspaceService, ) {} async createBranch( @@ -24,6 +26,7 @@ export class BranchService { const newBranch: Branch = { name: createBranchDto.name, items: createBranchDto.items, + collectionId: new ObjectId(createBranchDto.collectionId), createdBy: user._id, updatedBy: user._id, createdAt: new Date(), @@ -32,4 +35,18 @@ export class BranchService { const branch = await this.branchRepository.addBranch(newBranch); return branch; } + + async updateBranch( + workspaceId: string, + branchId: string, + items: CollectionItem[], + ): Promise { + await this.workspaceService.IsWorkspaceAdminOrEditor(workspaceId); + await this.branchRepository.updateBranch(branchId, items); + } + + async getBranch(branchId: string): Promise> { + const branch = await this.branchRepository.getBranch(branchId); + return branch; + } } diff --git a/src/modules/workspace/services/collection.service.ts b/src/modules/workspace/services/collection.service.ts index 586d1391..6ab0fb2d 100644 --- a/src/modules/workspace/services/collection.service.ts +++ b/src/modules/workspace/services/collection.service.ts @@ -14,18 +14,21 @@ import { WithId, } from "mongodb"; import { - Branches, Collection, + CollectionBranch, } from "@src/modules/common/models/collection.model"; import { ContextService } from "@src/modules/common/services/context.service"; import { ErrorMessages } from "@src/modules/common/enum/error-messages.enum"; import { WorkspaceService } from "./workspace.service"; +import { BranchRepository } from "../repositories/branch.repository"; +import { Branch } from "@src/modules/common/models/branch.model"; @Injectable() export class CollectionService { constructor( - private readonly collectionReposistory: CollectionRepository, - private readonly workspaceReposistory: WorkspaceRepository, + private readonly collectionRepository: CollectionRepository, + private readonly workspaceRepository: WorkspaceRepository, + private readonly branchRepository: BranchRepository, private readonly contextService: ContextService, private readonly workspaceService: WorkspaceService, ) {} @@ -48,24 +51,24 @@ export class CollectionService { createdAt: new Date(), updatedAt: new Date(), }; - const collection = await this.collectionReposistory.addCollection( + const collection = await this.collectionRepository.addCollection( newCollection, ); return collection; } async getCollection(id: string): Promise> { - return await this.collectionReposistory.get(id); + return await this.collectionRepository.get(id); } async getAllCollections(id: string): Promise[]> { const user = await this.contextService.get("user"); await this.checkPermission(id, user._id); - const workspace = await this.workspaceReposistory.get(id); + const workspace = await this.workspaceRepository.get(id); const collections = []; for (let i = 0; i < workspace.collection?.length; i++) { - const collection = await this.collectionReposistory.get( + const collection = await this.collectionRepository.get( workspace.collection[i].id.toString(), ); collections.push(collection); @@ -77,23 +80,27 @@ export class CollectionService { title: string, workspaceId: string, ): Promise> { - return await this.collectionReposistory.getActiveSyncedCollection( + return await this.collectionRepository.getActiveSyncedCollection( title, workspaceId, ); } - async getActiveSyncedBranch(id: string, name: string): Promise { + async getActiveSyncedBranch( + id: string, + name: string, + ): Promise | null> { const collection = await this.getCollection(id); - for (const branch of collection.allBranches) { + for (const branch of collection.branches) { if (branch.name === name) { - return branch; + return await this.branchRepository.getBranch(branch.id); } } + return null; } async checkPermission(workspaceId: string, userid: ObjectId): Promise { - const workspace = await this.workspaceReposistory.get(workspaceId); + const workspace = await this.workspaceRepository.get(workspaceId); const hasPermission = workspace.users.some((user) => { return user.id.toString() === userid.toString(); }); @@ -109,14 +116,30 @@ export class CollectionService { await this.workspaceService.IsWorkspaceAdminOrEditor(workspaceId); const user = await this.contextService.get("user"); await this.checkPermission(workspaceId, user._id); - await this.collectionReposistory.get(collectionId); - const data = await this.collectionReposistory.update( + await this.collectionRepository.get(collectionId); + const data = await this.collectionRepository.update( collectionId, updateCollectionDto, ); return data; } + async updateBranchArray( + collectionId: string, + branch: CollectionBranch, + workspaceId: string, + ): Promise { + await this.workspaceService.IsWorkspaceAdminOrEditor(workspaceId); + const user = await this.contextService.get("user"); + await this.checkPermission(workspaceId, user._id); + await this.collectionRepository.get(collectionId); + const data = await this.collectionRepository.updateBranchArray( + collectionId, + branch, + ); + return data; + } + async deleteCollection( id: string, workspaceId: string, @@ -124,16 +147,26 @@ export class CollectionService { await this.workspaceService.IsWorkspaceAdminOrEditor(workspaceId); const user = await this.contextService.get("user"); await this.checkPermission(workspaceId, user._id); - const data = await this.collectionReposistory.delete(id); + const data = await this.collectionRepository.delete(id); return data; } async importCollection(collection: Collection): Promise { - return await this.collectionReposistory.addCollection(collection); + return await this.collectionRepository.addCollection(collection); } async updateImportedCollection( id: string, collection: Collection, ): Promise> { - return await this.collectionReposistory.updateCollection(id, collection); + return await this.collectionRepository.updateCollection(id, collection); + } + + async getBranchData( + collectionId: string, + branchName: string, + ): Promise | void> { + return await this.branchRepository.getBranchByCollection( + collectionId, + branchName, + ); } } From 5a72e10581e389d2d610ead7f3fe3b2db4b4ebc9 Mon Sep 17 00:00:00 2001 From: Nayan Lakhwani Date: Mon, 11 Mar 2024 16:14:51 +0530 Subject: [PATCH 20/43] fix: validate oapi api[] --- src/modules/app/app.controller.ts | 60 ++++++++----------- src/modules/app/app.service.ts | 7 ++- src/modules/app/payloads/app.payload.ts | 3 + src/modules/common/services/parser.service.ts | 24 +++++++- .../workspace/payloads/collection.payload.ts | 8 +-- tsconfig.json | 8 +-- 6 files changed, 56 insertions(+), 54 deletions(-) create mode 100644 src/modules/app/payloads/app.payload.ts diff --git a/src/modules/app/app.controller.ts b/src/modules/app/app.controller.ts index 35c6782f..06086f26 100644 --- a/src/modules/app/app.controller.ts +++ b/src/modules/app/app.controller.ts @@ -1,5 +1,4 @@ import { - Body, Controller, Get, HttpStatus, @@ -11,6 +10,8 @@ import { } from "@nestjs/common"; import { ApiBearerAuth, + ApiBody, + ApiConsumes, ApiHeader, ApiOperation, ApiResponse, @@ -18,11 +19,8 @@ import { import { FastifyReply, FastifyRequest } from "fastify"; import { AppService } from "./app.service"; import { JwtAuthGuard } from "../common/guards/jwt-auth.guard"; -import { BodyModeEnum } from "../common/models/collection.model"; -import * as yml from "js-yaml"; -import axios from "axios"; import { ParserService } from "../common/services/parser.service"; -import { ValidateOapiPayload } from "../workspace/payloads/collection.payload"; +import { ParseCurlBodyPayload } from "./payloads/app.payload"; /** * App Controller @@ -56,11 +54,6 @@ export class AppController { } @Post("curl") - @ApiHeader({ - name: "curl", - description: "Pass in the curl command.", - allowEmptyValue: false, - }) @ApiOperation({ summary: "Parse Curl", description: "Parses the provided curl into Sparrow api request schema", @@ -69,8 +62,22 @@ export class AppController { status: 200, description: "Curl parsed successfully", }) + @ApiConsumes("application/x-www-form-urlencoded") + @ApiBody({ + schema: { + properties: { + curl: { + type: "string", + example: "Use sparrow to hit this request", + }, + }, + }, + }) @UseGuards(JwtAuthGuard) - async parseCurl(@Res() res: FastifyReply, @Req() req: FastifyRequest) { + async parseCurl( + @Res() res: FastifyReply, + @Req() req: FastifyRequest<{ Body: ParseCurlBodyPayload }>, + ) { const parsedRequestData = await this.appService.parseCurl(req); return res.status(200).send(parsedRequestData); } @@ -81,6 +88,10 @@ export class AppController { description: "Pass in the curl command.", allowEmptyValue: false, }) + @ApiBody({ + description: "Paste your JSON or YAML text", + required: false, + }) @ApiOperation({ summary: "Validate JSON/YAML/URL OAPI specification", description: "You can import a collection from jsonObj", @@ -90,32 +101,9 @@ export class AppController { description: "Provided OAPI is a valid specification.", }) @ApiResponse({ status: 400, description: "Provided OAPI is invalid." }) - async validateOAPI( - @Req() request: FastifyRequest, - @Res() res: FastifyReply, - @Body() body?: ValidateOapiPayload, - ) { + async validateOAPI(@Req() request: FastifyRequest, @Res() res: FastifyReply) { try { - let data: any; - const url = request.headers["x-oapi-url"] || null; - if (url) { - const isValidUrl = this.parserService.validateUrlIsALocalhostUrl( - url as string, - ); - if (!isValidUrl) throw new Error(); - const response = await axios.get(url as string); - data = response.data; - } else { - const requestType = request.headers["content-type"]; - if (requestType === BodyModeEnum["application/json"]) { - JSON.parse(body.data); - } else if (requestType === BodyModeEnum["application/yaml"]) { - data = yml.load(body.data); - } else { - throw new Error("Unsupported content type"); - } - } - await this.parserService.validateOapi(data); + await this.parserService.validateOapi(request); return res .status(HttpStatus.OK) .send({ valid: true, msg: "Provided OAPI is a valid specification." }); diff --git a/src/modules/app/app.service.ts b/src/modules/app/app.service.ts index 08ba7984..7c942652 100644 --- a/src/modules/app/app.service.ts +++ b/src/modules/app/app.service.ts @@ -14,6 +14,7 @@ import { TransformedRequest, } from "../common/models/collection.rxdb.model"; import { ContextService } from "../common/services/context.service"; +import { ParseCurlBodyPayload } from "./payloads/app.payload"; /** * Application Service @@ -70,11 +71,13 @@ export class AppService { }; } - async parseCurl(req: FastifyRequest): Promise { + async parseCurl( + req: FastifyRequest<{ Body: ParseCurlBodyPayload }>, + ): Promise { try { const curlconverter = await this.importCurlConverter(); const { toJsonString } = curlconverter; - const curl = req.headers?.curl; + const curl = req.body?.curl; if (!curl || !curl.length) { throw new Error(); } diff --git a/src/modules/app/payloads/app.payload.ts b/src/modules/app/payloads/app.payload.ts new file mode 100644 index 00000000..86b12328 --- /dev/null +++ b/src/modules/app/payloads/app.payload.ts @@ -0,0 +1,3 @@ +export interface ParseCurlBodyPayload { + curl: string; +} diff --git a/src/modules/common/services/parser.service.ts b/src/modules/common/services/parser.service.ts index 9aa48a1b..f8659a7a 100644 --- a/src/modules/common/services/parser.service.ts +++ b/src/modules/common/services/parser.service.ts @@ -16,6 +16,9 @@ import * as oapi2Transformer from "./helper/oapi2.transformer"; import * as oapi3Transformer from "./helper/oapi3.transformer"; import { BranchService } from "@src/modules/workspace/services/branch.service"; import { Branch } from "../models/branch.model"; +import { FastifyRequest } from "fastify"; +import axios from "axios"; +import * as yml from "js-yaml"; interface ActiveSyncResponsePayload { collection: WithId; existingCollection: boolean; @@ -277,9 +280,26 @@ export class ParserService { ); } - async validateOapi(data: string): Promise { + async validateOapi(request: FastifyRequest): Promise { try { - (await SwaggerParser.parse(data)) as OpenAPI303 | OpenAPI20; + let data: any; + const url = request.headers["x-oapi-url"] || null; + const oapi = request.body; + if (url) { + const isValidUrl = this.validateUrlIsALocalhostUrl(url as string); + if (!isValidUrl) throw new Error(); + const response = await axios.get(url as string); + data = response.data; + } else { + try { + data = yml.load(oapi as string); + if (data[0] == "object Object") throw new Error(); + } catch (err) { + data = JSON.stringify(oapi); + data = oapi; + } + } + await SwaggerParser.parse(data); return; } catch (err) { throw new Error("Invalid OAPI."); diff --git a/src/modules/workspace/payloads/collection.payload.ts b/src/modules/workspace/payloads/collection.payload.ts index 4979a60d..782be35d 100644 --- a/src/modules/workspace/payloads/collection.payload.ts +++ b/src/modules/workspace/payloads/collection.payload.ts @@ -8,7 +8,7 @@ import { ValidateNested, IsBoolean, } from "class-validator"; -import { ApiProperty, ApiPropertyOptional } from "@nestjs/swagger"; +import { ApiProperty } from "@nestjs/swagger"; import { CollectionItem, ItemTypeEnum, @@ -143,9 +143,3 @@ export class SwitchCollectionBranchDto { @IsNotEmpty() currentBranch: string; } - -export class ValidateOapiPayload { - @ApiPropertyOptional() - @IsString() - data?: string; -} diff --git a/tsconfig.json b/tsconfig.json index 92e2a44a..00bb8253 100755 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,6 +1,6 @@ { "compilerOptions": { - "module": "commonjs", + "module": "nodenext", "declaration": true, "removeComments": true, "noLib": false, @@ -29,11 +29,5 @@ }, "resolveJsonModule": true }, - "ts-node": { - "esm": true, - "compilerOptions": { - "module": "nodenext" - } - }, "exclude": ["node_modules", "./dist/**/*"] } \ No newline at end of file From 218d15931c63ab25e73343495436333793f28e81 Mon Sep 17 00:00:00 2001 From: Nayan Lakhwani Date: Mon, 11 Mar 2024 17:39:22 +0530 Subject: [PATCH 21/43] fix: users set to null on workspace update[] --- .../workspace/payloads/workspace.payload.ts | 42 ++++++++++++------- 1 file changed, 26 insertions(+), 16 deletions(-) diff --git a/src/modules/workspace/payloads/workspace.payload.ts b/src/modules/workspace/payloads/workspace.payload.ts index 0eef6efb..9a5d1e8e 100644 --- a/src/modules/workspace/payloads/workspace.payload.ts +++ b/src/modules/workspace/payloads/workspace.payload.ts @@ -12,21 +12,7 @@ import { } from "class-validator"; import { Type } from "class-transformer"; -export class WorkspaceDto { - @IsOptional() - @IsArray() - users?: string[]; - - @IsDateString() - @IsOptional() - createdAt?: Date; - - @IsMongoId() - @IsOptional() - createdBy?: string; -} - -export class CreateWorkspaceDto extends WorkspaceDto { +export class CreateWorkspaceDto { @ApiProperty({ example: "64f878a0293b1e4415866493" }) @IsMongoId() @IsNotEmpty() @@ -38,9 +24,21 @@ export class CreateWorkspaceDto extends WorkspaceDto { @IsString() @IsNotEmpty() name: string; + + @IsOptional() + @IsArray() + users?: string[]; + + @IsDateString() + @IsOptional() + createdAt?: Date; + + @IsMongoId() + @IsOptional() + createdBy?: string; } -export class UpdateWorkspaceDto extends WorkspaceDto { +export class UpdateWorkspaceDto { @ApiProperty({ example: "workspace 1", }) @@ -54,6 +52,18 @@ export class UpdateWorkspaceDto extends WorkspaceDto { @IsString() @IsOptional() description?: string; + + @IsOptional() + @IsArray() + users?: string[]; + + @IsDateString() + @IsOptional() + createdAt?: Date; + + @IsMongoId() + @IsOptional() + createdBy?: string; } export class UserDto { From 174487d50abd1da4606e76e13ac0ed361fbbd3a6 Mon Sep 17 00:00:00 2001 From: Astitva877 Date: Mon, 11 Mar 2024 17:39:47 +0530 Subject: [PATCH 22/43] feat: Added deleted logic for old API's [523] --- .../workspace/payloads/branch.payload.ts | 25 +++++++++++- .../repositories/branch.repository.ts | 11 ++++++ .../workspace/services/branch.service.ts | 7 +++- .../workspace/services/collection.service.ts | 39 ++++++++++++++++++- 4 files changed, 79 insertions(+), 3 deletions(-) diff --git a/src/modules/workspace/payloads/branch.payload.ts b/src/modules/workspace/payloads/branch.payload.ts index 594d4e22..fb34f97b 100644 --- a/src/modules/workspace/payloads/branch.payload.ts +++ b/src/modules/workspace/payloads/branch.payload.ts @@ -1,7 +1,14 @@ import { ApiProperty } from "@nestjs/swagger"; import { CollectionItem } from "@src/modules/common/models/collection.model"; import { Type } from "class-transformer"; -import { IsArray, IsNotEmpty, IsString, ValidateNested } from "class-validator"; +import { + IsArray, + IsDate, + IsNotEmpty, + IsOptional, + IsString, + ValidateNested, +} from "class-validator"; export class createBranchDto { @IsString() @@ -20,3 +27,19 @@ export class createBranchDto { @Type(() => CollectionItem) items: CollectionItem[]; } + +export class UpdateBranchDto { + @ApiProperty({ type: [CollectionItem] }) + @IsArray() + @ValidateNested({ each: true }) + @Type(() => CollectionItem) + items: CollectionItem[]; + + @IsDate() + @IsOptional() + updatedAt?: Date; + + @IsString() + @IsOptional() + updatedBy?: string; +} diff --git a/src/modules/workspace/repositories/branch.repository.ts b/src/modules/workspace/repositories/branch.repository.ts index 4e8270cb..3da0d60a 100644 --- a/src/modules/workspace/repositories/branch.repository.ts +++ b/src/modules/workspace/repositories/branch.repository.ts @@ -6,6 +6,7 @@ import { Collections } from "@src/modules/common/enum/database.collection.enum"; import { ContextService } from "@src/modules/common/services/context.service"; import { Branch } from "@src/modules/common/models/branch.model"; import { CollectionItem } from "@src/modules/common/models/collection.model"; +import { UpdateBranchDto } from "../payloads/branch.payload"; @Injectable() export class BranchRepository { @@ -54,4 +55,14 @@ export class BranchRepository { .findOne({ collectionId: collectionObjectId, name: branchName }); return response; } + + async updateBranchById(branchId: string, updateParams: UpdateBranchDto) { + const updatedBranchParams = { + $set: updateParams, + }; + const responseData = await this.db + .collection(Collections.BRANCHES) + .findOneAndUpdate({ _id: new ObjectId(branchId) }, updatedBranchParams); + return responseData.value; + } } diff --git a/src/modules/workspace/services/branch.service.ts b/src/modules/workspace/services/branch.service.ts index 0e0c9178..97a6fda0 100644 --- a/src/modules/workspace/services/branch.service.ts +++ b/src/modules/workspace/services/branch.service.ts @@ -42,7 +42,12 @@ export class BranchService { items: CollectionItem[], ): Promise { await this.workspaceService.IsWorkspaceAdminOrEditor(workspaceId); - await this.branchRepository.updateBranch(branchId, items); + const updatedParams = { + items: items, + updatedAt: new Date(), + updatedBy: this.contextService.get("user")._id, + }; + await this.branchRepository.updateBranchById(branchId, updatedParams); } async getBranch(branchId: string): Promise> { diff --git a/src/modules/workspace/services/collection.service.ts b/src/modules/workspace/services/collection.service.ts index 6ab0fb2d..25f1d53c 100644 --- a/src/modules/workspace/services/collection.service.ts +++ b/src/modules/workspace/services/collection.service.ts @@ -16,12 +16,14 @@ import { import { Collection, CollectionBranch, + ItemTypeEnum, } from "@src/modules/common/models/collection.model"; import { ContextService } from "@src/modules/common/services/context.service"; import { ErrorMessages } from "@src/modules/common/enum/error-messages.enum"; import { WorkspaceService } from "./workspace.service"; import { BranchRepository } from "../repositories/branch.repository"; import { Branch } from "@src/modules/common/models/branch.model"; +import { UpdateBranchDto } from "../payloads/branch.payload"; @Injectable() export class CollectionService { @@ -164,9 +166,44 @@ export class CollectionService { collectionId: string, branchName: string, ): Promise | void> { - return await this.branchRepository.getBranchByCollection( + const branch = await this.branchRepository.getBranchByCollection( collectionId, branchName, ); + for (let index = 0; index < branch.items.length; index++) { + if (branch.items[index].type === ItemTypeEnum.FOLDER) { + for (let flag = 0; flag < branch.items[index].items.length; flag++) { + const deletedDate = new Date( + branch.items[index].items[flag].updatedAt, + ); + const currentDate = new Date(); + const diff = currentDate.getTime() - deletedDate.getTime(); + const differenceInDays = diff / (1000 * 60 * 60 * 24); + if ( + branch.items[index].items[flag].isDeleted && + differenceInDays > 7 + ) { + branch.items[index].items.splice(flag, 1); + } + } + } else { + const deletedDate = new Date(branch.items[index].updatedAt); + const currentDate = new Date(); + const diff = currentDate.getTime() - deletedDate.getTime(); + if (branch.items[index].isDeleted && diff > 7) { + branch.items.splice(index, 1); + } + } + } + const updatedBranch: UpdateBranchDto = { + items: branch.items, + updatedAt: new Date(), + updatedBy: this.contextService.get("user")._id, + }; + await this.branchRepository.updateBranchById( + branch._id.toJSON(), + updatedBranch, + ); + return branch; } } From c7e9f38905f15369749030f6df4e51319e952343 Mon Sep 17 00:00:00 2001 From: Astitva877 Date: Mon, 11 Mar 2024 17:46:35 +0530 Subject: [PATCH 23/43] fix: added return type [523] --- src/modules/workspace/repositories/branch.repository.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/modules/workspace/repositories/branch.repository.ts b/src/modules/workspace/repositories/branch.repository.ts index 3da0d60a..2bafecfb 100644 --- a/src/modules/workspace/repositories/branch.repository.ts +++ b/src/modules/workspace/repositories/branch.repository.ts @@ -56,7 +56,10 @@ export class BranchRepository { return response; } - async updateBranchById(branchId: string, updateParams: UpdateBranchDto) { + async updateBranchById( + branchId: string, + updateParams: UpdateBranchDto, + ): Promise> { const updatedBranchParams = { $set: updateParams, }; From 11da1d39f845ce6f348bd85d1e1cf0a6dafac68c Mon Sep 17 00:00:00 2001 From: Astitva877 Date: Mon, 11 Mar 2024 18:56:39 +0530 Subject: [PATCH 24/43] fix: added constants [523] --- src/modules/common/config/configuration.ts | 2 ++ .../workspace/services/collection.service.ts | 13 ++++++++++--- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/modules/common/config/configuration.ts b/src/modules/common/config/configuration.ts index 71460937..d188d3da 100644 --- a/src/modules/common/config/configuration.ts +++ b/src/modules/common/config/configuration.ts @@ -11,6 +11,8 @@ export default () => ({ userBlacklistPrefix: "BL_", defaultTeamNameSuffix: "'s Team", imageSizeLimit: 102400, + deletedAPILimitInDays: 7, + timeToDaysDivisor: 86400000, refreshTokenSecretKey: process.env.REFRESH_TOKEN_SECRET_KEY, emailValidationCodeExpirationTime: parseInt( process.env.EMAIL_VALIDATION_CODE_EXPIRY_TIME, diff --git a/src/modules/workspace/services/collection.service.ts b/src/modules/workspace/services/collection.service.ts index 25f1d53c..f323b92b 100644 --- a/src/modules/workspace/services/collection.service.ts +++ b/src/modules/workspace/services/collection.service.ts @@ -24,6 +24,7 @@ import { WorkspaceService } from "./workspace.service"; import { BranchRepository } from "../repositories/branch.repository"; import { Branch } from "@src/modules/common/models/branch.model"; import { UpdateBranchDto } from "../payloads/branch.payload"; +import { ConfigService } from "@nestjs/config"; @Injectable() export class CollectionService { @@ -33,6 +34,7 @@ export class CollectionService { private readonly branchRepository: BranchRepository, private readonly contextService: ContextService, private readonly workspaceService: WorkspaceService, + private readonly configService: ConfigService, ) {} async createCollection( @@ -178,10 +180,12 @@ export class CollectionService { ); const currentDate = new Date(); const diff = currentDate.getTime() - deletedDate.getTime(); - const differenceInDays = diff / (1000 * 60 * 60 * 24); + const differenceInDays = + diff / this.configService.get("app.timeToDaysDivisor"); if ( branch.items[index].items[flag].isDeleted && - differenceInDays > 7 + differenceInDays > + this.configService.get("app.deletedAPILimitInDays") ) { branch.items[index].items.splice(flag, 1); } @@ -190,7 +194,10 @@ export class CollectionService { const deletedDate = new Date(branch.items[index].updatedAt); const currentDate = new Date(); const diff = currentDate.getTime() - deletedDate.getTime(); - if (branch.items[index].isDeleted && diff > 7) { + if ( + branch.items[index].isDeleted && + diff > this.configService.get("app.deletedAPILimitInDays") + ) { branch.items.splice(index, 1); } } From b33812cd670cd9573954c2f820f5b34045e7346f Mon Sep 17 00:00:00 2001 From: Nayan Lakhwani Date: Tue, 12 Mar 2024 00:16:41 +0530 Subject: [PATCH 25/43] fix: add Partial type to payloads of all update functions[] --- src/modules/identity/controllers/team.controller.ts | 2 +- src/modules/identity/controllers/user.controller.ts | 2 +- src/modules/identity/repositories/team.repository.ts | 4 ++-- src/modules/identity/repositories/user.repository.ts | 4 ++-- src/modules/identity/services/team.service.ts | 2 +- src/modules/identity/services/user.service.ts | 2 +- src/modules/workspace/controllers/collection.controller.ts | 6 +++--- .../workspace/controllers/environment.controller.ts | 2 +- src/modules/workspace/controllers/workspace.controller.ts | 2 +- .../workspace/repositories/collection.repository.ts | 6 +++--- .../workspace/repositories/environment.repository.ts | 2 +- src/modules/workspace/repositories/workspace.repository.ts | 2 +- .../workspace/services/collection-request.service.ts | 4 ++-- src/modules/workspace/services/collection.service.ts | 4 ++-- src/modules/workspace/services/environment.service.ts | 2 +- src/modules/workspace/services/workspace.service.ts | 7 ++++--- 16 files changed, 27 insertions(+), 26 deletions(-) diff --git a/src/modules/identity/controllers/team.controller.ts b/src/modules/identity/controllers/team.controller.ts index 30a34dcd..f7c47834 100644 --- a/src/modules/identity/controllers/team.controller.ts +++ b/src/modules/identity/controllers/team.controller.ts @@ -132,7 +132,7 @@ export class TeamController { @ApiResponse({ status: 400, description: "Updated Team Failed" }) async updateTeam( @Param("teamId") teamId: string, - @Body() updateTeamDto: UpdateTeamDto, + @Body() updateTeamDto: Partial, @Res() res: FastifyReply, @UploadedFile() image: MemoryStorageFile, diff --git a/src/modules/identity/controllers/user.controller.ts b/src/modules/identity/controllers/user.controller.ts index f530c6f4..7fd2e09f 100644 --- a/src/modules/identity/controllers/user.controller.ts +++ b/src/modules/identity/controllers/user.controller.ts @@ -104,7 +104,7 @@ export class UserController { @UseGuards(JwtAuthGuard) async updateUser( @Param("userId") id: string, - @Body() updateUserDto: UpdateUserDto, + @Body() updateUserDto: Partial, @Res() res: FastifyReply, ) { const user = await this.userService.updateUser(id, updateUserDto); diff --git a/src/modules/identity/repositories/team.repository.ts b/src/modules/identity/repositories/team.repository.ts index fcfe07dc..0e704919 100644 --- a/src/modules/identity/repositories/team.repository.ts +++ b/src/modules/identity/repositories/team.repository.ts @@ -95,7 +95,7 @@ export class TeamRepository { */ async update( id: string, - payload: UpdateTeamDto, + payload: Partial, ): Promise> { const _id = new ObjectId(id); const updatedTeam = await this.db @@ -136,7 +136,7 @@ export class TeamRepository { async updateTeamById( id: ObjectId, - updateParams: TeamDto, + updateParams: Partial, ): Promise> { const updatedTeamParams = { $set: updateParams, diff --git a/src/modules/identity/repositories/user.repository.ts b/src/modules/identity/repositories/user.repository.ts index 806f0a2f..c2afd95b 100644 --- a/src/modules/identity/repositories/user.repository.ts +++ b/src/modules/identity/repositories/user.repository.ts @@ -102,7 +102,7 @@ export class UserRepository { */ async updateUser( userId: string, - payload: UpdateUserDto, + payload: Partial, ): Promise> { const _id = new ObjectId(userId); const updatedUser = await this.db @@ -146,7 +146,7 @@ export class UserRepository { async updateUserById( id: ObjectId, - updateParams: UserDto, + updateParams: Partial, ): Promise> { const updatedUserParams = { $set: updateParams, diff --git a/src/modules/identity/services/team.service.ts b/src/modules/identity/services/team.service.ts index 077d3c3a..d5fc13f3 100644 --- a/src/modules/identity/services/team.service.ts +++ b/src/modules/identity/services/team.service.ts @@ -120,7 +120,7 @@ export class TeamService { */ async update( id: string, - teamData: UpdateTeamDto, + teamData: Partial, image?: MemoryStorageFile, ): Promise> { const teamOwner = await this.isTeamOwner(id); diff --git a/src/modules/identity/services/user.service.ts b/src/modules/identity/services/user.service.ts index 794acdfb..e79b0fa9 100644 --- a/src/modules/identity/services/user.service.ts +++ b/src/modules/identity/services/user.service.ts @@ -133,7 +133,7 @@ export class UserService { */ async updateUser( userId: string, - payload: UpdateUserDto, + payload: Partial, ): Promise> { const data = await this.userRepository.updateUser(userId, payload); return data; diff --git a/src/modules/workspace/controllers/collection.controller.ts b/src/modules/workspace/controllers/collection.controller.ts index 840a16d1..c8461fd8 100644 --- a/src/modules/workspace/controllers/collection.controller.ts +++ b/src/modules/workspace/controllers/collection.controller.ts @@ -110,7 +110,7 @@ export class collectionController { async updateCollection( @Param("collectionId") collectionId: string, @Param("workspaceId") workspaceId: string, - @Body() updateCollectionDto: UpdateCollectionDto, + @Body() updateCollectionDto: Partial, @Res() res: FastifyReply, ) { await this.collectionService.updateCollection( @@ -198,7 +198,7 @@ export class collectionController { @Param("collectionId") collectionId: string, @Param("workspaceId") workspaceId: string, @Param("folderId") folderId: string, - @Body() body: FolderPayload, + @Body() body: Partial, @Res() res: FastifyReply, ) { const updatedfolder = await this.collectionRequestService.updateFolder({ @@ -288,7 +288,7 @@ export class collectionController { @ApiResponse({ status: 400, description: "Failed to save request" }) async updateRequest( @Param("requestId") requestId: string, - @Body() requestDto: CollectionRequestDto, + @Body() requestDto: Partial, @Res() res: FastifyReply, ) { const collectionId = requestDto.collectionId; diff --git a/src/modules/workspace/controllers/environment.controller.ts b/src/modules/workspace/controllers/environment.controller.ts index 6ff1c3ba..da785d12 100644 --- a/src/modules/workspace/controllers/environment.controller.ts +++ b/src/modules/workspace/controllers/environment.controller.ts @@ -134,7 +134,7 @@ export class EnvironmentController { async updateEnvironment( @Param("workspaceId") workspaceId: string, @Param("environmentId") environmentId: string, - @Body() updateEnvironmentDto: UpdateEnvironmentDto, + @Body() updateEnvironmentDto: Partial, @Res() res: FastifyReply, ) { await this.environmentService.updateEnvironment( diff --git a/src/modules/workspace/controllers/workspace.controller.ts b/src/modules/workspace/controllers/workspace.controller.ts index 72df9bcb..5c05af31 100644 --- a/src/modules/workspace/controllers/workspace.controller.ts +++ b/src/modules/workspace/controllers/workspace.controller.ts @@ -189,7 +189,7 @@ export class WorkSpaceController { @ApiResponse({ status: 400, description: "Update Workspace Failed" }) async updateWorkspace( @Param("workspaceId") workspaceId: string, - @Body() updateWorkspaceDto: UpdateWorkspaceDto, + @Body() updateWorkspaceDto: Partial, @Res() res: FastifyReply, ) { await this.workspaceService.update(workspaceId, updateWorkspaceDto); diff --git a/src/modules/workspace/repositories/collection.repository.ts b/src/modules/workspace/repositories/collection.repository.ts index 67a3011f..645861e0 100644 --- a/src/modules/workspace/repositories/collection.repository.ts +++ b/src/modules/workspace/repositories/collection.repository.ts @@ -47,7 +47,7 @@ export class CollectionRepository { } async update( id: string, - updateCollectionDto: UpdateCollectionDto, + updateCollectionDto: Partial, ): Promise { const collectionId = new ObjectId(id); const defaultParams = { @@ -80,7 +80,7 @@ export class CollectionRepository { async updateCollection( id: string, - payload: Collection, + payload: Partial, ): Promise> { const _id = new ObjectId(id); const data = await this.db @@ -148,7 +148,7 @@ export class CollectionRepository { async updateRequest( collectionId: string, requestId: string, - request: CollectionRequestDto, + request: Partial, ): Promise { const _id = new ObjectId(collectionId); const defaultParams = { diff --git a/src/modules/workspace/repositories/environment.repository.ts b/src/modules/workspace/repositories/environment.repository.ts index fd39f641..4c8ed34d 100644 --- a/src/modules/workspace/repositories/environment.repository.ts +++ b/src/modules/workspace/repositories/environment.repository.ts @@ -49,7 +49,7 @@ export class EnvironmentRepository { async update( id: string, - updateEnvironmentDto: UpdateEnvironmentDto, + updateEnvironmentDto: Partial, ): Promise { const environmentId = new ObjectId(id); const defaultParams = { diff --git a/src/modules/workspace/repositories/workspace.repository.ts b/src/modules/workspace/repositories/workspace.repository.ts index 0f13259f..3bd7f352 100644 --- a/src/modules/workspace/repositories/workspace.repository.ts +++ b/src/modules/workspace/repositories/workspace.repository.ts @@ -74,7 +74,7 @@ export class WorkspaceRepository { async updateWorkspaceById( id: ObjectId, - updatedWorkspace: WorkspaceDtoForIdDocument, + updatedWorkspace: Partial, ): Promise> { const response = await this.db .collection(Collections.WORKSPACE) diff --git a/src/modules/workspace/services/collection-request.service.ts b/src/modules/workspace/services/collection-request.service.ts index 4643ef07..4e734eb4 100644 --- a/src/modules/workspace/services/collection-request.service.ts +++ b/src/modules/workspace/services/collection-request.service.ts @@ -64,7 +64,7 @@ export class CollectionRequestService { return updatedFolder; } - async updateFolder(payload: FolderDto): Promise { + async updateFolder(payload: Partial): Promise { await this.workspaceService.IsWorkspaceAdminOrEditor(payload.workspaceId); const user = await this.contextService.get("user"); await this.checkPermission(payload.workspaceId, user._id); @@ -185,7 +185,7 @@ export class CollectionRequestService { async updateRequest( collectionId: string, requestId: string, - request: CollectionRequestDto, + request: Partial, ): Promise { return await this.collectionReposistory.updateRequest( collectionId, diff --git a/src/modules/workspace/services/collection.service.ts b/src/modules/workspace/services/collection.service.ts index 6138db33..3886e0c8 100644 --- a/src/modules/workspace/services/collection.service.ts +++ b/src/modules/workspace/services/collection.service.ts @@ -91,7 +91,7 @@ export class CollectionService { } async updateCollection( collectionId: string, - updateCollectionDto: UpdateCollectionDto, + updateCollectionDto: Partial, workspaceId: string, ): Promise { await this.workspaceService.IsWorkspaceAdminOrEditor(workspaceId); @@ -120,7 +120,7 @@ export class CollectionService { } async updateImportedCollection( id: string, - collection: Collection, + collection: Partial, ): Promise> { return await this.collectionReposistory.updateCollection(id, collection); } diff --git a/src/modules/workspace/services/environment.service.ts b/src/modules/workspace/services/environment.service.ts index 3bbe95d8..a17bf666 100644 --- a/src/modules/workspace/services/environment.service.ts +++ b/src/modules/workspace/services/environment.service.ts @@ -136,7 +136,7 @@ export class EnvironmentService { */ async updateEnvironment( environmentId: string, - updateEnvironmentDto: UpdateEnvironmentDto, + updateEnvironmentDto: Partial, workspaceId: string, ): Promise { await this.isWorkspaceAdminorEditor(workspaceId); diff --git a/src/modules/workspace/services/workspace.service.ts b/src/modules/workspace/services/workspace.service.ts index f8b327e2..817f3429 100644 --- a/src/modules/workspace/services/workspace.service.ts +++ b/src/modules/workspace/services/workspace.service.ts @@ -22,7 +22,6 @@ import { TeamRole, WorkspaceRole } from "@src/modules/common/enum/roles.enum"; import { TeamRepository } from "@src/modules/identity/repositories/team.repository"; import { CollectionDto } from "@src/modules/common/models/collection.model"; -import { Logger } from "nestjs-pino"; import { UserRepository } from "@src/modules/identity/repositories/user.repository"; import { DefaultEnvironment, @@ -57,7 +56,6 @@ export class WorkspaceService { private readonly userRepository: UserRepository, private readonly teamService: TeamService, private readonly configService: ConfigService, - private readonly logger: Logger, ) {} async get(id: string): Promise> { @@ -302,9 +300,12 @@ export class WorkspaceService { */ async update( id: string, - updates: UpdateWorkspaceDto, + updates: Partial, ): Promise> { const workspace = await this.IsWorkspaceAdminOrEditor(id); + // for(const value of Object.values(updates)){ + // if(const) + // } const data = await this.workspaceRepository.update(id, updates); const team = await this.teamRepository.findTeamByTeamId( new ObjectId(workspace.team.id), From 72b078ef17f2dd0ec0c98ea49a430e687617009a Mon Sep 17 00:00:00 2001 From: Nayan Lakhwani Date: Tue, 12 Mar 2024 00:19:50 +0530 Subject: [PATCH 26/43] fix: add Partial type to payloads of all update functions[] --- src/modules/workspace/services/workspace.service.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/modules/workspace/services/workspace.service.ts b/src/modules/workspace/services/workspace.service.ts index 817f3429..8c88a5dc 100644 --- a/src/modules/workspace/services/workspace.service.ts +++ b/src/modules/workspace/services/workspace.service.ts @@ -303,9 +303,6 @@ export class WorkspaceService { updates: Partial, ): Promise> { const workspace = await this.IsWorkspaceAdminOrEditor(id); - // for(const value of Object.values(updates)){ - // if(const) - // } const data = await this.workspaceRepository.update(id, updates); const team = await this.teamRepository.findTeamByTeamId( new ObjectId(workspace.team.id), From a79beea0ae98e788b74ab55bedf2e6fffe870c02 Mon Sep 17 00:00:00 2001 From: Nayan Lakhwani Date: Tue, 12 Mar 2024 10:24:55 +0530 Subject: [PATCH 27/43] fix: taking curl in raw body[] --- src/modules/app/app.controller.ts | 6 +----- src/modules/app/app.service.ts | 7 ++----- src/modules/app/payloads/app.payload.ts | 3 --- 3 files changed, 3 insertions(+), 13 deletions(-) delete mode 100644 src/modules/app/payloads/app.payload.ts diff --git a/src/modules/app/app.controller.ts b/src/modules/app/app.controller.ts index f5b52f36..b4864ec6 100644 --- a/src/modules/app/app.controller.ts +++ b/src/modules/app/app.controller.ts @@ -17,7 +17,6 @@ import { import { FastifyReply, FastifyRequest } from "fastify"; import { AppService } from "./app.service"; import { JwtAuthGuard } from "../common/guards/jwt-auth.guard"; -import { ParseCurlBodyPayload } from "./payloads/app.payload"; /** * App Controller @@ -68,10 +67,7 @@ export class AppController { }, }) @UseGuards(JwtAuthGuard) - async parseCurl( - @Res() res: FastifyReply, - @Req() req: FastifyRequest<{ Body: ParseCurlBodyPayload }>, - ) { + async parseCurl(@Res() res: FastifyReply, @Req() req: FastifyRequest) { const parsedRequestData = await this.appService.parseCurl(req); return res.status(200).send(parsedRequestData); } diff --git a/src/modules/app/app.service.ts b/src/modules/app/app.service.ts index 7c942652..a305901c 100644 --- a/src/modules/app/app.service.ts +++ b/src/modules/app/app.service.ts @@ -14,7 +14,6 @@ import { TransformedRequest, } from "../common/models/collection.rxdb.model"; import { ContextService } from "../common/services/context.service"; -import { ParseCurlBodyPayload } from "./payloads/app.payload"; /** * Application Service @@ -71,13 +70,11 @@ export class AppService { }; } - async parseCurl( - req: FastifyRequest<{ Body: ParseCurlBodyPayload }>, - ): Promise { + async parseCurl(req: FastifyRequest): Promise { try { const curlconverter = await this.importCurlConverter(); const { toJsonString } = curlconverter; - const curl = req.body?.curl; + const curl = req.body as string; if (!curl || !curl.length) { throw new Error(); } diff --git a/src/modules/app/payloads/app.payload.ts b/src/modules/app/payloads/app.payload.ts deleted file mode 100644 index 86b12328..00000000 --- a/src/modules/app/payloads/app.payload.ts +++ /dev/null @@ -1,3 +0,0 @@ -export interface ParseCurlBodyPayload { - curl: string; -} From 87c7eaf42bee2282ebadf6a7a65ad223b2303461 Mon Sep 17 00:00:00 2001 From: Nayan Lakhwani Date: Tue, 12 Mar 2024 10:27:06 +0530 Subject: [PATCH 28/43] fix: taking curl in raw body[] --- src/modules/app/app.controller.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/modules/app/app.controller.ts b/src/modules/app/app.controller.ts index 06086f26..cbb26a15 100644 --- a/src/modules/app/app.controller.ts +++ b/src/modules/app/app.controller.ts @@ -20,7 +20,6 @@ import { FastifyReply, FastifyRequest } from "fastify"; import { AppService } from "./app.service"; import { JwtAuthGuard } from "../common/guards/jwt-auth.guard"; import { ParserService } from "../common/services/parser.service"; -import { ParseCurlBodyPayload } from "./payloads/app.payload"; /** * App Controller @@ -74,10 +73,7 @@ export class AppController { }, }) @UseGuards(JwtAuthGuard) - async parseCurl( - @Res() res: FastifyReply, - @Req() req: FastifyRequest<{ Body: ParseCurlBodyPayload }>, - ) { + async parseCurl(@Res() res: FastifyReply, @Req() req: FastifyRequest) { const parsedRequestData = await this.appService.parseCurl(req); return res.status(200).send(parsedRequestData); } From 938fe927b35fa03d69a138fb50114c1fcbbfb163 Mon Sep 17 00:00:00 2001 From: Astitva877 Date: Tue, 12 Mar 2024 15:10:17 +0530 Subject: [PATCH 29/43] fix: Fixed localhost Url issue in importurl [] --- src/modules/workspace/controllers/workspace.controller.ts | 6 ++---- src/modules/workspace/payloads/collection.payload.ts | 4 ++++ 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/modules/workspace/controllers/workspace.controller.ts b/src/modules/workspace/controllers/workspace.controller.ts index 306c4d61..8f57a2e9 100644 --- a/src/modules/workspace/controllers/workspace.controller.ts +++ b/src/modules/workspace/controllers/workspace.controller.ts @@ -37,7 +37,6 @@ import { import * as yml from "js-yaml"; import { ParserService } from "@src/modules/common/services/parser.service"; import { CollectionService } from "../services/collection.service"; -import axios from "axios"; import { ImportCollectionDto } from "../payloads/collection.payload"; import { JwtAuthGuard } from "@src/modules/common/guards/jwt-auth.guard"; import { ObjectId } from "mongodb"; @@ -359,9 +358,8 @@ export class WorkSpaceController { @Body() importCollectionDto: ImportCollectionDto, ) { const activeSync = importCollectionDto.activeSync ?? false; - const response = await axios.get(importCollectionDto.url); - const data = response.data; - const responseType = response.headers["content-type"]; + const data = importCollectionDto.urlData.data; + const responseType = importCollectionDto.urlData.headers["content-type"]; const dataObj = responseType.includes(BodyModeEnum["application/json"]) ? data : yml.load(data); diff --git a/src/modules/workspace/payloads/collection.payload.ts b/src/modules/workspace/payloads/collection.payload.ts index 782be35d..0e78ec84 100644 --- a/src/modules/workspace/payloads/collection.payload.ts +++ b/src/modules/workspace/payloads/collection.payload.ts @@ -135,6 +135,10 @@ export class ImportCollectionDto { @IsString() @IsOptional() currentBranch?: string; + + @ApiProperty() + @IsOptional() + urlData?: any; } export class SwitchCollectionBranchDto { From 1a7e24ac0adf3111564bfb13279172e788c7a586 Mon Sep 17 00:00:00 2001 From: Astitva877 Date: Tue, 12 Mar 2024 15:28:28 +0530 Subject: [PATCH 30/43] fix: remove url [] --- src/modules/workspace/controllers/workspace.controller.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/modules/workspace/controllers/workspace.controller.ts b/src/modules/workspace/controllers/workspace.controller.ts index 8f57a2e9..c4b28702 100644 --- a/src/modules/workspace/controllers/workspace.controller.ts +++ b/src/modules/workspace/controllers/workspace.controller.ts @@ -368,7 +368,6 @@ export class WorkSpaceController { dataObj, activeSync, workspaceId, - importCollectionDto.url, importCollectionDto?.primaryBranch, importCollectionDto?.currentBranch, ); From bd21b84e2c8383205b0d34a08286ed51b66fba5f Mon Sep 17 00:00:00 2001 From: Astitva877 Date: Tue, 12 Mar 2024 15:32:56 +0530 Subject: [PATCH 31/43] fix: Remove localhost validation [] --- src/modules/common/services/parser.service.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/modules/common/services/parser.service.ts b/src/modules/common/services/parser.service.ts index f8659a7a..4a66fe4b 100644 --- a/src/modules/common/services/parser.service.ts +++ b/src/modules/common/services/parser.service.ts @@ -286,8 +286,6 @@ export class ParserService { const url = request.headers["x-oapi-url"] || null; const oapi = request.body; if (url) { - const isValidUrl = this.validateUrlIsALocalhostUrl(url as string); - if (!isValidUrl) throw new Error(); const response = await axios.get(url as string); data = response.data; } else { From 3b32f1e31e10d65a20d802f37a1f09f66fa16493 Mon Sep 17 00:00:00 2001 From: Astitva877 Date: Tue, 12 Mar 2024 17:17:52 +0530 Subject: [PATCH 32/43] fix: Added misisng url field [] --- src/modules/workspace/controllers/workspace.controller.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/modules/workspace/controllers/workspace.controller.ts b/src/modules/workspace/controllers/workspace.controller.ts index c4b28702..8f57a2e9 100644 --- a/src/modules/workspace/controllers/workspace.controller.ts +++ b/src/modules/workspace/controllers/workspace.controller.ts @@ -368,6 +368,7 @@ export class WorkSpaceController { dataObj, activeSync, workspaceId, + importCollectionDto.url, importCollectionDto?.primaryBranch, importCollectionDto?.currentBranch, ); From 20c4bfaf997f30d7d191ee6cbfefd59b6b20928c Mon Sep 17 00:00:00 2001 From: Nayan Lakhwani Date: Tue, 12 Mar 2024 18:30:52 +0530 Subject: [PATCH 33/43] fix: create collection[] --- src/modules/workspace/controllers/collection.controller.ts | 2 +- src/modules/workspace/services/collection.service.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/modules/workspace/controllers/collection.controller.ts b/src/modules/workspace/controllers/collection.controller.ts index c8461fd8..deb389e3 100644 --- a/src/modules/workspace/controllers/collection.controller.ts +++ b/src/modules/workspace/controllers/collection.controller.ts @@ -53,7 +53,7 @@ export class collectionController { @ApiResponse({ status: 201, description: "Collection Created Successfully" }) @ApiResponse({ status: 400, description: "Create Collection Failed" }) async createCollection( - @Body() createCollectionDto: CreateCollectionDto, + @Body() createCollectionDto: Partial, @Res() res: FastifyReply, ) { const workspaceId = createCollectionDto.workspaceId; diff --git a/src/modules/workspace/services/collection.service.ts b/src/modules/workspace/services/collection.service.ts index 3886e0c8..a8e6c223 100644 --- a/src/modules/workspace/services/collection.service.ts +++ b/src/modules/workspace/services/collection.service.ts @@ -28,7 +28,7 @@ export class CollectionService { ) {} async createCollection( - createCollectionDto: CreateCollectionDto, + createCollectionDto: Partial, ): Promise { await this.workspaceService.IsWorkspaceAdminOrEditor( createCollectionDto.workspaceId, From c9ca00e4f03063914ddb8a18eb3b4475948b6942 Mon Sep 17 00:00:00 2001 From: Astitva877 Date: Wed, 13 Mar 2024 17:35:35 +0530 Subject: [PATCH 34/43] fix: Replaced branch name param to body [] --- src/modules/app/app.controller.ts | 9 ++++++++- src/modules/app/app.service.ts | 2 +- .../workspace/controllers/collection.controller.ts | 7 ++++--- .../workspace/payloads/collectionRequest.payload.ts | 7 +++++++ 4 files changed, 20 insertions(+), 5 deletions(-) diff --git a/src/modules/app/app.controller.ts b/src/modules/app/app.controller.ts index cbb26a15..01ed018e 100644 --- a/src/modules/app/app.controller.ts +++ b/src/modules/app/app.controller.ts @@ -20,6 +20,8 @@ import { FastifyReply, FastifyRequest } from "fastify"; import { AppService } from "./app.service"; import { JwtAuthGuard } from "../common/guards/jwt-auth.guard"; import { ParserService } from "../common/services/parser.service"; +import { ApiResponseService } from "../common/services/api-response.service"; +import { HttpStatusCode } from "../common/enum/httpStatusCode.enum"; /** * App Controller @@ -75,7 +77,12 @@ export class AppController { @UseGuards(JwtAuthGuard) async parseCurl(@Res() res: FastifyReply, @Req() req: FastifyRequest) { const parsedRequestData = await this.appService.parseCurl(req); - return res.status(200).send(parsedRequestData); + const responseData = new ApiResponseService( + "Success", + HttpStatusCode.OK, + parsedRequestData, + ); + return res.status(responseData.httpStatusCode).send(responseData); } @Post("/validate/oapi") diff --git a/src/modules/app/app.service.ts b/src/modules/app/app.service.ts index a305901c..607e1031 100644 --- a/src/modules/app/app.service.ts +++ b/src/modules/app/app.service.ts @@ -105,7 +105,7 @@ export class AppService { source: SourceTypeEnum.USER, request: { method: requestObject.method.toUpperCase(), - url: "", + url: requestObject.url ?? "", body: { raw: "", urlencoded: [], diff --git a/src/modules/workspace/controllers/collection.controller.ts b/src/modules/workspace/controllers/collection.controller.ts index a0b232aa..b3799f24 100644 --- a/src/modules/workspace/controllers/collection.controller.ts +++ b/src/modules/workspace/controllers/collection.controller.ts @@ -25,6 +25,7 @@ import { ApiResponseService } from "@src/modules/common/services/api-response.se import { HttpStatusCode } from "@src/modules/common/enum/httpStatusCode.enum"; import { WorkspaceService } from "../services/workspace.service"; import { + BranchChangeDto, CollectionRequestDto, FolderPayload, } from "../payloads/collectionRequest.payload"; @@ -351,7 +352,7 @@ export class collectionController { return res.status(responseData.httpStatusCode).send(responseData); } - @Get(":collectionId/branch/:branchName") + @Post(":collectionId/branch") @ApiOperation({ summary: "Get collection items as per the branch selected", description: "Switch branch to get collection of that branch", @@ -360,12 +361,12 @@ export class collectionController { @ApiResponse({ status: 400, description: "Failed to switch branch" }) async switchCollectionBranch( @Param("collectionId") collectionId: string, - @Param("branchName") branchName: string, + @Body() branchChangeDto: BranchChangeDto, @Res() res: FastifyReply, ) { const branch = await this.collectionService.getBranchData( collectionId, - branchName, + branchChangeDto.branchName, ); const responseData = new ApiResponseService( "Branch switched Successfully", diff --git a/src/modules/workspace/payloads/collectionRequest.payload.ts b/src/modules/workspace/payloads/collectionRequest.payload.ts index 62682281..090170c5 100644 --- a/src/modules/workspace/payloads/collectionRequest.payload.ts +++ b/src/modules/workspace/payloads/collectionRequest.payload.ts @@ -315,3 +315,10 @@ export class DeleteFolderDto { @IsNotEmpty() folderId: string; } + +export class BranchChangeDto { + @ApiProperty({ example: "development" }) + @IsString() + @IsNotEmpty() + branchName: string; +} From c0da8d2f5065053181995f119305265b4cb80f68 Mon Sep 17 00:00:00 2001 From: Nayan Lakhwani Date: Wed, 13 Mar 2024 17:48:55 +0530 Subject: [PATCH 35/43] fix: handle anyOf and oneOf in oapi v3[] --- .../services/helper/oapi2.transformer.ts | 15 ++- .../services/helper/oapi3.transformer.ts | 98 ++++++++++++++++--- 2 files changed, 94 insertions(+), 19 deletions(-) diff --git a/src/modules/common/services/helper/oapi2.transformer.ts b/src/modules/common/services/helper/oapi2.transformer.ts index 0816ec20..e8a6f7c3 100644 --- a/src/modules/common/services/helper/oapi2.transformer.ts +++ b/src/modules/common/services/helper/oapi2.transformer.ts @@ -61,15 +61,24 @@ export function createCollectionItems( for (const item of collectionItems) { item.request.url = baseUrl + item.request.url; let tagDescription = ""; + if (!openApiDocument.tags) { + openApiDocument.tags = [ + { + name: "default", + description: "This is a default folder", + }, + ]; + } + const itemTag = item.tag ?? "default"; for (const tag of Object.values(openApiDocument?.tags)) { - if (tag.name === item.tag) { + if (tag.name === itemTag) { tagDescription = tag.description; } } - let folderObj = folderMap.get(item.tag); + let folderObj = folderMap.get(itemTag); if (!folderObj) { folderObj = {}; - folderObj.name = item.tag; + folderObj.name = itemTag; folderObj.description = tagDescription; folderObj.isDeleted = false; folderObj.type = ItemTypeEnum.FOLDER; diff --git a/src/modules/common/services/helper/oapi3.transformer.ts b/src/modules/common/services/helper/oapi3.transformer.ts index 75b17117..80e31be3 100644 --- a/src/modules/common/services/helper/oapi3.transformer.ts +++ b/src/modules/common/services/helper/oapi3.transformer.ts @@ -53,15 +53,24 @@ export function createCollectionItems( for (const item of collectionItems) { item.request.url = baseUrl + item.request.url; let tagDescription = ""; + if (!openApiDocument.tags) { + openApiDocument.tags = [ + { + name: "default", + description: "This is a default folder", + }, + ]; + } + const itemTag = item.tag ?? "default"; for (const tag of Object.values(openApiDocument?.tags)) { - if (tag.name === item.tag) { + if (tag.name === itemTag) { tagDescription = tag.description; } } - let folderObj = folderMap.get(item.tag); + let folderObj = folderMap.get(itemTag); if (!folderObj) { folderObj = {}; - folderObj.name = item.tag; + folderObj.name = itemTag; folderObj.description = tagDescription; folderObj.isDeleted = false; folderObj.type = ItemTypeEnum.FOLDER; @@ -162,25 +171,82 @@ function transformPathV3( } transformedObject.request.url = url; + function callRecursively(schema: any, bodyObject: { [key: string]: any }) { + if (schema && schema.type === "object") { + let properties = schema.properties || {}; + + if (schema.allOf) { + for (const property of Object.values(schema.allOf) as any) { + if (property.type === "object") { + callRecursively(property, bodyObject); + } else if (property.properties) { + properties = property.properties; + callRecursively( + { + type: "object", + properties, + }, + bodyObject, + ); + } + } + } + if (properties) { + for (let [propertyName, property] of Object.entries(properties)) { + propertyName = propertyName as string; + const anyProperty = property as any; + if (anyProperty.oneOf) { + if (anyProperty.oneOf[0].type === "object") { + callRecursively(anyProperty.oneOf[0], bodyObject); + } else { + property = anyProperty.oneOf[0]; + } + } else if (anyProperty.allOf) { + if (anyProperty.type === "object") { + callRecursively(property, bodyObject); + } + } + const exampleType = anyProperty.type; + const exampleValue = anyProperty.example; + bodyObject[propertyName] = + exampleValue || + buildExampleValue(anyProperty) || + getExampleValue(exampleType); + } + } + } + } + + // Extract Request Body const content = pathItemObject?.requestBody?.content; if (content) { const contentKeys = Object.keys(pathItemObject.requestBody.content) || []; + const bodyObject = {}; for (const key of contentKeys) { if (key === "application/json") { const schema = content[key].schema; - if (schema && schema.type === "object") { - const properties = schema.properties || {}; - const bodyObject: any = {}; - for (const [propertyName, property] of Object.entries(properties)) { - const exampleType = property.type; - const exampleValue = property.example; - bodyObject[propertyName] = - exampleValue || - buildExampleValue(property) || - getExampleValue(exampleType); - } - transformedObject.request.body.raw = JSON.stringify(bodyObject); - } + callRecursively(schema, bodyObject); + // if (schema && schema.type === "object") { + // const properties = schema.properties || {}; + // const bodyObject: any = {}; + // for (let [propertyName, property] of Object.entries(properties)) { + // propertyName = propertyName; + // if (property.oneOf) { + // property = property.oneOf[0]; + // } else if (property.allOf) { + // // property = property.oneOf[0]; + // } + // const exampleType = property.type; + // const exampleValue = property.example; + // bodyObject[propertyName] = + // exampleValue || + // buildExampleValue(property) || + // getExampleValue(exampleType); + // } + // transformedObject.request.body.raw = JSON.stringify(bodyObject); + // } + + transformedObject.request.body.raw = JSON.stringify(bodyObject); transformedObject.request.selectedRequestBodyType = BodyModeEnum["application/json"]; } From 91e0ba9ea7328a79ee14649b40020eb214ab03b8 Mon Sep 17 00:00:00 2001 From: Nayan Lakhwani Date: Wed, 13 Mar 2024 17:53:26 +0530 Subject: [PATCH 36/43] fix: handle anyOf and oneOf in oapi v3[] --- .../services/helper/oapi3.transformer.ts | 32 ++++--------------- 1 file changed, 6 insertions(+), 26 deletions(-) diff --git a/src/modules/common/services/helper/oapi3.transformer.ts b/src/modules/common/services/helper/oapi3.transformer.ts index 80e31be3..9a8175ac 100644 --- a/src/modules/common/services/helper/oapi3.transformer.ts +++ b/src/modules/common/services/helper/oapi3.transformer.ts @@ -171,17 +171,17 @@ function transformPathV3( } transformedObject.request.url = url; - function callRecursively(schema: any, bodyObject: { [key: string]: any }) { + function extractJsonBody(schema: any, bodyObject: { [key: string]: any }) { if (schema && schema.type === "object") { let properties = schema.properties || {}; if (schema.allOf) { for (const property of Object.values(schema.allOf) as any) { if (property.type === "object") { - callRecursively(property, bodyObject); + extractJsonBody(property, bodyObject); } else if (property.properties) { properties = property.properties; - callRecursively( + extractJsonBody( { type: "object", properties, @@ -197,13 +197,13 @@ function transformPathV3( const anyProperty = property as any; if (anyProperty.oneOf) { if (anyProperty.oneOf[0].type === "object") { - callRecursively(anyProperty.oneOf[0], bodyObject); + extractJsonBody(anyProperty.oneOf[0], bodyObject); } else { property = anyProperty.oneOf[0]; } } else if (anyProperty.allOf) { if (anyProperty.type === "object") { - callRecursively(property, bodyObject); + extractJsonBody(property, bodyObject); } } const exampleType = anyProperty.type; @@ -225,27 +225,7 @@ function transformPathV3( for (const key of contentKeys) { if (key === "application/json") { const schema = content[key].schema; - callRecursively(schema, bodyObject); - // if (schema && schema.type === "object") { - // const properties = schema.properties || {}; - // const bodyObject: any = {}; - // for (let [propertyName, property] of Object.entries(properties)) { - // propertyName = propertyName; - // if (property.oneOf) { - // property = property.oneOf[0]; - // } else if (property.allOf) { - // // property = property.oneOf[0]; - // } - // const exampleType = property.type; - // const exampleValue = property.example; - // bodyObject[propertyName] = - // exampleValue || - // buildExampleValue(property) || - // getExampleValue(exampleType); - // } - // transformedObject.request.body.raw = JSON.stringify(bodyObject); - // } - + extractJsonBody(schema, bodyObject); transformedObject.request.body.raw = JSON.stringify(bodyObject); transformedObject.request.selectedRequestBodyType = BodyModeEnum["application/json"]; From f6aff04fa969c64b9b10433539a4b55575d690a9 Mon Sep 17 00:00:00 2001 From: Nayan Lakhwani Date: Thu, 14 Mar 2024 10:41:14 +0530 Subject: [PATCH 37/43] feat: added handling for different sources when adding api/folder to collection[] --- .../services/helper/oapi2.transformer.ts | 2 +- .../services/helper/oapi3.transformer.ts | 2 +- .../controllers/collection.controller.ts | 6 ++--- .../payloads/collectionRequest.payload.ts | 22 ++++++++++++++++--- .../services/collection-request.service.ts | 8 +++---- 5 files changed, 28 insertions(+), 12 deletions(-) diff --git a/src/modules/common/services/helper/oapi2.transformer.ts b/src/modules/common/services/helper/oapi2.transformer.ts index 0816ec20..1435e8b1 100644 --- a/src/modules/common/services/helper/oapi2.transformer.ts +++ b/src/modules/common/services/helper/oapi2.transformer.ts @@ -104,7 +104,7 @@ function transformPath( name: pathName || "", description: "", type: ItemTypeEnum.REQUEST, - source: SourceTypeEnum.USER, + source: SourceTypeEnum.SPEC, request: { method: "", url: "", diff --git a/src/modules/common/services/helper/oapi3.transformer.ts b/src/modules/common/services/helper/oapi3.transformer.ts index 75b17117..c372405f 100644 --- a/src/modules/common/services/helper/oapi3.transformer.ts +++ b/src/modules/common/services/helper/oapi3.transformer.ts @@ -96,7 +96,7 @@ function transformPathV3( name: pathName || "", description: "", type: ItemTypeEnum.REQUEST, - source: SourceTypeEnum.USER, + source: SourceTypeEnum.SPEC, request: { method: "", url: "", diff --git a/src/modules/workspace/controllers/collection.controller.ts b/src/modules/workspace/controllers/collection.controller.ts index b3799f24..e472711c 100644 --- a/src/modules/workspace/controllers/collection.controller.ts +++ b/src/modules/workspace/controllers/collection.controller.ts @@ -172,7 +172,7 @@ export class collectionController { async addFolder( @Param("collectionId") collectionId: string, @Param("workspaceId") workspaceId: string, - @Body() body: FolderPayload, + @Body() body: Partial, @Res() res: FastifyReply, ) { const newFolder = await this.collectionRequestService.addFolder({ @@ -251,7 +251,7 @@ export class collectionController { @ApiResponse({ status: 200, description: "Request Updated Successfully" }) @ApiResponse({ status: 400, description: "Failed to Update a request" }) async addRequest( - @Body() requestDto: CollectionRequestDto, + @Body() requestDto: Partial, @Res() res: FastifyReply, ) { const collectionId = requestDto.collectionId; @@ -323,7 +323,7 @@ export class collectionController { @ApiResponse({ status: 400, description: "Failed to delete request" }) async deleteRequest( @Param("requestId") requestId: string, - @Body() requestDto: CollectionRequestDto, + @Body() requestDto: Partial, @Res() res: FastifyReply, ) { const collectionId = requestDto.collectionId; diff --git a/src/modules/workspace/payloads/collectionRequest.payload.ts b/src/modules/workspace/payloads/collectionRequest.payload.ts index 090170c5..debe68c2 100644 --- a/src/modules/workspace/payloads/collectionRequest.payload.ts +++ b/src/modules/workspace/payloads/collectionRequest.payload.ts @@ -16,6 +16,7 @@ import { ApiProperty } from "@nestjs/swagger"; import { BodyModeEnum, ItemTypeEnum, + SourceTypeEnum, } from "@src/modules/common/models/collection.model"; // eslint-disable-next-line @typescript-eslint/no-unused-vars @@ -260,7 +261,13 @@ export class CollectionRequestDto { @ApiProperty({ example: "6538e910aa77d958912371f5" }) @IsString() @IsOptional() - folderId: string; + folderId?: string; + + @ApiProperty({ enum: ["SPEC", "USER"] }) + @IsEnum(SourceTypeEnum) + @IsOptional() + @IsString() + source?: SourceTypeEnum; @ApiProperty() @Type(() => CollectionRequestItem) @@ -272,12 +279,17 @@ export class FolderPayload { @ApiProperty({ example: "pet" }) @IsString() @IsOptional() - name: string; + name?: string; @ApiProperty({ example: "Everything about your Pets" }) @IsString() @IsOptional() - description: string; + description?: string; + + @ApiProperty({ example: SourceTypeEnum.USER }) + @IsEnum(ItemTypeEnum) + @IsOptional() + source?: SourceTypeEnum; } export class FolderDto { @@ -293,6 +305,10 @@ export class FolderDto { @IsOptional() description?: string; + @IsEnum(SourceTypeEnum) + @IsOptional() + source?: SourceTypeEnum; + @IsString() @IsNotEmpty() collectionId: string; diff --git a/src/modules/workspace/services/collection-request.service.ts b/src/modules/workspace/services/collection-request.service.ts index 4e734eb4..905b4995 100644 --- a/src/modules/workspace/services/collection-request.service.ts +++ b/src/modules/workspace/services/collection-request.service.ts @@ -32,7 +32,7 @@ export class CollectionRequestService { private readonly workspaceService: WorkspaceService, ) {} - async addFolder(payload: FolderDto): Promise { + async addFolder(payload: Partial): Promise { await this.workspaceService.IsWorkspaceAdminOrEditor(payload.workspaceId); const user = await this.contextService.get("user"); const uuid = uuidv4(); @@ -48,7 +48,7 @@ export class CollectionRequestService { name: payload.name, description: payload.description ?? "", type: ItemTypeEnum.FOLDER, - source: SourceTypeEnum.USER, + source: payload.source ?? SourceTypeEnum.USER, isDeleted: false, items: [], createdBy: user.name, @@ -130,7 +130,7 @@ export class CollectionRequestService { } async addRequest( collectionId: string, - request: CollectionRequestDto, + request: Partial, noOfRequests: number, userName: string, folderId?: string, @@ -141,7 +141,7 @@ export class CollectionRequestService { name: request.items.name, type: request.items.type, description: request.items.description, - source: SourceTypeEnum.USER, + source: request.source ?? SourceTypeEnum.USER, isDeleted: false, createdBy: userName, updatedBy: userName, From be94540e47af8639c0bd2d92ce364fb7a8b0cc42 Mon Sep 17 00:00:00 2001 From: Astitva877 Date: Thu, 14 Mar 2024 11:31:21 +0530 Subject: [PATCH 38/43] feat: Added local repo path [] --- src/modules/common/models/collection.model.ts | 5 +++++ src/modules/common/services/parser.service.ts | 5 +++++ src/modules/workspace/controllers/workspace.controller.ts | 1 + src/modules/workspace/payloads/collection.payload.ts | 5 +++++ 4 files changed, 16 insertions(+) diff --git a/src/modules/common/models/collection.model.ts b/src/modules/common/models/collection.model.ts index a22a5224..6193fbc8 100644 --- a/src/modules/common/models/collection.model.ts +++ b/src/modules/common/models/collection.model.ts @@ -272,6 +272,11 @@ export class Collection { @IsOptional() primaryBranch?: string; + @ApiProperty() + @IsString() + @IsOptional() + localRepositoryPath?: string; + @ApiProperty() @IsNumber() @IsNotEmpty() diff --git a/src/modules/common/services/parser.service.ts b/src/modules/common/services/parser.service.ts index 4a66fe4b..c65c56d8 100644 --- a/src/modules/common/services/parser.service.ts +++ b/src/modules/common/services/parser.service.ts @@ -39,6 +39,7 @@ export class ParserService { activeSyncUrl?: string, primaryBranch?: string, currentBranch?: string, + localRepositoryPath?: string, ): Promise<{ collection: WithId; existingCollection: boolean; @@ -84,6 +85,7 @@ export class ParserService { totalRequests, activeSyncUrl, items, + localRepositoryPath, ); return { collection, @@ -94,6 +96,7 @@ export class ParserService { name: openApiDocument.info.title, description: openApiDocument.info.description, primaryBranch: "", + localRepositoryPath: "", branches: [], totalRequests, items: items, @@ -127,6 +130,7 @@ export class ParserService { totalRequests: number, activeSyncUrl: string, items: CollectionItem[], + localRepositoryPath: string, ): Promise { const collectionTitle = openApiDocument.info.title; let mergedFolderItems: CollectionItem[] = []; @@ -188,6 +192,7 @@ export class ParserService { name: collectionTitle, description: openApiDocument.info.description, primaryBranch: primaryBranch ?? "", + localRepositoryPath: localRepositoryPath ?? "", totalRequests, items: items, branches: [], diff --git a/src/modules/workspace/controllers/workspace.controller.ts b/src/modules/workspace/controllers/workspace.controller.ts index 8f57a2e9..4c203d66 100644 --- a/src/modules/workspace/controllers/workspace.controller.ts +++ b/src/modules/workspace/controllers/workspace.controller.ts @@ -371,6 +371,7 @@ export class WorkSpaceController { importCollectionDto.url, importCollectionDto?.primaryBranch, importCollectionDto?.currentBranch, + importCollectionDto?.localRepositoryPath, ); if (!collectionObj.existingCollection) { await this.workspaceService.addCollectionInWorkSpace(workspaceId, { diff --git a/src/modules/workspace/payloads/collection.payload.ts b/src/modules/workspace/payloads/collection.payload.ts index 0e78ec84..99c4363f 100644 --- a/src/modules/workspace/payloads/collection.payload.ts +++ b/src/modules/workspace/payloads/collection.payload.ts @@ -126,6 +126,11 @@ export class ImportCollectionDto { @IsBoolean() activeSync?: boolean; + @ApiProperty({ example: "C://users/github" }) + @IsString() + @IsOptional() + localRepositoryPath?: string; + @ApiProperty({ example: "development" }) @IsString() @IsOptional() From 24c8def2d6ffc4047e814660675f6e570b9b7491 Mon Sep 17 00:00:00 2001 From: Astitva877 Date: Thu, 14 Mar 2024 17:24:09 +0530 Subject: [PATCH 39/43] fix: fixed save folder and request in branhces [] --- .../controllers/collection.controller.ts | 15 +- .../payloads/collectionRequest.payload.ts | 20 ++- .../repositories/branch.repository.ts | 152 +++++++++++++++++- .../services/collection-request.service.ts | 117 +++++++++++++- 4 files changed, 290 insertions(+), 14 deletions(-) diff --git a/src/modules/workspace/controllers/collection.controller.ts b/src/modules/workspace/controllers/collection.controller.ts index e472711c..7ac355c3 100644 --- a/src/modules/workspace/controllers/collection.controller.ts +++ b/src/modules/workspace/controllers/collection.controller.ts @@ -227,13 +227,16 @@ export class collectionController { @Param("collectionId") collectionId: string, @Param("workspaceId") workspaceId: string, @Param("folderId") folderId: string, + @Body() branchNameDto: Partial, @Res() res: FastifyReply, ) { - const response = await this.collectionRequestService.deleteFolder({ - collectionId, - workspaceId, - folderId, - }); + const payload = { + collectionId: collectionId, + workspaceId: workspaceId, + folderId: folderId, + currentBranch: branchNameDto.branchName, + }; + const response = await this.collectionRequestService.deleteFolder(payload); const responseData = new ApiResponseService( "Success", HttpStatusCode.OK, @@ -340,7 +343,7 @@ export class collectionController { collectionId, requestId, noOfRequests, - requestDto.folderId, + requestDto, ); const collection = await this.collectionService.getCollection(collectionId); diff --git a/src/modules/workspace/payloads/collectionRequest.payload.ts b/src/modules/workspace/payloads/collectionRequest.payload.ts index debe68c2..cfa8b58d 100644 --- a/src/modules/workspace/payloads/collectionRequest.payload.ts +++ b/src/modules/workspace/payloads/collectionRequest.payload.ts @@ -273,6 +273,11 @@ export class CollectionRequestDto { @Type(() => CollectionRequestItem) @ValidateNested({ each: true }) items?: CollectionRequestItem; + + @ApiProperty({ example: "main" }) + @IsString() + @IsOptional() + currentBranch?: string; } export class FolderPayload { @@ -287,9 +292,14 @@ export class FolderPayload { description?: string; @ApiProperty({ example: SourceTypeEnum.USER }) - @IsEnum(ItemTypeEnum) + @IsEnum(SourceTypeEnum) @IsOptional() source?: SourceTypeEnum; + + @ApiProperty({ example: "development" }) + @IsString() + @IsOptional() + currentBranch?: string; } export class FolderDto { @@ -316,6 +326,10 @@ export class FolderDto { @IsString() @IsNotEmpty() workspaceId: string; + + @IsString() + @IsOptional() + currentBranch?: string; } export class DeleteFolderDto { @@ -330,6 +344,10 @@ export class DeleteFolderDto { @IsString() @IsNotEmpty() folderId: string; + + @IsString() + @IsOptional() + currentBranch?: string; } export class BranchChangeDto { diff --git a/src/modules/workspace/repositories/branch.repository.ts b/src/modules/workspace/repositories/branch.repository.ts index 2bafecfb..cd4783fc 100644 --- a/src/modules/workspace/repositories/branch.repository.ts +++ b/src/modules/workspace/repositories/branch.repository.ts @@ -1,12 +1,20 @@ -import { Inject, Injectable } from "@nestjs/common"; +import { BadRequestException, Inject, Injectable } from "@nestjs/common"; -import { Db, InsertOneResult, ObjectId, WithId } from "mongodb"; +import { Db, InsertOneResult, ObjectId, UpdateResult, WithId } from "mongodb"; import { Collections } from "@src/modules/common/enum/database.collection.enum"; import { ContextService } from "@src/modules/common/services/context.service"; import { Branch } from "@src/modules/common/models/branch.model"; -import { CollectionItem } from "@src/modules/common/models/collection.model"; +import { + CollectionItem, + ItemTypeEnum, +} from "@src/modules/common/models/collection.model"; import { UpdateBranchDto } from "../payloads/branch.payload"; +import { ErrorMessages } from "@src/modules/common/enum/error-messages.enum"; +import { + CollectionRequestDto, + CollectionRequestItem, +} from "../payloads/collectionRequest.payload"; @Injectable() export class BranchRepository { @@ -68,4 +76,142 @@ export class BranchRepository { .findOneAndUpdate({ _id: new ObjectId(branchId) }, updatedBranchParams); return responseData.value; } + + async addRequestInBranch( + collectionId: string, + branchName: string, + request: CollectionItem, + ): Promise> { + const branch = await this.getBranchByCollection(collectionId, branchName); + const _id = branch._id; + const data = await this.db + .collection(Collections.BRANCHES) + .updateOne( + { _id }, + { + $push: { + items: request, + }, + }, + ); + return data; + } + + async addRequestInBranchFolder( + collectionId: string, + branchName: string, + request: CollectionItem, + folderId: string, + ): Promise> { + const branch = await this.getBranchByCollection(collectionId, branchName); + const _id = branch._id; + const isFolderExists = branch.items.some((item) => { + return item.id === folderId; + }); + if (isFolderExists) { + return await this.db.collection(Collections.BRANCHES).updateOne( + { _id, "items.name": request.name }, + { + $push: { "items.$.items": request.items[0] }, + }, + ); + } else { + throw new BadRequestException(ErrorMessages.Unauthorized); + } + } + + async updateRequestInBranch( + collectionId: string, + branchName: string, + requestId: string, + request: Partial, + ): Promise { + const branch = await this.getBranchByCollection(collectionId, branchName); + const _id = branch._id; + const user = await this.contextService.get("user"); + const defaultParams = { + updatedAt: new Date(), + updatedBy: user._id, + }; + if (request.items.type === ItemTypeEnum.REQUEST) { + request.items = { ...request.items, ...defaultParams }; + await this.db.collection(Collections.BRANCHES).updateOne( + { _id, "items.id": requestId }, + { + $set: { + "items.$": request.items, + updatedAt: new Date(), + updatedBy: user._id, + }, + }, + ); + return { ...request.items, id: requestId }; + } else { + request.items.items = { ...request.items.items, ...defaultParams }; + await this.db.collection(Collections.BRANCHES).updateOne( + { + _id, + "items.id": request.folderId, + "items.items.id": requestId, + }, + { + $set: { + "items.$[i].items.$[j]": request.items.items, + updatedAt: new Date(), + updatedBy: user._id, + }, + }, + { + arrayFilters: [{ "i.id": request.folderId }, { "j.id": requestId }], + }, + ); + return { ...request.items.items, id: requestId }; + } + } + + async deleteRequestInBranch( + collectionId: string, + branchName: string, + requestId: string, + folderId?: string, + ): Promise> { + const branch = await this.getBranchByCollection(collectionId, branchName); + const _id = branch._id; + if (folderId) { + return await this.db.collection(Collections.BRANCHES).updateOne( + { + _id, + }, + { + $pull: { + "items.$[i].items": { + id: requestId, + }, + }, + $set: { + updatedAt: new Date(), + updatedBy: this.contextService.get("user")._id, + }, + }, + { + arrayFilters: [{ "i.id": folderId }], + }, + ); + } else { + return await this.db.collection(Collections.BRANCHES).updateOne( + { _id }, + { + $pull: { + items: { + id: requestId, + }, + }, + $set: { + updatedAt: new Date(), + updatedBy: this.contextService.get("user")._id, + }, + }, + ); + } + } } diff --git a/src/modules/workspace/services/collection-request.service.ts b/src/modules/workspace/services/collection-request.service.ts index 905b4995..ae5fda70 100644 --- a/src/modules/workspace/services/collection-request.service.ts +++ b/src/modules/workspace/services/collection-request.service.ts @@ -22,6 +22,9 @@ import { } from "@src/modules/common/models/collection.model"; import { CollectionService } from "./collection.service"; import { WorkspaceService } from "./workspace.service"; +import { BranchRepository } from "../repositories/branch.repository"; +import { UpdateBranchDto } from "../payloads/branch.payload"; +import { Branch } from "@src/modules/common/models/branch.model"; @Injectable() export class CollectionRequestService { constructor( @@ -30,6 +33,7 @@ export class CollectionRequestService { private readonly contextService: ContextService, private readonly collectionService: CollectionService, private readonly workspaceService: WorkspaceService, + private readonly branchRepository: BranchRepository, ) {} async addFolder(payload: Partial): Promise { @@ -61,9 +65,37 @@ export class CollectionRequestService { payload.collectionId, collection, ); + if (payload?.currentBranch) { + const branch = await this.branchRepository.getBranchByCollection( + payload.collectionId, + payload.currentBranch, + ); + if (!branch) { + throw new BadRequestException("Branch Not Found"); + } + branch.items.push(updatedFolder); + const updatedBranch: UpdateBranchDto = { + items: branch.items, + updatedAt: new Date(), + updatedBy: this.contextService.get("user")._id, + }; + await this.branchRepository.updateBranchById( + branch._id.toString(), + updatedBranch, + ); + } return updatedFolder; } + async isFolderExist(branch: Branch, id: string): Promise { + for (let i = 0; i < branch.items.length; i++) { + if (branch.items[i].id === id) { + return i; + } + } + throw new BadRequestException("Folder Doesn't Exist"); + } + async updateFolder(payload: Partial): Promise { await this.workspaceService.IsWorkspaceAdminOrEditor(payload.workspaceId); const user = await this.contextService.get("user"); @@ -82,6 +114,28 @@ export class CollectionRequestService { payload.collectionId, collection, ); + if (payload?.currentBranch) { + const branch = await this.branchRepository.getBranchByCollection( + payload.collectionId, + payload.currentBranch, + ); + if (!branch) { + throw new BadRequestException("Branch Not Found"); + } + const index = await this.isFolderExist(branch, payload.folderId); + branch.items[index].name = payload.name ?? branch.items[index].name; + branch.items[index].description = + payload.description ?? branch.items[index].description; + const updatedBranch: UpdateBranchDto = { + items: branch.items, + updatedAt: new Date(), + updatedBy: user._id, + }; + await this.branchRepository.updateBranchById( + branch._id.toString(), + updatedBranch, + ); + } return collection.items[index]; } @@ -105,6 +159,28 @@ export class CollectionRequestService { payload.collectionId, collection, ); + if (payload?.currentBranch) { + const branch = await this.branchRepository.getBranchByCollection( + payload.collectionId, + payload.currentBranch, + ); + if (!branch) { + throw new BadRequestException("Branch Not Found"); + } + const updatedBranchItems = branch.items.filter( + (item) => item.id !== payload.folderId, + ); + branch.items = updatedBranchItems; + const updatedBranch: UpdateBranchDto = { + items: branch.items, + updatedAt: new Date(), + updatedBy: user._id, + }; + await this.branchRepository.updateBranchById( + branch._id.toString(), + updatedBranch, + ); + } return data; } @@ -155,6 +231,13 @@ export class CollectionRequestService { requestObj, noOfRequests, ); + if (request?.currentBranch) { + await this.branchRepository.addRequestInBranch( + collectionId, + request.currentBranch, + requestObj, + ); + } return requestObj; } else { requestObj.items = [ @@ -178,6 +261,14 @@ export class CollectionRequestService { noOfRequests, folderId, ); + if (request?.currentBranch) { + await this.branchRepository.addRequestInBranchFolder( + collectionId, + request.currentBranch, + requestObj, + folderId, + ); + } return requestObj.items[0]; } } @@ -187,25 +278,43 @@ export class CollectionRequestService { requestId: string, request: Partial, ): Promise { - return await this.collectionReposistory.updateRequest( + const collection = await this.collectionReposistory.updateRequest( collectionId, requestId, request, ); + if (request?.currentBranch) { + await this.branchRepository.updateRequestInBranch( + collectionId, + request.currentBranch, + requestId, + request, + ); + } + return collection; } async deleteRequest( collectionId: string, requestId: string, noOfRequests: number, - folderId?: string, + requestDto: Partial, ): Promise> { - return await this.collectionReposistory.deleteRequest( + const collection = await this.collectionReposistory.deleteRequest( collectionId, requestId, noOfRequests, - folderId, + requestDto?.folderId, ); + if (requestDto.currentBranch) { + await this.branchRepository.deleteRequestInBranch( + collectionId, + requestDto.currentBranch, + requestId, + requestDto.folderId, + ); + } + return collection; } async getNoOfRequest(collectionId: string): Promise { From 6348718fb0cefb72772972674c81e5e52fe1bab8 Mon Sep 17 00:00:00 2001 From: Astitva877 Date: Fri, 15 Mar 2024 12:06:35 +0530 Subject: [PATCH 40/43] fix: fixed crash on add request [] --- src/modules/common/services/parser.service.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/modules/common/services/parser.service.ts b/src/modules/common/services/parser.service.ts index c65c56d8..bad06305 100644 --- a/src/modules/common/services/parser.service.ts +++ b/src/modules/common/services/parser.service.ts @@ -156,7 +156,7 @@ export class ParserService { }); //Check items on request level const mergedFolderRequests: CollectionItem[] = this.compareAndMerge( - branch.items[x].items, + branch.items[x].items ?? [], newItem[0]?.items || [], ); mergedFolderItems[x].items = mergedFolderRequests; @@ -330,7 +330,7 @@ export class ParserService { ): CollectionItem[] { const newItemMap = newItems ? new Map( - newItems.map((item) => [ + newItems?.map((item) => [ item.type === ItemTypeEnum.FOLDER ? item.name : item.name + item.request?.method, @@ -340,7 +340,7 @@ export class ParserService { : new Map(); const existingItemMap = existingitems ? new Map( - existingitems.map((item) => [ + existingitems?.map((item) => [ item.type === ItemTypeEnum.FOLDER ? item.name : item.name + item.request?.method, @@ -349,7 +349,7 @@ export class ParserService { ) : new Map(); // Merge old and new items while marking deleted - const mergedArray: CollectionItem[] = existingitems.map((existingItem) => { + const mergedArray: CollectionItem[] = existingitems?.map((existingItem) => { if ( newItemMap.has( existingItem.type === ItemTypeEnum.FOLDER From ff425d09edb7508133fdebe2319007c14616f035 Mon Sep 17 00:00:00 2001 From: Astitva877 Date: Fri, 15 Mar 2024 16:30:05 +0530 Subject: [PATCH 41/43] fix: fixed curl import for some curls [] --- src/modules/app/app.service.ts | 12 +++++++++++- src/modules/workspace/services/collection.service.ts | 4 ++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/modules/app/app.service.ts b/src/modules/app/app.service.ts index 607e1031..515d57ca 100644 --- a/src/modules/app/app.service.ts +++ b/src/modules/app/app.service.ts @@ -70,15 +70,25 @@ export class AppService { }; } + async formatCur(curlCommand: string) { + curlCommand = curlCommand.replace(/^curl/i, "curl"); + + // Remove extra spaces and line breaks + curlCommand = curlCommand.replace(/\s+/g, " ").trim(); + + return curlCommand; + } + async parseCurl(req: FastifyRequest): Promise { try { const curlconverter = await this.importCurlConverter(); const { toJsonString } = curlconverter; const curl = req.body as string; + const updatedCurl = await this.formatCur(curl); if (!curl || !curl.length) { throw new Error(); } - return this.transformRequest(JSON.parse(toJsonString(curl))); + return this.transformRequest(JSON.parse(toJsonString(updatedCurl))); } catch (error) { console.error("Error parsing :", error); throw new BadRequestException("Invalid Curl"); diff --git a/src/modules/workspace/services/collection.service.ts b/src/modules/workspace/services/collection.service.ts index b0e00eb5..b23f57d1 100644 --- a/src/modules/workspace/services/collection.service.ts +++ b/src/modules/workspace/services/collection.service.ts @@ -172,8 +172,8 @@ export class CollectionService { collectionId, branchName, ); - for (let index = 0; index < branch.items.length; index++) { - if (branch.items[index].type === ItemTypeEnum.FOLDER) { + for (let index = 0; index < branch?.items.length; index++) { + if (branch?.items[index].type === ItemTypeEnum.FOLDER) { for (let flag = 0; flag < branch.items[index].items.length; flag++) { const deletedDate = new Date( branch.items[index].items[flag].updatedAt, From ccfc2441b4daf3ff4853d17c4b21452448dc15d7 Mon Sep 17 00:00:00 2001 From: Astitva877 Date: Fri, 15 Mar 2024 16:34:48 +0530 Subject: [PATCH 42/43] fix: Spellling fix [] --- src/modules/app/app.service.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/modules/app/app.service.ts b/src/modules/app/app.service.ts index 515d57ca..91e3973f 100644 --- a/src/modules/app/app.service.ts +++ b/src/modules/app/app.service.ts @@ -70,7 +70,7 @@ export class AppService { }; } - async formatCur(curlCommand: string) { + async formatCurl(curlCommand: string) { curlCommand = curlCommand.replace(/^curl/i, "curl"); // Remove extra spaces and line breaks @@ -84,7 +84,7 @@ export class AppService { const curlconverter = await this.importCurlConverter(); const { toJsonString } = curlconverter; const curl = req.body as string; - const updatedCurl = await this.formatCur(curl); + const updatedCurl = await this.formatCurl(curl); if (!curl || !curl.length) { throw new Error(); } From e12d6b12a0df3ec93292165195aad4fa24099290 Mon Sep 17 00:00:00 2001 From: Astitva877 Date: Fri, 15 Mar 2024 17:21:35 +0530 Subject: [PATCH 43/43] fix: added source at folder level [] --- src/modules/common/services/helper/oapi2.transformer.ts | 1 + src/modules/common/services/helper/oapi3.transformer.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/src/modules/common/services/helper/oapi2.transformer.ts b/src/modules/common/services/helper/oapi2.transformer.ts index 1435e8b1..b4f697d0 100644 --- a/src/modules/common/services/helper/oapi2.transformer.ts +++ b/src/modules/common/services/helper/oapi2.transformer.ts @@ -72,6 +72,7 @@ export function createCollectionItems( folderObj.name = item.tag; folderObj.description = tagDescription; folderObj.isDeleted = false; + folderObj.source = SourceTypeEnum.SPEC; folderObj.type = ItemTypeEnum.FOLDER; folderObj.id = uuidv4(); folderObj.items = []; diff --git a/src/modules/common/services/helper/oapi3.transformer.ts b/src/modules/common/services/helper/oapi3.transformer.ts index c372405f..b5da3d1b 100644 --- a/src/modules/common/services/helper/oapi3.transformer.ts +++ b/src/modules/common/services/helper/oapi3.transformer.ts @@ -64,6 +64,7 @@ export function createCollectionItems( folderObj.name = item.tag; folderObj.description = tagDescription; folderObj.isDeleted = false; + folderObj.source = SourceTypeEnum.SPEC; folderObj.type = ItemTypeEnum.FOLDER; folderObj.id = uuidv4(); folderObj.items = [];