diff --git a/packages/server/package.json b/packages/server/package.json index 4656c6d..9dfcd3d 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -23,6 +23,7 @@ "dependencies": { "@fastify/cors": "^8.4.0", "fastify": "^4.23.2", + "fastify-type-provider-zod": "^1.1.9", "mongodb": "^4.17.1", "nanoid": "^4.0.2", "ts-byob": "^1.0.3", diff --git a/packages/server/src/routes.ts b/packages/server/src/routes.ts index 015c668..0580a62 100644 --- a/packages/server/src/routes.ts +++ b/packages/server/src/routes.ts @@ -1,6 +1,7 @@ import {LineItem, Order, Product, ProductTemplate} from "./types"; -import {FastifyInstance, FastifyRequest} from "fastify"; - +import {FastifyInstance} from "fastify"; +import {serializerCompiler, validatorCompiler, ZodTypeProvider} from "fastify-type-provider-zod"; +import {z} from "zod"; type Cart = { id: string; @@ -9,76 +10,132 @@ type Cart = { export interface ProductRepository { findById(productId: Product["id"]): Promise; + create(template: Omit): Promise; + findAll(): Promise; } export interface OrderRepository { create(order: Omit): Promise; + findById(orderId: string): Promise; } -type CartId = {Params: {cartId: string}}; -type OrderId = {Params: {orderId: string}}; +const CartIdSchema = z.object({ + cartId: z.string() +}); export const createRoutes = (productRepo: ProductRepository, orderRepo: OrderRepository) => (fastify: FastifyInstance, opts: any, done: () => void) => { + fastify.setValidatorCompiler(validatorCompiler); + fastify.setSerializerCompiler(serializerCompiler); + + const f = fastify.withTypeProvider(); + const sessions: Record = {}; - fastify.get("/cart/:cartId", (req: FastifyRequest, res) => { - const {cartId} = req.params; - res.send(sessions[cartId]); + f.route({ + url: "/cart/:cartId", + method: "GET", + schema: { + params: CartIdSchema, + }, + handler: (req, res) => { + const {cartId} = req.params; + res.send(sessions[cartId]); + } }); - fastify.get("/cart/:cartId/count", (req: FastifyRequest, res) => { - const {cartId} = req.params; - res.send(sessions[cartId]?.items.length || 0); + f.route({ + method: "GET", + url: "/cart/:cartId/count", + schema: { + params: CartIdSchema, + }, + handler: (req, res) => { + const {cartId} = req.params; + res.send(sessions[cartId]?.items.length || 0); + } }); - fastify.post("/cart/:cartId", async (req: FastifyRequest , res) => { - const {cartId} = req.params; - const {productId} = req.body; - sessions[cartId] = sessions[cartId] || {id: cartId, items: []}; - const product = await productRepo.findById(productId); - - if (product) { - sessions[cartId].items.push(({productId, name: product.title, price: product.price})) - res.status(201).send(); - } else { - res.status(404).send(); + f.route({ + url: "/cart/:cartId", + method: "POST", + schema: { + body: z.object({ + productId: z.string() + }), + params: CartIdSchema, + }, + handler: async (req, res) => { + const {cartId} = req.params; + const {productId} = req.body; + sessions[cartId] = sessions[cartId] || {id: cartId, items: []}; + const product = await productRepo.findById(productId); + + if (product) { + sessions[cartId].items.push(({productId, name: product.title, price: product.price})) + res.status(201).send(); + } else { + res.status(404).send(); + } } }); - fastify.post("/checkout/:cartId", async (req: FastifyRequest, res) => { - const {cartId} = req.params; - const cart = sessions[cartId]; - if (!cart) { - throw new Error(`no cart with id ${cartId} was found`); - } else { - - const order = await orderRepo.create({items: cart.items}); - res.status(201).send(order.id); + f.route({ + method: "POST", + url: "/checkout/:cartId", + schema: { + params: CartIdSchema, + }, + handler: async (req, res) => { + const {cartId} = req.params; + const cart = sessions[cartId]; + if (!cart) { + throw new Error(`no cart with id ${cartId} was found`); + } else { + + const order = await orderRepo.create({items: cart.items}); + res.status(201).send(order.id); + } } }); - fastify.get("/order/:orderId", async (req: FastifyRequest, res) => { - const {orderId} = req.params; - const order = await orderRepo.findById(orderId); - if (order) { - res.send(order); - } else { - res.status(404).send(`Order with id ${orderId} was not found`); + f.route({ + method: "GET", + url: "/order/:orderId", + schema: { + params: z.object({ + orderId: z.string(), + }) + }, + handler: async (req, res) => { + const {orderId} = req.params; + const order = await orderRepo.findById(orderId); + if (order) { + res.send(order); + } else { + res.status(404).send(`Order with id ${orderId} was not found`); + } } }); //TODO remove - fastify.post("/products", async (req, res) => { - const product = await productRepo.create(ProductTemplate.parse(req.body)); - res.status(201).send(product); + f.route({ + method: "POST", + url: "/products", + schema: { + body: ProductTemplate, + }, + handler: async (req, res) => { + const product = await productRepo.create(req.body); + res.status(201).send(product); + } }) - fastify.get("/products", async (_, res) => { + f.get("/products", async (_, res) => { res.send(await productRepo.findAll()); }) diff --git a/yarn.lock b/yarn.lock index f10c3c8..676da06 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2727,6 +2727,7 @@ __metadata: "@types/morgan": ^1.9.5 "@types/node": ^17.0.45 fastify: ^4.23.2 + fastify-type-provider-zod: ^1.1.9 jest: ^28.1.3 mongodb: ^4.17.1 nanoid: ^4.0.2 @@ -4997,6 +4998,18 @@ __metadata: languageName: node linkType: hard +"fastify-type-provider-zod@npm:^1.1.9": + version: 1.1.9 + resolution: "fastify-type-provider-zod@npm:1.1.9" + dependencies: + zod-to-json-schema: ^3.17.1 + peerDependencies: + fastify: ^4.0.0 + zod: ^3.14.2 + checksum: 86792031057cf3807a2e77ef41b16161d94c89ab95bd6284574502351df9c4d1e1621d4f7e7f3f14cd81aec9923c3a4f61c8889d5abb3b57e52ee9c0015056e1 + languageName: node + linkType: hard + "fastify@npm:^4.23.2": version: 4.23.2 resolution: "fastify@npm:4.23.2" @@ -9416,6 +9429,15 @@ __metadata: languageName: node linkType: hard +"zod-to-json-schema@npm:^3.17.1": + version: 3.21.4 + resolution: "zod-to-json-schema@npm:3.21.4" + peerDependencies: + zod: ^3.21.4 + checksum: 899c1f461fb6547c0b08a265c82040c250be9b88d3f408f2f3ff77a418fdfad7549077e589d418fccb312c1f6d555c3c7217b199cc9072762e1fab20716dd2a6 + languageName: node + linkType: hard + "zod@npm:^3.22.2": version: 3.22.2 resolution: "zod@npm:3.22.2"