diff --git a/docs/developer-portal/05-menu-integration-audit.md b/docs/developer-portal/05-menu-integration-audit.md index c51489e..10cea77 100644 --- a/docs/developer-portal/05-menu-integration-audit.md +++ b/docs/developer-portal/05-menu-integration-audit.md @@ -17,7 +17,9 @@ Source checked: Uber Eats "Menu Integration" section shared by you. - validates known menu_type enum values - Individual item updates: - `POST /api/v1/uber/menu/items` - - supports stock/price style item-level updates via Menu Items endpoint + - aligned to upstream `POST /v2/eats/stores/{store_id}/menus/items/{item_id}` + - request body is sparse update (only provided fields are changed) + - supports `menu_type` enum for split delivery/pickup/dine-in menus ## Existing Before diff --git a/docs/developer-portal/05-menus.md b/docs/developer-portal/05-menus.md index d23d041..58bb347 100644 --- a/docs/developer-portal/05-menus.md +++ b/docs/developer-portal/05-menus.md @@ -21,6 +21,13 @@ Current wrapper route for full replacement: Item update route: - `POST /api/v1/uber/menu/items` +- upstream mapped to `POST /v2/eats/stores/{store_id}/menus/items/{item_id}` +- sparse update only (only provided fields are changed) +- wrapper payload shape: + - `merchantId` + - `storeId` + - `itemId` + - `update` object with fields such as `price_info`, `suspension_info`, `menu_type`, `product_info`, `classifications`, `beverage_info`, `physical_properties_info`, `medication_info`, `nutritional_info`, `selling_info` Menu fetch route: diff --git a/docs/openapi/openapi.json b/docs/openapi/openapi.json index e4576fc..5240e69 100644 --- a/docs/openapi/openapi.json +++ b/docs/openapi/openapi.json @@ -318,13 +318,13 @@ }, "/api/v1/uber/menu/items": { "post": { - "summary": "Update individual menu items (stock/price updates)", + "summary": "Update single menu item (POST /v2/eats/stores/{store_id}/menus/items/{item_id})", "tags": [ "Uber Menu" ], "responses": { "200": { - "description": "Menu items updated" + "description": "Menu item sparsely updated (Uber returns 204 No Content)" } } } diff --git a/postman/Uber_Wrapper.postman_collection.json b/postman/Uber_Wrapper.postman_collection.json index d4352ca..d715d1b 100644 --- a/postman/Uber_Wrapper.postman_collection.json +++ b/postman/Uber_Wrapper.postman_collection.json @@ -200,7 +200,7 @@ } }, { - "name": "Update Menu Items", + "name": "Update Item (v2 Sparse)", "request": { "method": "POST", "header": [ @@ -215,7 +215,7 @@ ], "body": { "mode": "raw", - "raw": "{\n \"merchantId\": \"{{merchantId}}\",\n \"storeId\": \"{{storeId}}\",\n \"items\": [\n {\n \"id\": \"item_1\",\n \"price_info\": {\n \"price\": 799\n },\n \"suspension_info\": {\n \"suspend_until\": null\n }\n }\n ]\n}" + "raw": "{\n \"merchantId\": \"{{merchantId}}\",\n \"storeId\": \"{{storeId}}\",\n \"itemId\": \"item_1\",\n \"update\": {\n \"menu_type\": \"MENU_TYPE_FULFILLMENT_DELIVERY\",\n \"price_info\": {\n \"price\": 799\n },\n \"suspension_info\": {\n \"suspension\": null\n }\n }\n}" }, "url": { "raw": "{{baseUrl}}/api/v1/uber/menu/items", diff --git a/src/config/uberEndpoints.js b/src/config/uberEndpoints.js index 648516d..e48b97e 100644 --- a/src/config/uberEndpoints.js +++ b/src/config/uberEndpoints.js @@ -3,7 +3,7 @@ module.exports = { upsert: "/v1/eats/stores/{storeId}/menus", upload: "/v2/eats/stores/{storeId}/menus", get: "/v2/eats/stores/{storeId}/menus", - itemsUpdate: "/v1/eats/stores/{storeId}/menus/items" + itemUpdate: "/v2/eats/stores/{storeId}/menus/items/{itemId}" }, orders: { list: "/v1/eats/stores/{storeId}/orders", diff --git a/src/modules/proxy/proxy.controller.js b/src/modules/proxy/proxy.controller.js index 603aa72..14bd381 100644 --- a/src/modules/proxy/proxy.controller.js +++ b/src/modules/proxy/proxy.controller.js @@ -90,16 +90,41 @@ async function replaceMenu(req, res) { } async function updateMenuItems(req, res) { + const menuTypeEnum = z.enum([ + "MENU_TYPE_FULFILLMENT_DELIVERY", + "MENU_TYPE_FULFILLMENT_PICK_UP", + "MENU_TYPE_FULFILLMENT_DINE_IN" + ]); + + const updateSchema = z + .object({ + price_info: z.any().optional(), + suspension_info: z.any().optional(), + menu_type: menuTypeEnum.optional(), + product_info: z.any().optional(), + classifications: z.any().optional(), + beverage_info: z.any().optional(), + physical_properties_info: z.any().optional(), + medication_info: z.any().optional(), + nutritional_info: z.any().optional(), + selling_info: z.any().optional() + }) + .refine((value) => Object.keys(value).length > 0, { + message: "update must include at least one sparse-update field" + }); + const schema = z.object({ merchantId: z.string().min(1), storeId: z.string().min(1), - items: z.array(z.any()).min(1) + itemId: z.string().min(1), + update: updateSchema }); const payload = schema.parse(req.body); const data = await proxyService.updateMenuItems({ merchantId: payload.merchantId, storeId: payload.storeId, - payload: { items: payload.items } + itemId: payload.itemId, + payload: payload.update }); return res.json({ success: true, data }); } diff --git a/src/modules/proxy/proxy.service.js b/src/modules/proxy/proxy.service.js index 193d5f3..89af300 100644 --- a/src/modules/proxy/proxy.service.js +++ b/src/modules/proxy/proxy.service.js @@ -188,8 +188,8 @@ async function menuGet({ merchantId, storeId, menuType }) { }); } -async function updateMenuItems({ merchantId, storeId, payload }) { - const uberPath = interpolatePath(uberEndpoints.menu.itemsUpdate, { storeId }); +async function updateMenuItems({ merchantId, storeId, itemId, payload }) { + const uberPath = interpolatePath(uberEndpoints.menu.itemUpdate, { storeId, itemId }); return callUberApi({ merchantId, method: "POST", diff --git a/src/routes/proxy.routes.js b/src/routes/proxy.routes.js index 4df7082..8cbcdc2 100644 --- a/src/routes/proxy.routes.js +++ b/src/routes/proxy.routes.js @@ -47,12 +47,12 @@ router.put("/uber/menu/replace", asyncHandler(controller.replaceMenu)); * @openapi * /api/v1/uber/menu/items: * post: - * summary: Update individual menu items (stock/price updates) + * summary: Update single menu item (POST /v2/eats/stores/{store_id}/menus/items/{item_id}) * tags: * - Uber Menu * responses: * 200: - * description: Menu items updated + * description: Menu item sparsely updated (Uber returns 204 No Content) */ router.post("/uber/menu/items", asyncHandler(controller.updateMenuItems));