feat: implement menu items update endpoint and align menu integration routes

This commit is contained in:
MOHAN 2026-03-29 17:56:07 +05:30
parent 1474781c08
commit 8fb333918c
8 changed files with 127 additions and 3 deletions

View File

@ -0,0 +1,25 @@
# 05 Menu Integration Audit
Source checked: Uber Eats "Menu Integration" section shared by you.
## Implemented Now
- Retrieve menu:
- `GET /api/v1/uber/menu`
- Full menu upload/replace:
- `PUT /api/v1/uber/menu/replace` (primary)
- Individual item updates:
- `POST /api/v1/uber/menu/items`
- supports stock/price style item-level updates via Menu Items endpoint
## Existing Before
- Legacy menu upsert helper route (`POST /api/v1/uber/menu/upsert`)
- Store/menu module structure and app-token auth for menu calls
## Pending
- 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

View File

@ -4,9 +4,18 @@ Menu sync between POS and Uber Eats:
- Full menu replacement via PUT
- Fetch menu from Uber
- Item-level updates (out-of-stock/price updates)
- Item and modifier mapping strategy
- Validation and publish error handling
Current wrapper route for full replacement:
- `PUT /api/v1/uber/menu/replace`
Item update route:
- `POST /api/v1/uber/menu/items`
Best-practice note:
- Use API-managed menus only for integrated stores (avoid manual Menu Maker edits to prevent drift).

View File

@ -230,7 +230,7 @@
},
"/api/v1/uber/menu/upsert": {
"post": {
"summary": "Upsert store menu",
"summary": "Legacy upsert helper for store menu",
"tags": [
"Uber Menu"
],
@ -254,6 +254,19 @@
}
}
},
"/api/v1/uber/menu/items": {
"post": {
"summary": "Update individual menu items (stock/price updates)",
"tags": [
"Uber Menu"
],
"responses": {
"200": {
"description": "Menu items updated"
}
}
}
},
"/api/v1/uber/menu": {
"get": {
"summary": "Fetch store menu",

View File

@ -199,6 +199,39 @@
}
}
},
{
"name": "Update Menu Items",
"request": {
"method": "POST",
"header": [
{
"key": "x-api-key",
"value": "{{apiKey}}"
},
{
"key": "Content-Type",
"value": "application/json"
}
],
"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}"
},
"url": {
"raw": "{{baseUrl}}/api/v1/uber/menu/items",
"host": [
"{{baseUrl}}"
],
"path": [
"api",
"v1",
"uber",
"menu",
"items"
]
}
}
},
{
"name": "List Provisionable Stores",
"request": {

View File

@ -1,7 +1,8 @@
module.exports = {
menu: {
upsert: "/v1/eats/stores/{storeId}/menus",
get: "/v1/eats/stores/{storeId}/menus"
get: "/v1/eats/stores/{storeId}/menus",
itemsUpdate: "/v1/eats/stores/{storeId}/menus/items"
},
orders: {
list: "/v1/eats/stores/{storeId}/orders",

View File

@ -57,6 +57,21 @@ async function replaceMenu(req, res) {
return res.json({ success: true, data });
}
async function updateMenuItems(req, res) {
const schema = z.object({
merchantId: z.string().min(1),
storeId: z.string().min(1),
items: z.array(z.any()).min(1)
});
const payload = schema.parse(req.body);
const data = await proxyService.updateMenuItems({
merchantId: payload.merchantId,
storeId: payload.storeId,
payload: { items: payload.items }
});
return res.json({ success: true, data });
}
async function listOrders(req, res) {
const schema = z.object({
merchantId: z.string().min(1),
@ -208,6 +223,7 @@ module.exports = {
genericProxy,
upsertMenu,
replaceMenu,
updateMenuItems,
getMenu,
listOrders,
orderAction,

View File

@ -151,6 +151,19 @@ async function menuGet({ merchantId, storeId }) {
});
}
async function updateMenuItems({ merchantId, storeId, payload }) {
const uberPath = interpolatePath(uberEndpoints.menu.itemsUpdate, { storeId });
return callUberApi({
merchantId,
method: "POST",
uberPath,
body: payload,
wrapperRoute: "/api/v1/uber/menu/items",
authMode: "app",
scopes: AUTH_SCOPES.STORE
});
}
async function ordersList({ merchantId, storeId, query }) {
const uberPath = interpolatePath(uberEndpoints.orders.list, { storeId });
return callUberApi({
@ -326,6 +339,7 @@ module.exports = {
menuUpsert,
menuReplace,
menuGet,
updateMenuItems,
ordersList,
orderAction,
updateStoreHours,

View File

@ -21,7 +21,7 @@ router.post("/uber/request", asyncHandler(controller.genericProxy));
* @openapi
* /api/v1/uber/menu/upsert:
* post:
* summary: Upsert store menu
* summary: Legacy upsert helper for store menu
* tags:
* - Uber Menu
* responses:
@ -43,6 +43,19 @@ router.post("/uber/menu/upsert", asyncHandler(controller.upsertMenu));
*/
router.put("/uber/menu/replace", asyncHandler(controller.replaceMenu));
/**
* @openapi
* /api/v1/uber/menu/items:
* post:
* summary: Update individual menu items (stock/price updates)
* tags:
* - Uber Menu
* responses:
* 200:
* description: Menu items updated
*/
router.post("/uber/menu/items", asyncHandler(controller.updateMenuItems));
/**
* @openapi
* /api/v1/uber/menu: