Skip to content

Commit

Permalink
enhance nodejs-openapi action
Browse files Browse the repository at this point in the history
update nodejs-prettier action
update nodejs action
  • Loading branch information
TimurRin committed Sep 13, 2024
1 parent 31ef4af commit c622d86
Show file tree
Hide file tree
Showing 5 changed files with 124 additions and 80 deletions.
4 changes: 2 additions & 2 deletions anca.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@
"dataVersion": 0,
"version": {
"latest": "0.1.0-dev.2",
"latestNext": "0.1.0-dev.2+next.20240912_150037",
"timestamp": 1726153237
"latestNext": "0.1.0-dev.2+next.20240913_091654",
"timestamp": 1726219014
},
"files": [
{
Expand Down
185 changes: 115 additions & 70 deletions src/actions/nodejs-openapi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,30 @@ function getCodeModelPath(development: AncaDevelopment, model: string) {
: path.resolve(modelsLocation.codePath, `${model}.js`);
}

/**
*
* @param development
*/
function generateTypeScriptEssentialModels(development: AncaDevelopment) {
const modelsLocation = path.resolve(development.fullPath, "./src/models");

console.log(
"generateTypeScriptEssentialModels",
"modelsLocation",
modelsLocation,
);

fs.writeFileSync(
path.join(modelsLocation, "ServiceResponse.ts"),
`export interface ServiceResponse<T, C> {
code: C;
data: T;
error?: string;
}
`,
);
}

/**
* Generate TypeScript models (interfaces) based on OpenAPI schema
* @param development
Expand Down Expand Up @@ -357,6 +381,8 @@ export async function generateNodejsOpenapiFiles(development: AncaDevelopment) {
return;
}

generateTypeScriptEssentialModels(development);

const { modelsByFile } = generateTypeScriptModels(development, openapi);

const controllersDir = path.join(development.fullPath, "src", "controllers");
Expand All @@ -372,6 +398,7 @@ export async function generateNodejsOpenapiFiles(development: AncaDevelopment) {
}

let routesImports = "";
let routesAuth = false;
let routesFunctions = "";

const pathsMethods: {
Expand All @@ -380,6 +407,7 @@ export async function generateNodejsOpenapiFiles(development: AncaDevelopment) {
method: string;
operation: any;
apiPath: string;
responseCodes: string;
}[] = [];

if (openapi.paths) {
Expand All @@ -396,102 +424,119 @@ export async function generateNodejsOpenapiFiles(development: AncaDevelopment) {
apiPath,
);
const functionName = `route${capitalize(fileName)}`;

const responseCodes: Set<number> = new Set<number>();
if (operation.responses) {
Object.keys(operation.responses).forEach((code: string) => {
responseCodes.add(parseInt(code) || 500);
});
}

if (responseCodes.size === 0) {
responseCodes.add(200);
}

pathsMethods.push({
apiPath,
fileName,
functionName,
method,
operation,
responseCodes: Array.from(responseCodes).sort().join(" | "),
});

routesFunctions += `router.${method}("${apiPath}", ${functionName});\n`;
if (!routesAuth) {
routesAuth = true;
routesImports += `import { isAuthenticated } from "./middleware/isAuthenticated.js";\n`;
}

routesFunctions += `router.${method}("${apiPath}", ${operation.security ? "isAuthenticated, " : ""}${functionName});\n`;
});
});
}

pathsMethods.sort((a, b) => a.fileName.localeCompare(b.fileName));

pathsMethods.forEach(({ fileName, functionName, operation }) => {
routesImports += `import ${functionName} from "./controllers/${fileName}.js";\n`;
pathsMethods.forEach(
({ fileName, functionName, operation, responseCodes }) => {
routesImports += `import ${functionName} from "./controllers/${fileName}.js";\n`;

const controllerFile = path.join(controllersDir, `${fileName}.ts`);
const serviceFile = path.join(servicesDir, `${fileName}.ts`);
const controllerFile = path.join(controllersDir, `${fileName}.ts`);
const serviceFile = path.join(servicesDir, `${fileName}.ts`);

const fileModelData = modelsByFile[fileName];
const fileModelData = modelsByFile[fileName];

let responsesImportContent = "";
fileModelData?.response?.forEach((responseModelData) => {
responsesImportContent += `import { ${responseModelData.name} } from "${getCodeModelPath(development, responseModelData.name)}";\n`;
});

const responsesTypesContent =
fileModelData?.response?.map(getModelData).join(" | ") || "unknown";
let responsesImportContent = `import { ServiceResponse } from "../models/ServiceResponse.js";\n`;
fileModelData?.response?.forEach((responseModelData) => {
responsesImportContent += `import { ${responseModelData.name} } from "${getCodeModelPath(development, responseModelData.name)}";\n`;
});
const responsesTypesContent =
fileModelData?.response?.map(getModelData).join(" | ") || "unknown";

// eslint-disable-next-line sonarjs/no-gratuitous-expressions, no-constant-condition
if (true) {
// !fs.existsSync(serviceFile)
let serviceContent = "";
// eslint-disable-next-line sonarjs/no-gratuitous-expressions, no-constant-condition
if (true) {
// !fs.existsSync(serviceFile)
let serviceContent = "";

if (responsesImportContent) {
serviceContent += responsesImportContent;
serviceContent += `\n`;
}

serviceContent += "/**\n";
serviceContent += ` * ${operation.summary || ""}\n`;
serviceContent += " */\n";
serviceContent += `export async function ${fileName}(): Promise<${responsesTypesContent}> {\n`;
serviceContent += ` // This stub is generated if this file doesn't exist.\n`;
serviceContent += ` // You can change body of this function, but it should comply with controllers' call.\n`;
serviceContent += ` return ${responsesTypesContent.includes("[]") ? "[]" : responsesTypesContent !== "unknown" ? "{}" : "null"};\n`;
serviceContent += `}\n`;
serviceContent += "/**\n";
serviceContent += ` * ${operation.summary || ""}\n`;
serviceContent += " */\n";
serviceContent += `export async function ${fileName}(): Promise<ServiceResponse<${responsesTypesContent}, ${responseCodes}>> {\n`;
serviceContent += ` // This stub is generated if this file doesn't exist.\n`;
serviceContent += ` // You can change body of this function, but it should comply with controllers' call.\n`;
serviceContent += ` return { code: 200, data: ${responsesTypesContent.includes("[]") ? "[]" : responsesTypesContent !== "unknown" ? "{}" : "null"} };\n`;
serviceContent += `}\n`;

fs.writeFileSync(serviceFile, serviceContent);
}
fs.writeFileSync(serviceFile, serviceContent);
}

