Rebasely Beta: data may be purged

examples/storefront Public

TypeScript · 93 lines · 4.0 KiB · at 5c7d155

// The storefront's HTTP API. createApp takes the environment and the few things tests replace// (the clock and id generation) and returns a fetch handler.import { createCart, getCart, setQuantity } from "./carts.ts";import { getProduct, listProducts, parseProductInput, upsertProduct } from "./catalog.ts";import { checkout } from "./checkout.ts";import type { Env } from "./env.ts";import { errorResponse, HttpError, json, readJson, requireAdmin } from "./http.ts";import { imageUrls, putProductImage } from "./images.ts";import { getOrder } from "./orders.ts";import { stripePayments, type Payments } from "./payments.ts";import { Router } from "./router.ts";export type Deps = {  now: () => number;  newId: () => string;  payments: (env: Env) => Payments;};export const defaultDeps: Deps = {  now: Date.now,  newId: () => crypto.randomUUID(),  payments: (env) => stripePayments(env.STRIPE_SECRET_KEY),};export function createApp(env: Env, deps: Deps = defaultDeps) {  const r = new Router();  const images = imageUrls(env.IMAGES_URL);  r.on("GET", "/products", async () => json({ products: await listProducts(env.DB, images) }));  r.on("GET", "/products/:id", async (_req, p) => {    const product = await getProduct(env.DB, p.id!, images);    if (!product || !product.active) throw new HttpError(404, "not_found", "No such product.");    return json({ product });  });  r.on("PUT", "/admin/products/:id", async (req, p) => {    requireAdmin(req, env.ADMIN_TOKEN);    const input = parseProductInput(p.id!, await readJson(req));    return json({ product: await upsertProduct(env.DB, input, deps.now(), images) });  });  r.on("PUT", "/admin/products/:id/image", async (req, p) => {    requireAdmin(req, env.ADMIN_TOKEN);    await putProductImage(env.DB, env.IMAGES, p.id!, req, deps.now());    return json({ product: await getProduct(env.DB, p.id!, images) });  });  r.on("POST", "/carts", async () => json({ cart: await createCart(env.DB, deps.newId(), deps.now(), images) }, 201));  r.on("GET", "/carts/:id", async (_req, p) => {    const cart = await getCart(env.DB, p.id!, images);    if (!cart) throw new HttpError(404, "cart_not_found", "No such cart.");    return json({ cart });  });  r.on("PUT", "/carts/:id/items/:product", async (req, p) => {    const body = await readJson(req);    return json({ cart: await setQuantity(env.DB, p.id!, p.product!, body.quantity, deps.now(), images) });  });  r.on("DELETE", "/carts/:id/items/:product", async (_req, p) => json({ cart: await setQuantity(env.DB, p.id!, p.product!, 0, deps.now(), images) }));  r.on("POST", "/carts/:id/checkout", async (req, p) => {    const result = await checkout(env.DB, deps.payments(env), p.id!, await readJson(req), deps);    return json({ order: result.order, payment: { provider: "stripe", client_secret: result.client_secret } }, result.created ? 201 : 200);  });  // The order id is a random UUID that only the shopper who checked out has.  r.on("GET", "/orders/:id", async (_req, p) => {    const order = await getOrder(env.DB, p.id!);    if (!order) throw new HttpError(404, "order_not_found", "No such order.");    return json({ order });  });  return {    async fetch(req: Request): Promise<Response> {      const url = new URL(req.url);      try {        const hit = r.match(req.method, url.pathname);        if (hit === null) throw new HttpError(404, "not_found", "No such route.");        if (hit === "method_not_allowed") throw new HttpError(405, "method_not_allowed", `${req.method} is not allowed here.`);        return await hit.handler(req, hit.params);      } catch (e) {        if (e instanceof HttpError) return errorResponse(e);        if (e instanceof URIError) return errorResponse(new HttpError(400, "bad_path", "The path is not valid."));        console.error(JSON.stringify({ msg: "unhandled error", path: url.pathname, error: String(e) }));        return errorResponse(new HttpError(500, "internal", "Something went wrong."));      }    },  };}