Align Upload Menu to v2 with gzip request encoding
This commit is contained in:
parent
5ea2d86a48
commit
d80f9e9baf
@ -11,6 +11,10 @@ Source checked: Uber Eats "Menu Integration" section shared by you.
|
|||||||
- applies `Accept-Encoding: gzip` for large payload responses
|
- applies `Accept-Encoding: gzip` for large payload responses
|
||||||
- Full menu upload/replace:
|
- Full menu upload/replace:
|
||||||
- `PUT /api/v1/uber/menu/replace` (primary)
|
- `PUT /api/v1/uber/menu/replace` (primary)
|
||||||
|
- aligned to upstream `PUT /v2/eats/stores/{store_id}/menus`
|
||||||
|
- wrapper uploads gzip-compressed JSON (`Content-Encoding: gzip`)
|
||||||
|
- supports optional `menu.menu_type` (delivery/pick-up/dine-in)
|
||||||
|
- validates known menu_type enum values
|
||||||
- Individual item updates:
|
- Individual item updates:
|
||||||
- `POST /api/v1/uber/menu/items`
|
- `POST /api/v1/uber/menu/items`
|
||||||
- supports stock/price style item-level updates via Menu Items endpoint
|
- supports stock/price style item-level updates via Menu Items endpoint
|
||||||
|
|||||||
@ -11,6 +11,12 @@ Menu sync between POS and Uber Eats:
|
|||||||
Current wrapper route for full replacement:
|
Current wrapper route for full replacement:
|
||||||
|
|
||||||
- `PUT /api/v1/uber/menu/replace`
|
- `PUT /api/v1/uber/menu/replace`
|
||||||
|
- upstream mapped to `PUT /v2/eats/stores/{store_id}/menus`
|
||||||
|
- request body is gzip-compressed for upload (`Content-Encoding: gzip`, `Content-Type: application/json`)
|
||||||
|
- optional `menu.menu_type` supported:
|
||||||
|
- `MENU_TYPE_FULFILLMENT_DELIVERY` (default)
|
||||||
|
- `MENU_TYPE_FULFILLMENT_PICK_UP`
|
||||||
|
- `MENU_TYPE_FULFILLMENT_DINE_IN`
|
||||||
|
|
||||||
Item update route:
|
Item update route:
|
||||||
|
|
||||||
@ -29,3 +35,4 @@ Menu fetch route:
|
|||||||
Best-practice note:
|
Best-practice note:
|
||||||
|
|
||||||
- Use API-managed menus only for integrated stores (avoid manual Menu Maker edits to prevent drift).
|
- Use API-managed menus only for integrated stores (avoid manual Menu Maker edits to prevent drift).
|
||||||
|
- Alcoholic item flag is effectively sticky in Uber (`alcoholic_items > 0` cannot be reverted by API update).
|
||||||
|
|||||||
@ -305,13 +305,13 @@
|
|||||||
},
|
},
|
||||||
"/api/v1/uber/menu/replace": {
|
"/api/v1/uber/menu/replace": {
|
||||||
"put": {
|
"put": {
|
||||||
"summary": "Replace store menu (full upload)",
|
"summary": "Upload/replace store menu (PUT /v2/eats/stores/{store_id}/menus)",
|
||||||
"tags": [
|
"tags": [
|
||||||
"Uber Menu"
|
"Uber Menu"
|
||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"200": {
|
||||||
"description": "Menu replaced"
|
"description": "Menu replaced (Uber returns 204 No Content)"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -167,7 +167,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Replace Menu (PUT)",
|
"name": "Upload Menu (PUT v2)",
|
||||||
"request": {
|
"request": {
|
||||||
"method": "PUT",
|
"method": "PUT",
|
||||||
"header": [
|
"header": [
|
||||||
@ -182,7 +182,7 @@
|
|||||||
],
|
],
|
||||||
"body": {
|
"body": {
|
||||||
"mode": "raw",
|
"mode": "raw",
|
||||||
"raw": "{\n \"merchantId\": \"{{merchantId}}\",\n \"storeId\": \"{{storeId}}\",\n \"menu\": {\n \"categories\": []\n }\n}"
|
"raw": "{\n \"merchantId\": \"{{merchantId}}\",\n \"storeId\": \"{{storeId}}\",\n \"menu\": {\n \"menu_type\": \"MENU_TYPE_FULFILLMENT_DELIVERY\",\n \"menus\": [],\n \"categories\": [],\n \"items\": [],\n \"modifier_groups\": []\n }\n}"
|
||||||
},
|
},
|
||||||
"url": {
|
"url": {
|
||||||
"raw": "{{baseUrl}}/api/v1/uber/menu/replace",
|
"raw": "{{baseUrl}}/api/v1/uber/menu/replace",
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
menu: {
|
menu: {
|
||||||
upsert: "/v1/eats/stores/{storeId}/menus",
|
upsert: "/v1/eats/stores/{storeId}/menus",
|
||||||
|
upload: "/v2/eats/stores/{storeId}/menus",
|
||||||
get: "/v2/eats/stores/{storeId}/menus",
|
get: "/v2/eats/stores/{storeId}/menus",
|
||||||
itemsUpdate: "/v1/eats/stores/{storeId}/menus/items"
|
itemsUpdate: "/v1/eats/stores/{storeId}/menus/items"
|
||||||
},
|
},
|
||||||
|
|||||||
@ -67,6 +67,20 @@ async function replaceMenu(req, res) {
|
|||||||
menu: z.any()
|
menu: z.any()
|
||||||
});
|
});
|
||||||
const payload = schema.parse(req.body);
|
const payload = schema.parse(req.body);
|
||||||
|
const allowedMenuTypes = new Set([
|
||||||
|
"MENU_TYPE_FULFILLMENT_DELIVERY",
|
||||||
|
"MENU_TYPE_FULFILLMENT_PICK_UP",
|
||||||
|
"MENU_TYPE_FULFILLMENT_DINE_IN"
|
||||||
|
]);
|
||||||
|
const menuType = payload.menu && payload.menu.menu_type;
|
||||||
|
if (menuType && !allowedMenuTypes.has(menuType)) {
|
||||||
|
const error = new Error(
|
||||||
|
"menu.menu_type must be one of MENU_TYPE_FULFILLMENT_DELIVERY, MENU_TYPE_FULFILLMENT_PICK_UP, MENU_TYPE_FULFILLMENT_DINE_IN"
|
||||||
|
);
|
||||||
|
error.status = 400;
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
const data = await proxyService.menuReplace({
|
const data = await proxyService.menuReplace({
|
||||||
merchantId: payload.merchantId,
|
merchantId: payload.merchantId,
|
||||||
storeId: payload.storeId,
|
storeId: payload.storeId,
|
||||||
|
|||||||
@ -1,4 +1,6 @@
|
|||||||
const axios = require("axios");
|
const axios = require("axios");
|
||||||
|
const zlib = require("zlib");
|
||||||
|
const { promisify } = require("util");
|
||||||
const env = require("../../config/env");
|
const env = require("../../config/env");
|
||||||
const uberEndpoints = require("../../config/uberEndpoints");
|
const uberEndpoints = require("../../config/uberEndpoints");
|
||||||
const { uberConnectionRepository, apiLogRepository } = require("../../db/adapter");
|
const { uberConnectionRepository, apiLogRepository } = require("../../db/adapter");
|
||||||
@ -11,6 +13,8 @@ const uberApiClient = axios.create({
|
|||||||
timeout: 30000
|
timeout: 30000
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const gzipAsync = promisify(zlib.gzip);
|
||||||
|
|
||||||
function interpolatePath(pathTemplate, params = {}) {
|
function interpolatePath(pathTemplate, params = {}) {
|
||||||
let output = pathTemplate;
|
let output = pathTemplate;
|
||||||
Object.entries(params).forEach(([key, value]) => {
|
Object.entries(params).forEach(([key, value]) => {
|
||||||
@ -61,6 +65,7 @@ async function callUberApi({
|
|||||||
uberPath,
|
uberPath,
|
||||||
query,
|
query,
|
||||||
body,
|
body,
|
||||||
|
logRequestBody,
|
||||||
wrapperRoute,
|
wrapperRoute,
|
||||||
authMode,
|
authMode,
|
||||||
scopes,
|
scopes,
|
||||||
@ -93,7 +98,7 @@ async function callUberApi({
|
|||||||
wrapperRoute,
|
wrapperRoute,
|
||||||
uberPath,
|
uberPath,
|
||||||
responseStatus: response.status,
|
responseStatus: response.status,
|
||||||
requestBody: body,
|
requestBody: logRequestBody !== undefined ? logRequestBody : body,
|
||||||
responseBody: response.data
|
responseBody: response.data
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -107,7 +112,7 @@ async function callUberApi({
|
|||||||
wrapperRoute,
|
wrapperRoute,
|
||||||
uberPath,
|
uberPath,
|
||||||
responseStatus: normalized.status,
|
responseStatus: normalized.status,
|
||||||
requestBody: body,
|
requestBody: logRequestBody !== undefined ? logRequestBody : body,
|
||||||
responseBody: {
|
responseBody: {
|
||||||
code: normalized.code,
|
code: normalized.code,
|
||||||
message: normalized.message,
|
message: normalized.message,
|
||||||
@ -147,12 +152,18 @@ async function menuUpsert({ merchantId, storeId, payload }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function menuReplace({ merchantId, storeId, payload }) {
|
async function menuReplace({ merchantId, storeId, payload }) {
|
||||||
const uberPath = interpolatePath(uberEndpoints.menu.upsert, { storeId });
|
const uberPath = interpolatePath(uberEndpoints.menu.upload, { storeId });
|
||||||
|
const compressedPayload = await gzipAsync(Buffer.from(JSON.stringify(payload), "utf8"));
|
||||||
return callUberApi({
|
return callUberApi({
|
||||||
merchantId,
|
merchantId,
|
||||||
method: "PUT",
|
method: "PUT",
|
||||||
uberPath,
|
uberPath,
|
||||||
body: payload,
|
body: compressedPayload,
|
||||||
|
logRequestBody: payload,
|
||||||
|
headers: {
|
||||||
|
"Content-Encoding": "gzip",
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
},
|
||||||
wrapperRoute: "/api/v1/uber/menu/replace",
|
wrapperRoute: "/api/v1/uber/menu/replace",
|
||||||
authMode: "app",
|
authMode: "app",
|
||||||
scopes: AUTH_SCOPES.STORE
|
scopes: AUTH_SCOPES.STORE
|
||||||
|
|||||||
@ -34,12 +34,12 @@ router.post("/uber/menu/upsert", asyncHandler(controller.upsertMenu));
|
|||||||
* @openapi
|
* @openapi
|
||||||
* /api/v1/uber/menu/replace:
|
* /api/v1/uber/menu/replace:
|
||||||
* put:
|
* put:
|
||||||
* summary: Replace store menu (full upload)
|
* summary: Upload/replace store menu (PUT /v2/eats/stores/{store_id}/menus)
|
||||||
* tags:
|
* tags:
|
||||||
* - Uber Menu
|
* - Uber Menu
|
||||||
* responses:
|
* responses:
|
||||||
* 200:
|
* 200:
|
||||||
* description: Menu replaced
|
* description: Menu replaced (Uber returns 204 No Content)
|
||||||
*/
|
*/
|
||||||
router.put("/uber/menu/replace", asyncHandler(controller.replaceMenu));
|
router.put("/uber/menu/replace", asyncHandler(controller.replaceMenu));
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user