diff --git a/src/DataAccessGenerator.ts b/src/DataAccessGenerator.ts index 43a7e29..4fc8142 100644 --- a/src/DataAccessGenerator.ts +++ b/src/DataAccessGenerator.ts @@ -1,10 +1,15 @@ import { Collection, Entity, Property } from "./MySchema"; import { FileWrite } from "./app"; +import { modelModuleName } from "./ModelGenerator"; export function generateDataAccess(collections: Collection[]): FileWrite[] { return collections.map(generateDataAccessFile); } +export function dataAccessModuleName(collection: Collection): string { + return `${collection.name}DataAccess`; +} + function generateDataAccessFile(collection: Collection): FileWrite { const definitions: string[] = [ defineImports(collection), @@ -16,14 +21,14 @@ ]; return { - location: collection.name + "DataAccess.ts", + location: dataAccessModuleName(collection) + ".ts", content: definitions.join("\n\n") + "\n", }; } function defineImports(collection: Collection): string { const entity = collection.entities; - return `import { ${collection.name}, ${entity.name} } from "./${collection.name}Model"; + return `import { ${collection.name}, ${entity.name} } from "./${modelModuleName(collection)}"; import * as fs from "fs";`; } diff --git a/src/DataAccessGenerator.ts b/src/DataAccessGenerator.ts index 43a7e29..4fc8142 100644 --- a/src/DataAccessGenerator.ts +++ b/src/DataAccessGenerator.ts @@ -1,10 +1,15 @@ import { Collection, Entity, Property } from "./MySchema"; import { FileWrite } from "./app"; +import { modelModuleName } from "./ModelGenerator"; export function generateDataAccess(collections: Collection[]): FileWrite[] { return collections.map(generateDataAccessFile); } +export function dataAccessModuleName(collection: Collection): string { + return `${collection.name}DataAccess`; +} + function generateDataAccessFile(collection: Collection): FileWrite { const definitions: string[] = [ defineImports(collection), @@ -16,14 +21,14 @@ ]; return { - location: collection.name + "DataAccess.ts", + location: dataAccessModuleName(collection) + ".ts", content: definitions.join("\n\n") + "\n", }; } function defineImports(collection: Collection): string { const entity = collection.entities; - return `import { ${collection.name}, ${entity.name} } from "./${collection.name}Model"; + return `import { ${collection.name}, ${entity.name} } from "./${modelModuleName(collection)}"; import * as fs from "fs";`; } diff --git a/src/ExampleOutput/Middleware.ts b/src/ExampleOutput/Middleware.ts index e61fd9d..2a50fc3 100644 --- a/src/ExampleOutput/Middleware.ts +++ b/src/ExampleOutput/Middleware.ts @@ -1,4 +1,4 @@ -import express, { Request, Response, RequestHandler } from "express"; +import express from "express"; import * as DataAccess from "./DataAccess"; import { findSingle, toPayload } from "./Model"; @@ -9,7 +9,7 @@ // TODO get/post collection, get/post/delete single entity route.get("/nodes/:id(\\d+)", (req, res) => { - const id = Number.parseInt(req.param("id"), undefined); + const id = Number.parseInt(req.params.id, undefined); const node = findSingle(graph, id); if (node == null) { res.status(404).send("not found"); diff --git a/src/DataAccessGenerator.ts b/src/DataAccessGenerator.ts index 43a7e29..4fc8142 100644 --- a/src/DataAccessGenerator.ts +++ b/src/DataAccessGenerator.ts @@ -1,10 +1,15 @@ import { Collection, Entity, Property } from "./MySchema"; import { FileWrite } from "./app"; +import { modelModuleName } from "./ModelGenerator"; export function generateDataAccess(collections: Collection[]): FileWrite[] { return collections.map(generateDataAccessFile); } +export function dataAccessModuleName(collection: Collection): string { + return `${collection.name}DataAccess`; +} + function generateDataAccessFile(collection: Collection): FileWrite { const definitions: string[] = [ defineImports(collection), @@ -16,14 +21,14 @@ ]; return { - location: collection.name + "DataAccess.ts", + location: dataAccessModuleName(collection) + ".ts", content: definitions.join("\n\n") + "\n", }; } function defineImports(collection: Collection): string { const entity = collection.entities; - return `import { ${collection.name}, ${entity.name} } from "./${collection.name}Model"; + return `import { ${collection.name}, ${entity.name} } from "./${modelModuleName(collection)}"; import * as fs from "fs";`; } diff --git a/src/ExampleOutput/Middleware.ts b/src/ExampleOutput/Middleware.ts index e61fd9d..2a50fc3 100644 --- a/src/ExampleOutput/Middleware.ts +++ b/src/ExampleOutput/Middleware.ts @@ -1,4 +1,4 @@ -import express, { Request, Response, RequestHandler } from "express"; +import express from "express"; import * as DataAccess from "./DataAccess"; import { findSingle, toPayload } from "./Model"; @@ -9,7 +9,7 @@ // TODO get/post collection, get/post/delete single entity route.get("/nodes/:id(\\d+)", (req, res) => { - const id = Number.parseInt(req.param("id"), undefined); + const id = Number.parseInt(req.params.id, undefined); const node = findSingle(graph, id); if (node == null) { res.status(404).send("not found"); diff --git a/src/MiddlewareGenerator.ts b/src/MiddlewareGenerator.ts new file mode 100644 index 0000000..6731183 --- /dev/null +++ b/src/MiddlewareGenerator.ts @@ -0,0 +1,58 @@ +import { Description, FileWrite } from "./app"; +import { dataAccessModuleName } from "./DataAccessGenerator"; +import { modelModuleName } from "./ModelGenerator"; +import { Collection } from "./MySchema"; +import { collectionRoute } from "./OpenApiGenerator"; + +export function middlewareModuleName(collection: Collection): string { + return `${collection.name}Middleware`; +} + +export function generateMiddlewares(description: Description): FileWrite[] { + return description.collections.map(generateMiddleware); +} + +function generateMiddleware(collection: Collection): FileWrite { + const definitions = [ + defineImports(collection), + defineCreateMiddlewareFunction(collection), + ]; + return { + location: middlewareModuleName(collection) + ".ts", + content: definitions.join("\n\n") + "\n", + }; +} + +function defineImports(collection: Collection): string { + return `import express from "express"; +import * as DataAccess from "./${dataAccessModuleName(collection)}"; +import { findSingle, toPayload } from "./${modelModuleName(collection)}";`; +} + +function defineCreateMiddlewareFunction(collection: Collection): string { + const col = collection.name.toLowerCase(); + const route = collectionRoute(collection); + return `export function ${col}Middleware({ fileName }: { fileName: string }) { + const ${col} = DataAccess.load(fileName); + + const route = express.Router(); + + // TODO get/post collection, get/post/delete single entity + route.get("${route}/:id(\\d+)", (req, res) => { + const id = Number.parseInt(req.params.id, undefined); + const item = findSingle(${col}, id); + if (item == null) { + res.status(404).send("not found"); + return; + } + res.send(toPayload(item)); + }); + + route.get("${route}", (req, res) => { + const items = ${col}.items; + res.send(items.map(toPayload)); + }); + + return route; +}`; +} \ No newline at end of file diff --git a/src/DataAccessGenerator.ts b/src/DataAccessGenerator.ts index 43a7e29..4fc8142 100644 --- a/src/DataAccessGenerator.ts +++ b/src/DataAccessGenerator.ts @@ -1,10 +1,15 @@ import { Collection, Entity, Property } from "./MySchema"; import { FileWrite } from "./app"; +import { modelModuleName } from "./ModelGenerator"; export function generateDataAccess(collections: Collection[]): FileWrite[] { return collections.map(generateDataAccessFile); } +export function dataAccessModuleName(collection: Collection): string { + return `${collection.name}DataAccess`; +} + function generateDataAccessFile(collection: Collection): FileWrite { const definitions: string[] = [ defineImports(collection), @@ -16,14 +21,14 @@ ]; return { - location: collection.name + "DataAccess.ts", + location: dataAccessModuleName(collection) + ".ts", content: definitions.join("\n\n") + "\n", }; } function defineImports(collection: Collection): string { const entity = collection.entities; - return `import { ${collection.name}, ${entity.name} } from "./${collection.name}Model"; + return `import { ${collection.name}, ${entity.name} } from "./${modelModuleName(collection)}"; import * as fs from "fs";`; } diff --git a/src/ExampleOutput/Middleware.ts b/src/ExampleOutput/Middleware.ts index e61fd9d..2a50fc3 100644 --- a/src/ExampleOutput/Middleware.ts +++ b/src/ExampleOutput/Middleware.ts @@ -1,4 +1,4 @@ -import express, { Request, Response, RequestHandler } from "express"; +import express from "express"; import * as DataAccess from "./DataAccess"; import { findSingle, toPayload } from "./Model"; @@ -9,7 +9,7 @@ // TODO get/post collection, get/post/delete single entity route.get("/nodes/:id(\\d+)", (req, res) => { - const id = Number.parseInt(req.param("id"), undefined); + const id = Number.parseInt(req.params.id, undefined); const node = findSingle(graph, id); if (node == null) { res.status(404).send("not found"); diff --git a/src/MiddlewareGenerator.ts b/src/MiddlewareGenerator.ts new file mode 100644 index 0000000..6731183 --- /dev/null +++ b/src/MiddlewareGenerator.ts @@ -0,0 +1,58 @@ +import { Description, FileWrite } from "./app"; +import { dataAccessModuleName } from "./DataAccessGenerator"; +import { modelModuleName } from "./ModelGenerator"; +import { Collection } from "./MySchema"; +import { collectionRoute } from "./OpenApiGenerator"; + +export function middlewareModuleName(collection: Collection): string { + return `${collection.name}Middleware`; +} + +export function generateMiddlewares(description: Description): FileWrite[] { + return description.collections.map(generateMiddleware); +} + +function generateMiddleware(collection: Collection): FileWrite { + const definitions = [ + defineImports(collection), + defineCreateMiddlewareFunction(collection), + ]; + return { + location: middlewareModuleName(collection) + ".ts", + content: definitions.join("\n\n") + "\n", + }; +} + +function defineImports(collection: Collection): string { + return `import express from "express"; +import * as DataAccess from "./${dataAccessModuleName(collection)}"; +import { findSingle, toPayload } from "./${modelModuleName(collection)}";`; +} + +function defineCreateMiddlewareFunction(collection: Collection): string { + const col = collection.name.toLowerCase(); + const route = collectionRoute(collection); + return `export function ${col}Middleware({ fileName }: { fileName: string }) { + const ${col} = DataAccess.load(fileName); + + const route = express.Router(); + + // TODO get/post collection, get/post/delete single entity + route.get("${route}/:id(\\d+)", (req, res) => { + const id = Number.parseInt(req.params.id, undefined); + const item = findSingle(${col}, id); + if (item == null) { + res.status(404).send("not found"); + return; + } + res.send(toPayload(item)); + }); + + route.get("${route}", (req, res) => { + const items = ${col}.items; + res.send(items.map(toPayload)); + }); + + return route; +}`; +} \ No newline at end of file diff --git a/src/ModelGenerator.ts b/src/ModelGenerator.ts index d471407..147ca0e 100644 --- a/src/ModelGenerator.ts +++ b/src/ModelGenerator.ts @@ -6,9 +6,13 @@ return collections.map(generateModelFile); } +export function modelModuleName(collection: Collection): string { + return `${collection.name}Model`; +} + function generateModelFile(collection: Collection): FileWrite { - const fileName = collection.name + "Model.ts"; + const fileName = modelModuleName(collection) + ".ts"; const definitions: string[] = [ defineCollection(collection), defineEntity(collection.entities), diff --git a/src/DataAccessGenerator.ts b/src/DataAccessGenerator.ts index 43a7e29..4fc8142 100644 --- a/src/DataAccessGenerator.ts +++ b/src/DataAccessGenerator.ts @@ -1,10 +1,15 @@ import { Collection, Entity, Property } from "./MySchema"; import { FileWrite } from "./app"; +import { modelModuleName } from "./ModelGenerator"; export function generateDataAccess(collections: Collection[]): FileWrite[] { return collections.map(generateDataAccessFile); } +export function dataAccessModuleName(collection: Collection): string { + return `${collection.name}DataAccess`; +} + function generateDataAccessFile(collection: Collection): FileWrite { const definitions: string[] = [ defineImports(collection), @@ -16,14 +21,14 @@ ]; return { - location: collection.name + "DataAccess.ts", + location: dataAccessModuleName(collection) + ".ts", content: definitions.join("\n\n") + "\n", }; } function defineImports(collection: Collection): string { const entity = collection.entities; - return `import { ${collection.name}, ${entity.name} } from "./${collection.name}Model"; + return `import { ${collection.name}, ${entity.name} } from "./${modelModuleName(collection)}"; import * as fs from "fs";`; } diff --git a/src/ExampleOutput/Middleware.ts b/src/ExampleOutput/Middleware.ts index e61fd9d..2a50fc3 100644 --- a/src/ExampleOutput/Middleware.ts +++ b/src/ExampleOutput/Middleware.ts @@ -1,4 +1,4 @@ -import express, { Request, Response, RequestHandler } from "express"; +import express from "express"; import * as DataAccess from "./DataAccess"; import { findSingle, toPayload } from "./Model"; @@ -9,7 +9,7 @@ // TODO get/post collection, get/post/delete single entity route.get("/nodes/:id(\\d+)", (req, res) => { - const id = Number.parseInt(req.param("id"), undefined); + const id = Number.parseInt(req.params.id, undefined); const node = findSingle(graph, id); if (node == null) { res.status(404).send("not found"); diff --git a/src/MiddlewareGenerator.ts b/src/MiddlewareGenerator.ts new file mode 100644 index 0000000..6731183 --- /dev/null +++ b/src/MiddlewareGenerator.ts @@ -0,0 +1,58 @@ +import { Description, FileWrite } from "./app"; +import { dataAccessModuleName } from "./DataAccessGenerator"; +import { modelModuleName } from "./ModelGenerator"; +import { Collection } from "./MySchema"; +import { collectionRoute } from "./OpenApiGenerator"; + +export function middlewareModuleName(collection: Collection): string { + return `${collection.name}Middleware`; +} + +export function generateMiddlewares(description: Description): FileWrite[] { + return description.collections.map(generateMiddleware); +} + +function generateMiddleware(collection: Collection): FileWrite { + const definitions = [ + defineImports(collection), + defineCreateMiddlewareFunction(collection), + ]; + return { + location: middlewareModuleName(collection) + ".ts", + content: definitions.join("\n\n") + "\n", + }; +} + +function defineImports(collection: Collection): string { + return `import express from "express"; +import * as DataAccess from "./${dataAccessModuleName(collection)}"; +import { findSingle, toPayload } from "./${modelModuleName(collection)}";`; +} + +function defineCreateMiddlewareFunction(collection: Collection): string { + const col = collection.name.toLowerCase(); + const route = collectionRoute(collection); + return `export function ${col}Middleware({ fileName }: { fileName: string }) { + const ${col} = DataAccess.load(fileName); + + const route = express.Router(); + + // TODO get/post collection, get/post/delete single entity + route.get("${route}/:id(\\d+)", (req, res) => { + const id = Number.parseInt(req.params.id, undefined); + const item = findSingle(${col}, id); + if (item == null) { + res.status(404).send("not found"); + return; + } + res.send(toPayload(item)); + }); + + route.get("${route}", (req, res) => { + const items = ${col}.items; + res.send(items.map(toPayload)); + }); + + return route; +}`; +} \ No newline at end of file diff --git a/src/ModelGenerator.ts b/src/ModelGenerator.ts index d471407..147ca0e 100644 --- a/src/ModelGenerator.ts +++ b/src/ModelGenerator.ts @@ -6,9 +6,13 @@ return collections.map(generateModelFile); } +export function modelModuleName(collection: Collection): string { + return `${collection.name}Model`; +} + function generateModelFile(collection: Collection): FileWrite { - const fileName = collection.name + "Model.ts"; + const fileName = modelModuleName(collection) + ".ts"; const definitions: string[] = [ defineCollection(collection), defineEntity(collection.entities), diff --git a/src/OpenApiGenerator.ts b/src/OpenApiGenerator.ts index 573eb69..b59cb56 100644 --- a/src/OpenApiGenerator.ts +++ b/src/OpenApiGenerator.ts @@ -49,11 +49,19 @@ } function generatePaths(collections: Collection[]): OpenAPIV3.PathsObject { - return fromEntites(collections.map(generateCollectionPath)); + const paths = [ + ...collections.map(generateCollectionPath), + ...collections.map(generateEntityPath), + ]; + return fromEntites(paths); +} + +export function collectionRoute(collection: Collection): string { + return "/" + collection.name.toLowerCase(); } function generateCollectionPath(collection: Collection): [string, OpenAPIV3.PathItemObject] { - const route: string = "/" + collection.name.toLowerCase(); + const route = collectionRoute(collection); const path: OpenAPIV3.PathItemObject = { get: generateGetAllOperation(collection), }; @@ -81,6 +89,53 @@ return operation; } +function generateEntityPath(collection: Collection): [string, OpenAPIV3.PathItemObject] { + const route = itemRoute(collection); + const path: OpenAPIV3.PathItemObject = { + get: generateGetSpecificOperation(collection.entities), + }; + return [route, path]; +} + +function itemRoute(collection: Collection): string { + return collectionRoute(collection) + "/{" + idParameterName(collection.entities) + "}"; +} + +function idParameterName(entity: Entity): string { + return entity.name.toLowerCase() + "Id"; +} + +function generateGetSpecificOperation(entity: Entity): OpenAPIV3.OperationObject { + const operation: OpenAPIV3.OperationObject = { + parameters: [ + { + in: "path", + name: idParameterName(entity), + required: true, + schema: { + type: "integer", + }, + }, + ], + responses: { + "200": { + description: "Returns one specific " + entity.name, + content: { + "application/json": { + schema: { + $ref: "#/components/schemas/" + entity.name, + } + } + } + }, + "404": { + $ref: "#/components/responses/NotFound", + } + } + }; + return operation; +} + const NotFoundResponse: OpenAPIV3.ResponseObject = { description: "The specified resource does not exist.", content: { diff --git a/src/DataAccessGenerator.ts b/src/DataAccessGenerator.ts index 43a7e29..4fc8142 100644 --- a/src/DataAccessGenerator.ts +++ b/src/DataAccessGenerator.ts @@ -1,10 +1,15 @@ import { Collection, Entity, Property } from "./MySchema"; import { FileWrite } from "./app"; +import { modelModuleName } from "./ModelGenerator"; export function generateDataAccess(collections: Collection[]): FileWrite[] { return collections.map(generateDataAccessFile); } +export function dataAccessModuleName(collection: Collection): string { + return `${collection.name}DataAccess`; +} + function generateDataAccessFile(collection: Collection): FileWrite { const definitions: string[] = [ defineImports(collection), @@ -16,14 +21,14 @@ ]; return { - location: collection.name + "DataAccess.ts", + location: dataAccessModuleName(collection) + ".ts", content: definitions.join("\n\n") + "\n", }; } function defineImports(collection: Collection): string { const entity = collection.entities; - return `import { ${collection.name}, ${entity.name} } from "./${collection.name}Model"; + return `import { ${collection.name}, ${entity.name} } from "./${modelModuleName(collection)}"; import * as fs from "fs";`; } diff --git a/src/ExampleOutput/Middleware.ts b/src/ExampleOutput/Middleware.ts index e61fd9d..2a50fc3 100644 --- a/src/ExampleOutput/Middleware.ts +++ b/src/ExampleOutput/Middleware.ts @@ -1,4 +1,4 @@ -import express, { Request, Response, RequestHandler } from "express"; +import express from "express"; import * as DataAccess from "./DataAccess"; import { findSingle, toPayload } from "./Model"; @@ -9,7 +9,7 @@ // TODO get/post collection, get/post/delete single entity route.get("/nodes/:id(\\d+)", (req, res) => { - const id = Number.parseInt(req.param("id"), undefined); + const id = Number.parseInt(req.params.id, undefined); const node = findSingle(graph, id); if (node == null) { res.status(404).send("not found"); diff --git a/src/MiddlewareGenerator.ts b/src/MiddlewareGenerator.ts new file mode 100644 index 0000000..6731183 --- /dev/null +++ b/src/MiddlewareGenerator.ts @@ -0,0 +1,58 @@ +import { Description, FileWrite } from "./app"; +import { dataAccessModuleName } from "./DataAccessGenerator"; +import { modelModuleName } from "./ModelGenerator"; +import { Collection } from "./MySchema"; +import { collectionRoute } from "./OpenApiGenerator"; + +export function middlewareModuleName(collection: Collection): string { + return `${collection.name}Middleware`; +} + +export function generateMiddlewares(description: Description): FileWrite[] { + return description.collections.map(generateMiddleware); +} + +function generateMiddleware(collection: Collection): FileWrite { + const definitions = [ + defineImports(collection), + defineCreateMiddlewareFunction(collection), + ]; + return { + location: middlewareModuleName(collection) + ".ts", + content: definitions.join("\n\n") + "\n", + }; +} + +function defineImports(collection: Collection): string { + return `import express from "express"; +import * as DataAccess from "./${dataAccessModuleName(collection)}"; +import { findSingle, toPayload } from "./${modelModuleName(collection)}";`; +} + +function defineCreateMiddlewareFunction(collection: Collection): string { + const col = collection.name.toLowerCase(); + const route = collectionRoute(collection); + return `export function ${col}Middleware({ fileName }: { fileName: string }) { + const ${col} = DataAccess.load(fileName); + + const route = express.Router(); + + // TODO get/post collection, get/post/delete single entity + route.get("${route}/:id(\\d+)", (req, res) => { + const id = Number.parseInt(req.params.id, undefined); + const item = findSingle(${col}, id); + if (item == null) { + res.status(404).send("not found"); + return; + } + res.send(toPayload(item)); + }); + + route.get("${route}", (req, res) => { + const items = ${col}.items; + res.send(items.map(toPayload)); + }); + + return route; +}`; +} \ No newline at end of file diff --git a/src/ModelGenerator.ts b/src/ModelGenerator.ts index d471407..147ca0e 100644 --- a/src/ModelGenerator.ts +++ b/src/ModelGenerator.ts @@ -6,9 +6,13 @@ return collections.map(generateModelFile); } +export function modelModuleName(collection: Collection): string { + return `${collection.name}Model`; +} + function generateModelFile(collection: Collection): FileWrite { - const fileName = collection.name + "Model.ts"; + const fileName = modelModuleName(collection) + ".ts"; const definitions: string[] = [ defineCollection(collection), defineEntity(collection.entities), diff --git a/src/OpenApiGenerator.ts b/src/OpenApiGenerator.ts index 573eb69..b59cb56 100644 --- a/src/OpenApiGenerator.ts +++ b/src/OpenApiGenerator.ts @@ -49,11 +49,19 @@ } function generatePaths(collections: Collection[]): OpenAPIV3.PathsObject { - return fromEntites(collections.map(generateCollectionPath)); + const paths = [ + ...collections.map(generateCollectionPath), + ...collections.map(generateEntityPath), + ]; + return fromEntites(paths); +} + +export function collectionRoute(collection: Collection): string { + return "/" + collection.name.toLowerCase(); } function generateCollectionPath(collection: Collection): [string, OpenAPIV3.PathItemObject] { - const route: string = "/" + collection.name.toLowerCase(); + const route = collectionRoute(collection); const path: OpenAPIV3.PathItemObject = { get: generateGetAllOperation(collection), }; @@ -81,6 +89,53 @@ return operation; } +function generateEntityPath(collection: Collection): [string, OpenAPIV3.PathItemObject] { + const route = itemRoute(collection); + const path: OpenAPIV3.PathItemObject = { + get: generateGetSpecificOperation(collection.entities), + }; + return [route, path]; +} + +function itemRoute(collection: Collection): string { + return collectionRoute(collection) + "/{" + idParameterName(collection.entities) + "}"; +} + +function idParameterName(entity: Entity): string { + return entity.name.toLowerCase() + "Id"; +} + +function generateGetSpecificOperation(entity: Entity): OpenAPIV3.OperationObject { + const operation: OpenAPIV3.OperationObject = { + parameters: [ + { + in: "path", + name: idParameterName(entity), + required: true, + schema: { + type: "integer", + }, + }, + ], + responses: { + "200": { + description: "Returns one specific " + entity.name, + content: { + "application/json": { + schema: { + $ref: "#/components/schemas/" + entity.name, + } + } + } + }, + "404": { + $ref: "#/components/responses/NotFound", + } + } + }; + return operation; +} + const NotFoundResponse: OpenAPIV3.ResponseObject = { description: "The specified resource does not exist.", content: { diff --git a/src/app.ts b/src/app.ts index 1d57731..4abe7c3 100644 --- a/src/app.ts +++ b/src/app.ts @@ -4,6 +4,7 @@ import { generateModel } from "./ModelGenerator"; import { generateDataAccess } from "./DataAccessGenerator"; import { generateOpenAPI } from "./OpenApiGenerator"; +import { generateMiddlewares } from "./MiddlewareGenerator"; interface Options { typeScriptOutputFolder: string; @@ -42,22 +43,26 @@ function generate(options: Options, description: Description): FileWrite[] { // generate model files const modelWrites = generateModel(description.collections); - modelWrites.forEach(w => { w.location = join(options.typeScriptOutputFolder, w.location) }) + modelWrites.forEach(w => { w.location = join(options.typeScriptOutputFolder, w.location) }); // generate data access files const dataAccessWrites = generateDataAccess(description.collections); - dataAccessWrites.forEach(w => { w.location = join(options.typeScriptOutputFolder, w.location) }) + dataAccessWrites.forEach(w => { w.location = join(options.typeScriptOutputFolder, w.location) }); // generate openapi file const openapiDoc = generateOpenAPI(description); const openapiWrites: FileWrite = { location: options.openapiOutput, content: JSON.stringify(openapiDoc, null, "\t") }; // generate middleware file + const middlewareWrites = generateMiddlewares(description); + middlewareWrites.forEach(w => { w.location = join(options.typeScriptOutputFolder, w.location) }); + // TODO create json schema file for the storage return [ ...modelWrites, ...dataAccessWrites, + ...middlewareWrites, openapiWrites, ]; } diff --git a/src/DataAccessGenerator.ts b/src/DataAccessGenerator.ts index 43a7e29..4fc8142 100644 --- a/src/DataAccessGenerator.ts +++ b/src/DataAccessGenerator.ts @@ -1,10 +1,15 @@ import { Collection, Entity, Property } from "./MySchema"; import { FileWrite } from "./app"; +import { modelModuleName } from "./ModelGenerator"; export function generateDataAccess(collections: Collection[]): FileWrite[] { return collections.map(generateDataAccessFile); } +export function dataAccessModuleName(collection: Collection): string { + return `${collection.name}DataAccess`; +} + function generateDataAccessFile(collection: Collection): FileWrite { const definitions: string[] = [ defineImports(collection), @@ -16,14 +21,14 @@ ]; return { - location: collection.name + "DataAccess.ts", + location: dataAccessModuleName(collection) + ".ts", content: definitions.join("\n\n") + "\n", }; } function defineImports(collection: Collection): string { const entity = collection.entities; - return `import { ${collection.name}, ${entity.name} } from "./${collection.name}Model"; + return `import { ${collection.name}, ${entity.name} } from "./${modelModuleName(collection)}"; import * as fs from "fs";`; } diff --git a/src/ExampleOutput/Middleware.ts b/src/ExampleOutput/Middleware.ts index e61fd9d..2a50fc3 100644 --- a/src/ExampleOutput/Middleware.ts +++ b/src/ExampleOutput/Middleware.ts @@ -1,4 +1,4 @@ -import express, { Request, Response, RequestHandler } from "express"; +import express from "express"; import * as DataAccess from "./DataAccess"; import { findSingle, toPayload } from "./Model"; @@ -9,7 +9,7 @@ // TODO get/post collection, get/post/delete single entity route.get("/nodes/:id(\\d+)", (req, res) => { - const id = Number.parseInt(req.param("id"), undefined); + const id = Number.parseInt(req.params.id, undefined); const node = findSingle(graph, id); if (node == null) { res.status(404).send("not found"); diff --git a/src/MiddlewareGenerator.ts b/src/MiddlewareGenerator.ts new file mode 100644 index 0000000..6731183 --- /dev/null +++ b/src/MiddlewareGenerator.ts @@ -0,0 +1,58 @@ +import { Description, FileWrite } from "./app"; +import { dataAccessModuleName } from "./DataAccessGenerator"; +import { modelModuleName } from "./ModelGenerator"; +import { Collection } from "./MySchema"; +import { collectionRoute } from "./OpenApiGenerator"; + +export function middlewareModuleName(collection: Collection): string { + return `${collection.name}Middleware`; +} + +export function generateMiddlewares(description: Description): FileWrite[] { + return description.collections.map(generateMiddleware); +} + +function generateMiddleware(collection: Collection): FileWrite { + const definitions = [ + defineImports(collection), + defineCreateMiddlewareFunction(collection), + ]; + return { + location: middlewareModuleName(collection) + ".ts", + content: definitions.join("\n\n") + "\n", + }; +} + +function defineImports(collection: Collection): string { + return `import express from "express"; +import * as DataAccess from "./${dataAccessModuleName(collection)}"; +import { findSingle, toPayload } from "./${modelModuleName(collection)}";`; +} + +function defineCreateMiddlewareFunction(collection: Collection): string { + const col = collection.name.toLowerCase(); + const route = collectionRoute(collection); + return `export function ${col}Middleware({ fileName }: { fileName: string }) { + const ${col} = DataAccess.load(fileName); + + const route = express.Router(); + + // TODO get/post collection, get/post/delete single entity + route.get("${route}/:id(\\d+)", (req, res) => { + const id = Number.parseInt(req.params.id, undefined); + const item = findSingle(${col}, id); + if (item == null) { + res.status(404).send("not found"); + return; + } + res.send(toPayload(item)); + }); + + route.get("${route}", (req, res) => { + const items = ${col}.items; + res.send(items.map(toPayload)); + }); + + return route; +}`; +} \ No newline at end of file diff --git a/src/ModelGenerator.ts b/src/ModelGenerator.ts index d471407..147ca0e 100644 --- a/src/ModelGenerator.ts +++ b/src/ModelGenerator.ts @@ -6,9 +6,13 @@ return collections.map(generateModelFile); } +export function modelModuleName(collection: Collection): string { + return `${collection.name}Model`; +} + function generateModelFile(collection: Collection): FileWrite { - const fileName = collection.name + "Model.ts"; + const fileName = modelModuleName(collection) + ".ts"; const definitions: string[] = [ defineCollection(collection), defineEntity(collection.entities), diff --git a/src/OpenApiGenerator.ts b/src/OpenApiGenerator.ts index 573eb69..b59cb56 100644 --- a/src/OpenApiGenerator.ts +++ b/src/OpenApiGenerator.ts @@ -49,11 +49,19 @@ } function generatePaths(collections: Collection[]): OpenAPIV3.PathsObject { - return fromEntites(collections.map(generateCollectionPath)); + const paths = [ + ...collections.map(generateCollectionPath), + ...collections.map(generateEntityPath), + ]; + return fromEntites(paths); +} + +export function collectionRoute(collection: Collection): string { + return "/" + collection.name.toLowerCase(); } function generateCollectionPath(collection: Collection): [string, OpenAPIV3.PathItemObject] { - const route: string = "/" + collection.name.toLowerCase(); + const route = collectionRoute(collection); const path: OpenAPIV3.PathItemObject = { get: generateGetAllOperation(collection), }; @@ -81,6 +89,53 @@ return operation; } +function generateEntityPath(collection: Collection): [string, OpenAPIV3.PathItemObject] { + const route = itemRoute(collection); + const path: OpenAPIV3.PathItemObject = { + get: generateGetSpecificOperation(collection.entities), + }; + return [route, path]; +} + +function itemRoute(collection: Collection): string { + return collectionRoute(collection) + "/{" + idParameterName(collection.entities) + "}"; +} + +function idParameterName(entity: Entity): string { + return entity.name.toLowerCase() + "Id"; +} + +function generateGetSpecificOperation(entity: Entity): OpenAPIV3.OperationObject { + const operation: OpenAPIV3.OperationObject = { + parameters: [ + { + in: "path", + name: idParameterName(entity), + required: true, + schema: { + type: "integer", + }, + }, + ], + responses: { + "200": { + description: "Returns one specific " + entity.name, + content: { + "application/json": { + schema: { + $ref: "#/components/schemas/" + entity.name, + } + } + } + }, + "404": { + $ref: "#/components/responses/NotFound", + } + } + }; + return operation; +} + const NotFoundResponse: OpenAPIV3.ResponseObject = { description: "The specified resource does not exist.", content: { diff --git a/src/app.ts b/src/app.ts index 1d57731..4abe7c3 100644 --- a/src/app.ts +++ b/src/app.ts @@ -4,6 +4,7 @@ import { generateModel } from "./ModelGenerator"; import { generateDataAccess } from "./DataAccessGenerator"; import { generateOpenAPI } from "./OpenApiGenerator"; +import { generateMiddlewares } from "./MiddlewareGenerator"; interface Options { typeScriptOutputFolder: string; @@ -42,22 +43,26 @@ function generate(options: Options, description: Description): FileWrite[] { // generate model files const modelWrites = generateModel(description.collections); - modelWrites.forEach(w => { w.location = join(options.typeScriptOutputFolder, w.location) }) + modelWrites.forEach(w => { w.location = join(options.typeScriptOutputFolder, w.location) }); // generate data access files const dataAccessWrites = generateDataAccess(description.collections); - dataAccessWrites.forEach(w => { w.location = join(options.typeScriptOutputFolder, w.location) }) + dataAccessWrites.forEach(w => { w.location = join(options.typeScriptOutputFolder, w.location) }); // generate openapi file const openapiDoc = generateOpenAPI(description); const openapiWrites: FileWrite = { location: options.openapiOutput, content: JSON.stringify(openapiDoc, null, "\t") }; // generate middleware file + const middlewareWrites = generateMiddlewares(description); + middlewareWrites.forEach(w => { w.location = join(options.typeScriptOutputFolder, w.location) }); + // TODO create json schema file for the storage return [ ...modelWrites, ...dataAccessWrites, + ...middlewareWrites, openapiWrites, ]; } diff --git a/src/server.ts b/src/server.ts index 3813ce8..367d5a4 100644 --- a/src/server.ts +++ b/src/server.ts @@ -1,6 +1,10 @@ import express from "express"; import { graphMiddleware } from "./ExampleOutput/Middleware"; import {join} from "path"; +import { charactersMiddleware } from "./TestOutput/CharactersMiddleware"; +import * as swaggerUi from "swagger-ui-express"; +import * as swaggerDocument from "./ExampleOutput/openapi.json"; +import * as swaggerDocumentTest from "./TestOutput/openapi.json"; const port = 8080; const webRoot = "/"; @@ -11,10 +15,8 @@ app.disable("x-powered-by"); app.use(graphMiddleware({fileName: join(__dirname, "..", "src", "ExampleOutput", "graph.json")})) +app.use(charactersMiddleware({fileName: join(__dirname, "..", "src", "TestOutput", "characters.json")})) -import * as swaggerUi from "swagger-ui-express"; -import * as swaggerDocument from "./ExampleOutput/openapi.json"; -import * as swaggerDocumentTest from "./TestOutput/openapi.json"; app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerDocument)); app.use('/api-docs-test', swaggerUi.serve, swaggerUi.setup(swaggerDocumentTest));