import type { LoaderFunctionArgs } from "@remix-run/node"; import type { Method } from "@prisma/client"; import { authenticate } from "../shopify.server"; import { resolveAvailabilityRequest } from "../services/availability-request.server"; import { resolveProductRefs } from "../services/product-rules.server"; import { parseCartLinesParam, parseProductIdsParam } from "../lib/cart-rule-params"; // Public endpoint, reachable only through Shopify's App Proxy (signature // verified by authenticate.public.appProxy) โ€” this is what the storefront // Theme App Extension calls. Requests to https://{shop}/apps/scheduling/* // forward here because shopify.app.toml's [app_proxy].url already includes // the /apps/scheduling prefix, so this file's path (apps.scheduling.*) // mirrors the shop-facing URL exactly. The actual resolution logic lives in // services/availability-request.server.ts, shared with the POS route // (pos.scheduling.availability.tsx) โ€” same pool, same code, different auth. const VALID_METHODS = new Set(["SHIPPING", "LOCAL_DELIVERY", "PICKUP"]); export const loader = async ({ request }: LoaderFunctionArgs) => { const { session, admin } = await authenticate.public.appProxy(request); if (!session) { return Response.json({ error: "Shop not found" }, { status: 404 }); } const url = new URL(request.url); const methodParam = url.searchParams.get("method"); if (!methodParam || !VALID_METHODS.has(methodParam as Method)) { return Response.json({ error: "Invalid or missing method" }, { status: 400 }); } // ProductRule scoping (PRODUCT_STRATEGY.md ยง2): vendor/type come free from // the widget's own /cart.js read; product/collection/tag rules additionally // need one Admin API round trip to resolve productIds โ€” skipped gracefully // if there's no admin client (shouldn't happen for an installed shop's app // proxy request, but this endpoint must not 500 over an optional feature). const productIds = parseProductIdsParam(url.searchParams.get("productIds")); const productRefs = admin && productIds.length > 0 ? await resolveProductRefs(admin, productIds) : []; const result = await resolveAvailabilityRequest(session.shop, { method: methodParam as Method, locationId: url.searchParams.get("locationId") || undefined, postalCode: url.searchParams.get("postalCode") || undefined, address: url.searchParams.get("address") || undefined, days: Number(url.searchParams.get("days")) || undefined, cartLines: parseCartLinesParam(url.searchParams.get("cartLines")), productRefs, }); if (!result.locationId) { return Response.json(result, { status: result.error === "No active location configured" ? 404 : 200 }); } return Response.json(result); };