import { FileWrite } from "./server-generator"; import { dataAccessModuleName } from "./DataAccessGenerator"; import { modelModuleName } from "./ModelGenerator"; import { Collection, Description, Entity, Property } from "./types"; 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), defineInputParsers(collection.entities), ]; 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 { append, findSingle, ${collection.entities.name}Input, remove, toPayload } from "./${modelModuleName(collection)}"; import { andThen, compose, parseNotNull, ParseObject, parseObject, parseRequiredMember, parseString, resolve, Result } from "./util";`; } function defineCreateMiddlewareFunction(collection: Collection): string { const col = collection.name.toLowerCase(); const route = collectionRoute(collection); const entityId = collection.entities.name.toLowerCase() + "Id"; return `export function ${col}Middleware({ fileName }: { fileName: string }) { const ${col} = DataAccess.load(fileName); const route = express.Router(); // TODO post/delete single entity route.get("${route}/:${entityId}(\\\\d+)", (req, res) => { const id = Number.parseInt(req.params.${entityId}, undefined); const item = findSingle(${col}, id); if (item == null) { res.status(404).send("not found"); return; } res.send(toPayload(item)); }); route.post("${route}/:${entityId}(\\\\d+)", (req, res) => { const id = Number.parseInt(req.params.${entityId}, undefined); const existingItem = findSingle(${col}, id); const parsed = parse${collection.entities.name}Input(req.body); if(!parsed.isSuccessful) { res.status(400).send(parsed.errorMessage); return; } const inputItem = parsed.value; if(existingItem == null) { res.status(404).send("Not found"); return; } else { // update ${collection.entities.properties.map(p => `existingItem.${p.key} = inputItem.${p.key};`).join("\n\t\t\t")} DataAccess.save(fileName, ${col}); const payload = toPayload(existingItem); res.status(200).send(payload); return; } }); route.delete("${route}/:${entityId}(\\\\d+)", (req, res) => { const id = Number.parseInt(req.params.${entityId}, undefined); remove(${col}, id); DataAccess.save(fileName, ${col}); res.status(204).send(); }); route.get("${route}", (req, res) => { const items = ${col}.items; res.send(items.map(toPayload)); }); route.post("${route}", (req, res) => { try { const parsed = parse${collection.entities.name}Input(req.body); if(!parsed.isSuccessful) { res.status(400).send(parsed.errorMessage); return; } const item = parsed.value; const added = append(${col}, item); DataAccess.save(fileName, ${col}); const payload = toPayload(added); res.status(201).send(payload); } catch (error) { console.error(error); res.status(500).send("Internal Server Error."); return; } }); return route; }`; } function defineInputParsers(entity: Entity): string { const propParsers = entity.properties.map(p => definePropertyParser(entity, p)).join("\n"); return `${propParsers} function parse${entity.name}Input(data: unknown): Result<${entity.name}Input> { const obj = andThen(parseObject)(parseNotNull(data)); const members: ParseObject<${entity.name}Input> = { ${entity.properties.map(p => `${p.key}: parse${p.key}(obj),`).join("\n\t\t")} }; return resolve(members); }`; } function definePropertyParser(entity: Entity, property: Property): string { if (property.type === "string") { return defineStringPropertyParser(entity.name, property.key, property.isNullable); } else { throw new Error("Properties other than strings are not implemented yet."); } } function defineStringPropertyParser(typeName: string, propName: string, isNullable: boolean): string { const parser = isNullable ? "parseNullableMember" : "parseRequiredMember"; return `const parse${propName} = andThen(compose(${parser}<${typeName}Input>("${propName}"), parseString));`; }