From 5ea2d86a48eac5185c94fc06dd8a6e0797837ac2 Mon Sep 17 00:00:00 2001 From: MOHAN Date: Sun, 29 Mar 2026 18:34:56 +0530 Subject: [PATCH] Align Get Menu with v2 endpoint, menu_type, and gzip --- .../05-menu-integration-audit.md | 4 +- docs/developer-portal/05-menus.md | 10 +++++ docs/openapi/openapi.json | 32 ++++++++++++++++ postman/Uber_Wrapper.postman_collection.json | 38 +++++++++++++++++++ src/config/uberEndpoints.js | 2 +- src/modules/proxy/proxy.controller.js | 22 ++++++++++- src/modules/proxy/proxy.service.js | 23 +++++++++-- src/routes/proxy.routes.js | 21 ++++++++++ 8 files changed, 145 insertions(+), 7 deletions(-) diff --git a/docs/developer-portal/05-menu-integration-audit.md b/docs/developer-portal/05-menu-integration-audit.md index 771e712..78203fe 100644 --- a/docs/developer-portal/05-menu-integration-audit.md +++ b/docs/developer-portal/05-menu-integration-audit.md @@ -6,6 +6,9 @@ Source checked: Uber Eats "Menu Integration" section shared by you. - Retrieve menu: - `GET /api/v1/uber/menu` + - aligned to upstream `GET /v2/eats/stores/{store_id}/menus` + - supports `menu_type` query values for delivery/pick-up/dine-in + - applies `Accept-Encoding: gzip` for large payload responses - Full menu upload/replace: - `PUT /api/v1/uber/menu/replace` (primary) - Individual item updates: @@ -22,4 +25,3 @@ Source checked: Uber Eats "Menu Integration" section shared by you. - Strict typed schemas for full menu payload entities (item, modifier group, category, menu) - Validation rules for image metadata limits and alcoholic item classifications - Dedicated mapper helpers for `core_price` and `bundled_items` enrichment - diff --git a/docs/developer-portal/05-menus.md b/docs/developer-portal/05-menus.md index 4f3c4ab..5f2e38b 100644 --- a/docs/developer-portal/05-menus.md +++ b/docs/developer-portal/05-menus.md @@ -16,6 +16,16 @@ Item update route: - `POST /api/v1/uber/menu/items` +Menu fetch route: + +- `GET /api/v1/uber/menu` +- upstream mapped to `GET /v2/eats/stores/{store_id}/menus` +- supports query `menu_type`: + - `MENU_TYPE_FULFILLMENT_DELIVERY` (default) + - `MENU_TYPE_FULFILLMENT_PICK_UP` + - `MENU_TYPE_FULFILLMENT_DINE_IN` +- sends `Accept-Encoding: gzip` upstream for large menu payloads + Best-practice note: - Use API-managed menus only for integrated stores (avoid manual Menu Maker edits to prevent drift). diff --git a/docs/openapi/openapi.json b/docs/openapi/openapi.json index 6410153..7c685cf 100644 --- a/docs/openapi/openapi.json +++ b/docs/openapi/openapi.json @@ -335,6 +335,38 @@ "tags": [ "Uber Menu" ], + "parameters": [ + { + "in": "query", + "name": "merchantId", + "required": true, + "schema": { + "type": "string" + } + }, + { + "in": "query", + "name": "storeId", + "required": true, + "schema": { + "type": "string" + } + }, + { + "in": "query", + "name": "menu_type", + "required": false, + "schema": { + "type": "string", + "enum": [ + "MENU_TYPE_FULFILLMENT_DELIVERY", + "MENU_TYPE_FULFILLMENT_PICK_UP", + "MENU_TYPE_FULFILLMENT_DINE_IN" + ] + }, + "description": "Defaults to MENU_TYPE_FULFILLMENT_DELIVERY." + } + ], "responses": { "200": { "description": "Menu fetched" diff --git a/postman/Uber_Wrapper.postman_collection.json b/postman/Uber_Wrapper.postman_collection.json index a49c242..7bd873b 100644 --- a/postman/Uber_Wrapper.postman_collection.json +++ b/postman/Uber_Wrapper.postman_collection.json @@ -232,6 +232,44 @@ } } }, + { + "name": "Get Menu (v2)", + "request": { + "method": "GET", + "header": [ + { + "key": "x-api-key", + "value": "{{apiKey}}" + } + ], + "url": { + "raw": "{{baseUrl}}/api/v1/uber/menu?merchantId={{merchantId}}&storeId={{storeId}}&menu_type=MENU_TYPE_FULFILLMENT_DELIVERY", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "api", + "v1", + "uber", + "menu" + ], + "query": [ + { + "key": "merchantId", + "value": "{{merchantId}}" + }, + { + "key": "storeId", + "value": "{{storeId}}" + }, + { + "key": "menu_type", + "value": "MENU_TYPE_FULFILLMENT_DELIVERY" + } + ] + } + } + }, { "name": "List Provisionable Stores", "request": { diff --git a/src/config/uberEndpoints.js b/src/config/uberEndpoints.js index a090dc9..f704827 100644 --- a/src/config/uberEndpoints.js +++ b/src/config/uberEndpoints.js @@ -1,7 +1,7 @@ module.exports = { menu: { upsert: "/v1/eats/stores/{storeId}/menus", - get: "/v1/eats/stores/{storeId}/menus", + get: "/v2/eats/stores/{storeId}/menus", itemsUpdate: "/v1/eats/stores/{storeId}/menus/items" }, orders: { diff --git a/src/modules/proxy/proxy.controller.js b/src/modules/proxy/proxy.controller.js index 4773ab3..c612403 100644 --- a/src/modules/proxy/proxy.controller.js +++ b/src/modules/proxy/proxy.controller.js @@ -35,10 +35,28 @@ async function upsertMenu(req, res) { async function getMenu(req, res) { const schema = z.object({ merchantId: z.string().min(1), - storeId: z.string().min(1) + storeId: z.string().min(1), + menu_type: z + .enum([ + "MENU_TYPE_FULFILLMENT_DELIVERY", + "MENU_TYPE_FULFILLMENT_PICK_UP", + "MENU_TYPE_FULFILLMENT_DINE_IN" + ]) + .optional(), + menuType: z + .enum([ + "MENU_TYPE_FULFILLMENT_DELIVERY", + "MENU_TYPE_FULFILLMENT_PICK_UP", + "MENU_TYPE_FULFILLMENT_DINE_IN" + ]) + .optional() }); const payload = schema.parse(req.query); - const data = await proxyService.menuGet(payload); + const data = await proxyService.menuGet({ + merchantId: payload.merchantId, + storeId: payload.storeId, + menuType: payload.menu_type || payload.menuType + }); return res.json({ success: true, data }); } diff --git a/src/modules/proxy/proxy.service.js b/src/modules/proxy/proxy.service.js index 17e11de..2a881f5 100644 --- a/src/modules/proxy/proxy.service.js +++ b/src/modules/proxy/proxy.service.js @@ -55,7 +55,17 @@ async function resolveAuthToken({ authMode = "app", merchantId, scopes }) { }; } -async function callUberApi({ merchantId, method, uberPath, query, body, wrapperRoute, authMode, scopes }) { +async function callUberApi({ + merchantId, + method, + uberPath, + query, + body, + wrapperRoute, + authMode, + scopes, + headers +}) { const resolvedAuth = await resolveAuthToken({ authMode, merchantId, scopes }); try { @@ -68,7 +78,8 @@ async function callUberApi({ merchantId, method, uberPath, query, body, wrapperR data: body, headers: { Authorization: buildAuthHeader(resolvedAuth.tokenType, resolvedAuth.accessToken), - "Content-Type": "application/json" + "Content-Type": "application/json", + ...(headers || {}) } }), maxAttempts: 4, @@ -148,12 +159,18 @@ async function menuReplace({ merchantId, storeId, payload }) { }); } -async function menuGet({ merchantId, storeId }) { +async function menuGet({ merchantId, storeId, menuType }) { const uberPath = interpolatePath(uberEndpoints.menu.get, { storeId }); return callUberApi({ merchantId, method: "GET", uberPath, + query: { + menu_type: menuType || "MENU_TYPE_FULFILLMENT_DELIVERY" + }, + headers: { + "Accept-Encoding": "gzip" + }, wrapperRoute: "/api/v1/uber/menu", authMode: "app", scopes: AUTH_SCOPES.STORE diff --git a/src/routes/proxy.routes.js b/src/routes/proxy.routes.js index 3ec2c71..174ccd2 100644 --- a/src/routes/proxy.routes.js +++ b/src/routes/proxy.routes.js @@ -63,6 +63,27 @@ router.post("/uber/menu/items", asyncHandler(controller.updateMenuItems)); * summary: Fetch store menu * tags: * - Uber Menu + * parameters: + * - in: query + * name: merchantId + * required: true + * schema: + * type: string + * - in: query + * name: storeId + * required: true + * schema: + * type: string + * - in: query + * name: menu_type + * required: false + * schema: + * type: string + * enum: + * - MENU_TYPE_FULFILLMENT_DELIVERY + * - MENU_TYPE_FULFILLMENT_PICK_UP + * - MENU_TYPE_FULFILLMENT_DINE_IN + * description: Defaults to MENU_TYPE_FULFILLMENT_DELIVERY. * responses: * 200: * description: Menu fetched