let controllerContent = "";
let controllerContent = "";

controllerContent += `import { Request, Response } from 'express';\n`;
controllerContent += `import { ${fileName} } from "../services/${fileName}.js";\n`;
if (fileModelData?.request) {
controllerContent += `import { ${fileModelData.request.name} } from "${getCodeModelPath(development, fileModelData.request.name)}";\n`;
}
if (fileModelData?.params) {
controllerContent += `import { ${fileModelData.params.name} } from "${getCodeModelPath(development, fileModelData.params.name)}";\n`;
}
if (fileModelData?.query) {
controllerContent += `import { ${fileModelData.query.name} } from "${getCodeModelPath(development, fileModelData.query.name)}";\n`;
}
if (responsesImportContent) {
controllerContent += responsesImportContent;
}
controllerContent += `import { Request, Response } from 'express';\n`;
controllerContent += `import { ${fileName} } from "../services/${fileName}.js";\n`;
if (fileModelData?.request) {
controllerContent += `import { ${fileModelData.request.name} } from "${getCodeModelPath(development, fileModelData.request.name)}";\n`;
}
if (fileModelData?.params) {
controllerContent += `import { ${fileModelData.params.name} } from "${getCodeModelPath(development, fileModelData.params.name)}";\n`;
}
if (fileModelData?.query) {
controllerContent += `import { ${fileModelData.query.name} } from "${getCodeModelPath(development, fileModelData.query.name)}";\n`;
}
if (responsesImportContent) {
controllerContent += responsesImportContent;
}

