From 4cb6f3c2cfc61f0023a893cf804f073161e1f7f2 Mon Sep 17 00:00:00 2001 From: confused-Techie Date: Mon, 22 Jan 2024 00:24:51 -0800 Subject: [PATCH 01/24] Add script to build OpenAPI docs --- scripts/openapi/main.js | 181 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 181 insertions(+) create mode 100644 scripts/openapi/main.js diff --git a/scripts/openapi/main.js b/scripts/openapi/main.js new file mode 100644 index 00000000..712eb38e --- /dev/null +++ b/scripts/openapi/main.js @@ -0,0 +1,181 @@ +// This script will be used in order to update the Open API spec according to whatever changes may exist within the codebase. +// We will accomplish this by: +// - Collecting our constant values +// - Collecting all endpoints we are concerned with + +// TODO +// - Get all endpoints updated with this information +// - find way to provide parameters for raw endpoint objects +// - determine how we want to better provide for the api key info +// - add options to endpoints +// - add rate limit info + +const fs = require("fs"); +const path = require("path"); +const yaml = require("js-yaml"); + +const packageJSON = require("../../package.json"); +const endpoints = require("../../src/controllers/endpoints.js"); +const queryParameters = require("../../src/query_parameters/index.js"); +const MODEL_DIR = "tests/models"; +const MODEL_DIR_FS = `./${MODEL_DIR}`; +const MODEL_DIR_REQUIRE = `../../${MODEL_DIR}`; +const SPEC_LOC = "./docs/swagger/openapi3_def.yaml"; + +// This is the object that will actually hold our specification until it's written +const spec = { + openapi: "3.1.0", + info: { + version: packageJSON.version, + title: "Pulsar", // Could use the packageJSON.title + description: "Allows for the management, viewing, and downloading of packages and themes for use within the Pulsar editor.", + // ^^ Could use packageJSON.description + license: { + name: "MIT", + identifier: "MIT" + } + }, + servers: [ + { + url: "https://api.pulsar-edit.dev", + description: "Production Server" + }, + { + url: "http://localhost:8080", + description: "Locally hosted development server" + } + ], +}; + +// With our initial setup done, lets add our paths onto the schema +spec.paths = {}; +constructAndAddPaths(); + +// Now to add our components +spec.components = {}; +// Starting with parameters +spec.components.parameters = {}; +constructAndAddParameters(); +// Finally add our schemas +spec.components.schemas = {}; +constructAndAddSchemas(); + +// Now to write out file +const specFile = yaml.dump(spec, { + noRefs: true +}); + +fs.writeFileSync(SPEC_LOC, specFile, { encoding: "utf8" }); + +function constructAndAddPaths() { + for (const node of endpoints) { + for (const ePath of node.endpoint.paths) { + + spec.paths[createPathString(ePath)] = { + [node.endpoint.method.toLowerCase()]: { + ...( typeof node.docs.summary === "string" && { summary: node.docs.summary } ), + ...( typeof node.docs.description === "string" && { description: node.docs.description } ), + ...( node.docs.deprecated && { deprecated: true } ), + responses: craftResponsesFromObject(node), + parameters: craftParametersFromObject(node) + } // TODO Add Options returns here, once I read up on the response schema enough to include it + }; + + } + } +} + +function createPathString(ePath) { + // Here we take a path like `/api/packages/:packageName` => `/api/packages/{packageName}` + let original = ePath; + let output = ""; + + let hangingBracket = false; + + for (let i = 0; i < original.length; i++) { + let char = original.charAt(i); + + if (char === ":") { + output += "{"; + hangingBracket = true; + } else if (i === original.length - 1) { + // we are at the end + if (hangingBracket) { + output += char; + output += "}"; + hangingBracket = false; + } else { + output += char; + } + } else if (char === "/") { + if (hangingBracket) { + output += "}"; + output += char; + hangingBracket = false; + } else { + output += char; + } + } else { + output += char; + } + } + + return output; +} + +function craftResponsesFromObject(node) { + // Takes a single endpoint node, and returns an object of responses + const responses = {}; + + for (const response in node.docs.responses) { + responses[response] = node.docs.responses[response]; + + // We just append the whole object since the schema is nearly identical + // Except we need to handle any replacements of the content types + if (responses[response].content) { + for (const format in responses[response].content) { + + if (typeof responses[response].content[format] === "string" && responses[response].content[format].startsWith("$")) { + // The string value of the format entry begins with "$" so we will want to replace + // it's content with either a reference to the object schema, or directly with the schema + responses[response].content[format] = { schema: { "$ref": `#/components/schemas/${responses[response].content[format].replace("$", "")}`}}; + } + } + } + } + + return responses; +} + +function craftParametersFromObject(node) { + // Takes a single endpoint node and returns an object of the parameters + const params = []; + + for (const param in node.params) { + params.push({ + "$ref": `#/components/parameters/${param}` + }); + } + + return params; +} + +function constructAndAddParameters() { + for (const param in queryParameters.schema) { + spec.components.parameters[queryParameters.schema[param].name] = queryParameters.schema[param]; + } +} + +function constructAndAddSchemas() { + let files = fs.readdirSync(MODEL_DIR_FS); + + // The schemas expect Joi to be globally defined for their test usage. + // We don't need it here, but we don't want them to error out + const Joi = require("joi"); + global.Joi = Joi; + + for (const file of files) { + const content = require(path.join(MODEL_DIR_REQUIRE, file)); + spec.components.schemas[file.replace(".js", "")] = content.schema; + } +} From c06319be62cc7c38f6801ad067382a0a96e27147 Mon Sep 17 00:00:00 2001 From: confused-Techie Date: Mon, 22 Jan 2024 00:25:24 -0800 Subject: [PATCH 02/24] Update controllers and models to work --- src/controllers/getStars.js | 14 ++++++-------- src/controllers/getUpdates.js | 14 ++++++-------- ...ackageNameVersionsVersionNameEventsUninstall.js | 1 + tests/models/packageObjectFullArray.js | 7 ++++++- tests/models/packageObjectShortArray.js | 7 ++++++- 5 files changed, 25 insertions(+), 18 deletions(-) diff --git a/src/controllers/getStars.js b/src/controllers/getStars.js index 28932788..d2fd7742 100644 --- a/src/controllers/getStars.js +++ b/src/controllers/getStars.js @@ -5,15 +5,13 @@ module.exports = { docs: { summary: "List the authenticated users' starred packages.", - responses: [ - { - 200: { - description: - "Return a value similar to `GET /api/packages`, an array of package objects.", - content: {}, - }, + responses: { + 200: { + description: + "Return a value similar to `GET /api/packages`, an array of package objects.", + content: {}, }, - ], + }, }, endpoint: { method: "GET", diff --git a/src/controllers/getUpdates.js b/src/controllers/getUpdates.js index 1615f08b..4ff9132a 100644 --- a/src/controllers/getUpdates.js +++ b/src/controllers/getUpdates.js @@ -7,15 +7,13 @@ module.exports = { summary: "List Pulsar Updates", description: "Currently returns 'Not Implemented' as Squirrel AutoUpdate is not supported.", - responses: [ - { - 200: { - description: - "Atom update feed, following the format expected by Squirrel.", - content: {}, - }, + responses: { + 200: { + description: + "Atom update feed, following the format expected by Squirrel.", + content: {}, }, - ], + }, }, endpoint: { method: "GET", diff --git a/src/controllers/postPackagesPackageNameVersionsVersionNameEventsUninstall.js b/src/controllers/postPackagesPackageNameVersionsVersionNameEventsUninstall.js index 86e34939..310c2901 100644 --- a/src/controllers/postPackagesPackageNameVersionsVersionNameEventsUninstall.js +++ b/src/controllers/postPackagesPackageNameVersionsVersionNameEventsUninstall.js @@ -5,6 +5,7 @@ module.exports = { docs: { summary: "Previously undocumented endpoint. Since v1.0.2 has no effect.", + deprecated: true }, endpoint: { method: "POST", diff --git a/tests/models/packageObjectFullArray.js b/tests/models/packageObjectFullArray.js index 62729a75..83089cbf 100644 --- a/tests/models/packageObjectFullArray.js +++ b/tests/models/packageObjectFullArray.js @@ -1,5 +1,10 @@ module.exports = { - schema: {}, + schema: { + type: "array", + items: { + "$ref": "#/components/schemas/packageObjectFull" + } + }, example: [require("./packageObjectFull.js").example], test: Joi.array().items(require("./packageObjectFull.js").test).required(), }; diff --git a/tests/models/packageObjectShortArray.js b/tests/models/packageObjectShortArray.js index 887ea550..ee4064fc 100644 --- a/tests/models/packageObjectShortArray.js +++ b/tests/models/packageObjectShortArray.js @@ -1,5 +1,10 @@ module.exports = { - schema: {}, + schema: { + type: "array", + items: { + "$ref": "#/components/schemas/packageObjectShort" + } + }, example: [require("./packageObjectShort.js").example], test: Joi.array().items(require("./packageObjectShort.js").test).required(), }; From b0bd94328c69a795f787e333236d352b03ceff34 Mon Sep 17 00:00:00 2001 From: confused-Techie Date: Mon, 22 Jan 2024 00:25:35 -0800 Subject: [PATCH 03/24] Add new OpenAPI docs --- docs/swagger/index.html | 4 +- docs/swagger/openapi3_def.yaml | 953 +++++++++++++++++---------------- 2 files changed, 486 insertions(+), 471 deletions(-) diff --git a/docs/swagger/index.html b/docs/swagger/index.html index b3df89a8..c42928c9 100644 --- a/docs/swagger/index.html +++ b/docs/swagger/index.html @@ -7,13 +7,13 @@ SwaggerUI