controllerContent += `\n`;
controllerContent += `/**\n`;
controllerContent += ` * ${operation.summary || ""}\n`;
controllerContent += ` * @param req\n`;
controllerContent += ` * @param res\n`;
controllerContent += ` */\n`;
controllerContent += `export default async function (req: Request<${getModelData(fileModelData?.params)}, ${"unknown"}, ${getModelData(fileModelData?.request)}, ${getModelData(fileModelData?.query)}>, res: Response<${responsesTypesContent}>) {\n`;
controllerContent += ` try {\n`;
controllerContent += ` const result: ${responsesTypesContent} = await ${fileName}();\n`;
switch (operation.responses && operation.responses[200]?.content.type) {
case "application/json":
controllerContent += ` res.status(200).json(result);\n`;
break;
case "application/xml":
controllerContent += ` res.status(200).xml(result);\n`;
break;
default:
controllerContent += ` res.status(200).send(result);\n`;
}
controllerContent += ` } catch (error) {\n`;
controllerContent += ` console.error(error);\n`;
controllerContent += ` res.end();\n`;
controllerContent += ` }\n`;
controllerContent += `}\n`;
fs.writeFileSync(controllerFile, controllerContent);
});
controllerContent += `\n`;
controllerContent += `/**\n`;
controllerContent += ` * ${operation.summary || ""}\n`;
controllerContent += ` * @param req\n`;
controllerContent += ` * @param res\n`;
controllerContent += ` */\n`;
controllerContent += `export default async function (req: Request<${getModelData(fileModelData?.params)}, ${"unknown"}, ${getModelData(fileModelData?.request)}, ${getModelData(fileModelData?.query)}>, res: Response<${responsesTypesContent}>) {\n`;
controllerContent += ` try {\n`;
controllerContent += ` const result: ServiceResponse<${responsesTypesContent}, ${responseCodes}> = await ${fileName}();\n`;
switch (operation.responses && operation.responses[200]?.content.type) {
case "application/json":
controllerContent += ` res.status(result.code).json(result.data);\n`;
break;
case "application/xml":
controllerContent += ` res.status(result.code).xml(result.data);\n`;
break;
default:
controllerContent += ` res.status(result.code).send(result.data);\n`;
}
controllerContent += ` } catch (error) {\n`;
controllerContent += ` console.error(error);\n`;
controllerContent += ` res.end();\n`;
controllerContent += ` }\n`;
controllerContent += `}\n`;
fs.writeFileSync(controllerFile, controllerContent);
},
);

fs.writeFileSync(
routesFile,
Expand Down
3 changes: 3 additions & 0 deletions src/actions/nodejs-prettier.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ CHANGELOG.md
# Anca
anca.json
esbuild.js
eslint.config.js
openapi.json
`;

const RC_FILE_PATH = ".prettierrc";
Expand Down
8 changes: 2 additions & 6 deletions src/actions/nodejs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,20 +128,16 @@ const DEPENDENCIES_API: string[] = [
"dotenv",
"express",
"express-session",
"jsonwebtoken",
"knex",
"passport",
"passport-local",
"swagger-ui-express",
"winston",
];

const DEV_DEPENDENCIES_API: string[] = [
"@cinnabar-forge/eslint-plugin",
"@types/express",
"@types/express-session",
"@types/passport",
"@types/passport-local",
"@types/swagger-ui-express",
"@types/jsonwebtoken",
"esbuild",
"typescript",
];
Expand Down
4 changes: 2 additions & 2 deletions src/cinnabar.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// This file was generated by Cinnabar Meta. Do not edit.

export const CINNABAR_PROJECT_TIMESTAMP = 1726153237;
export const CINNABAR_PROJECT_VERSION = "0.1.0-dev.2+next.20240912_150037";
export const CINNABAR_PROJECT_TIMESTAMP = 1726219014;
export const CINNABAR_PROJECT_VERSION = "0.1.0-dev.2+next.20240913_091654";

0 comments on commit c622d86

Please sign in to comment.