diff --git a/docs/developer-portal/05-menus.md b/docs/developer-portal/05-menus.md index 1899441..6e3d690 100644 --- a/docs/developer-portal/05-menus.md +++ b/docs/developer-portal/05-menus.md @@ -28,6 +28,9 @@ Item update route: - `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` +- reference catalogs for `product_info`: + - `GET /api/v1/uber/catalog/product-types` + - includes `product_types` and `mixin_types` Menu fetch route: diff --git a/docs/developer-portal/18-product-types-audit.md b/docs/developer-portal/18-product-types-audit.md new file mode 100644 index 0000000..fb402d1 --- /dev/null +++ b/docs/developer-portal/18-product-types-audit.md @@ -0,0 +1,19 @@ +# 18 Product Types Audit + +Source checked: Uber Eats "Uber Product Types / Uber Mixin Types" section shared by you. + +## Implemented Now + +- Added a centralized in-code product catalog: + - [uberProductCatalog.js](../../src/config/uberProductCatalog.js) + - `PRODUCT_TYPES` + - `MIXIN_TYPES` +- Added a wrapper endpoint for developers/POS UIs: + - `GET /api/v1/uber/catalog/product-types` + - returns both product and mixin type lists as JSON +- Added Swagger route annotation for the catalog endpoint. + +## Notes + +- The catalog endpoint is reference data for building menu payloads (`product_info.product_type`, `product_info.product_traits`). +- Wrapper still keeps menu payload bodies flexible (`z.any`) to avoid blocking future Uber enum additions. diff --git a/docs/openapi/openapi.json b/docs/openapi/openapi.json index 5240e69..423971a 100644 --- a/docs/openapi/openapi.json +++ b/docs/openapi/openapi.json @@ -290,6 +290,19 @@ } } }, + "/api/v1/uber/catalog/product-types": { + "get": { + "summary": "Return Uber Product Types and Uber Mixin Types catalog", + "tags": [ + "Uber Menu" + ], + "responses": { + "200": { + "description": "Catalog returned" + } + } + } + }, "/api/v1/uber/menu/upsert": { "post": { "summary": "Legacy upsert helper for store menu", diff --git a/postman/Uber_Wrapper.postman_collection.json b/postman/Uber_Wrapper.postman_collection.json index 1fde0e9..6b17a8b 100644 --- a/postman/Uber_Wrapper.postman_collection.json +++ b/postman/Uber_Wrapper.postman_collection.json @@ -166,6 +166,31 @@ } } }, + { + "name": "Get Uber Product Types Catalog", + "request": { + "method": "GET", + "header": [ + { + "key": "x-api-key", + "value": "{{apiKey}}" + } + ], + "url": { + "raw": "{{baseUrl}}/api/v1/uber/catalog/product-types", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "api", + "v1", + "uber", + "catalog", + "product-types" + ] + } + } + }, { "name": "Upload Menu (PUT v2)", "request": { diff --git a/src/config/uberProductCatalog.js b/src/config/uberProductCatalog.js new file mode 100644 index 0000000..4ccb40f --- /dev/null +++ b/src/config/uberProductCatalog.js @@ -0,0 +1,346 @@ +const RAW_PRODUCT_TYPES = ` +ANIMALS_AND_PET_SUPPLIES +LIVE_ANIMALS +BIRD_SUPPLIES +CAT_SUPPLIES +DOG_SUPPLIES +FISH_SUPPLIES +REPTILE_AND_AMPHIBIAN_SUPPLIES +SMALL_ANIMAL_SUPPLIES +OTHER_PET_SUPPLIES +APPAREL_AND_ACCESSORIES +ARTS_AND_ENTERTAINMENT +ARTS_AND_CRAFTS +MUSICAL_INSTRUMENTS_AND_ACCESSORIES +PARTY_AND_OCCASION +BABY_AND_TODDLER +BABY_BATH_AND_BODY_CARE +BABY_DIAPERING_AND_WIPES +BABY_NURSING_AND_FEEDING +BABY_STROLLERS +BABY_CAR_SEATS +BABY_CARRIERS +BABY_TRANSPORT_ACCESSORIES +BABY_BEDDING_AND_DECOR +BABY_FURNITURE_AND_SAFETY_ACCESSORIES +BABY_CLOTHING_AND_ACCESSORIES +BABY_TOYS_AND_ACTIVITY_EQUIPMENT +BEVERAGE +BEVERAGE_ALCOHOLIC +BEVERAGE_ALCOHOLIC_BEER +BEVERAGE_ALCOHOLIC_WINE +LIQUORS_AND_SPIRITS +BEVERAGE_ALCOHOLIC_HARD_SELTZERS_AND_ALTERNATIVES +HARD_CIDER +SAKE +SHOCHU +BEVERAGE_NON-ALCOHOLIC +BEVERAGE_NON-ALCOHOLIC_SOFT_DRINKS +BEVERAGE_NON-ALCOHOLIC_COFFEE +BEVERAGE_NON-ALCOHOLIC_TEA +BEVERAGE_NON-ALCOHOLIC_WATER_AND_SELTZER +JUICE +POWDERED_DRINK_MIXES +SPORTS_ENERGY_AND_ELECTROLYTE_DRINKS +MILK_EGGNOG_AND_BUTTERMILK +MEAL_REPLACEMENTS_AND_PROTEIN_DRINKS +BEVERAGE_NON-ALCOHOLIC_WINE +BEVERAGE_NON-ALCOHOLIC_BEER +BEVERAGE_NON-ALCOHOLIC_SPIRITS +NON-ALCOHOLIC_COCKTAIL_MIXERS +BUSINESS_AND_INDUSTRIAL +CAMERAS_AND_OPTICS +CANNABIS +CANNABIS_VAPE +CANNABIS_EDIBLE +CANNABIS_BEVERAGE +CANNABIS_TOPICAL +CANNABIS_EXTRACT +CANNABIS_FLOWER +CANNABIS_PRE-ROLL +CANNABIS_OIL +CANNABIS_CAPSULE +CANNABIS_SPRAY +CANNABIS_OTHER +ELECTRONICS +FOOD +FOOD_RAW_MEATS_AND_FISH +PORK +BEEF +CHICKEN +TURKEY +OTHER_POULTRY +FISH_AND_SEAFOOD +LAMB +HOT_DOGS_SAUSAGES_AND_BACON +LUNCH_AND_DELI_MEAT +MEAT_AND_SEAFOOD_GIFTS +TOFU_SOY_AND_MEAT_ALTERNATIVES +MEAT_ALTERNATIVES +SEITAN +TEMPEH +TOFU +FOOD_PRODUCE +FRESH_FRUIT +FRESH_VEGETABLES +FRESH_HERBS +PRE-PACKAGED_FRUIT_AND_VEGETABLES +FOOD_BAKERY +BAGELS +BAKERY_ASSORTMENTS +BREADS_AND_BUNS +CAKES_AND_DESSERT_BARS +COFFEE_CAKES +CUPCAKES +DONUTS +ICE_CREAM_CONES +MUFFINS +PASTRIES_AND_SCONES +PIES_AND_TARTS +TACO_SHELLS_AND_TOSTADAS +TORTILLAS_AND_WRAPS +CANDIED_AND_CHOCOLATE_COVERED_FRUIT +CANDY_COOKIES_AND_CHOCOLATE +CANDY_AND_CHOCOLATE +COOKIES_AND_SWEET_BISCUITS +FOOD_SNACKS_AND_CANDY +APPLESAUCE_AND_FRUIT_CUPS +BREADSTICKS +BARS +CHEESE_PUFFS +CHIPS_AND_CRISPS +CRACKERS +CROUTONS +DRIED_FRUIT_AND_RAISINS +FRUIT_LEATHER +FRUIT_SNACKS +GUMN_AND_MINTS +ICE_CREAM_CONES_AND_TOPPINGS +JERKY_AND_PORK_RINDS +NUTS_AND_SEEDS +POPCORN +PRETZELS +PUDDING_AND_GELATIN_SNACKS +PUFFED_RICE_CAKES +SALAD_TOPPINGS +SESAME_STICKS +SNACK_CAKES +STICKY_RICE_CAKES +TRAIL_AND_SNACK_MIXES +FOOD_FROZEN_FOODS +FROZEN_APPETIZERS_AND_SNACKS +FROZEN_BREAD_AND_DOUGH +FROZEN_BREAKFAST_FOODS +FROZEN_DESSERTS_AND_TOPPINGS +FROZEN_FRUIT +ICE +ICE_CREAM_AND_NOVELTIES +FROZEN_JUICE +FROZEN_MEALS_AND_ENTREES +FROZEN_MEATS +FROZEN_PASTA_AND_SAUCES +FROZEN_PIZZA +FROZEN_POTATOES_AND_ONION_RINGS +FROZEN_FISH_AND_SEAFOOD +FROZEN_VEGETABLES +FOOD_DAIRY +BUTTER_AND_MARGARINE +CHEESE +COFFEE_CREAMER +COTTAGE_CHEESE +CREAM +EGGS +SOUR_CREAM +WHIPPED_CREAM +YOGURT +FOOD_CONDIMENTS +COCKTAIL_SAUCE +CURRY_SAUCE +DESSERT_TOPPINGS +FISH_SAUCE +GRAVY +HONEY +HORSERADISH_SAUCE +HOT_SAUCE +KETCHUP +MARINADES_AND_GRILLING_SAUCES +MAYONNAISE +MUSTARD +OLIVES_AND_CAPERS +PASTA_SAUCE +PICKLED_FRUITS_AND_VEGETABLES +PIZZA_SAUCE +RELISH_AND_CHUTNEY +SALAD_DRESSING +SATAY_SAUCE +SOY_SAUCE +SWEET_AND_SOUR_SAUCES +SYRUP +TAHINI +TARTAR_SAUCE +WHITE_AND_CREAM_SAUCES +WORCESTERSHIRE_SAUCE +COOKING_AND_BAKING_INGREDIENTS +BAKING_CHIPS +BAKING_CHOCOLATE +BAKING_FLAVORS_AND_EXTRACTS +BAKING_MIXERS +BAKING_POWDER +BAKING_SODA +BATTER_AND_COATING_MIXES +BEAN_PASTE +BREAD_CRUMBS +CANNED_AND_DRY_MILK +COOKIE_DECORATING_KITS +COOKING_OILS +COOKING_STARCH +COOKING_WINE +CORN_SYRUP +DOUGH +EDIBLE_BAKING_DECORATIONS +EGG_REPLACERS +FLOSS_SUGAR +FLOUR +FOOD_COLORING +FROSTING_AND_ICING +LEMON_AND_LIME_JUICE +MARSHMALLOWS +MEAL +MOLASSES +PIE_AND_PASTRY_FILLINGS +SHORTENING_AND_LARD +STARTER_CULTURES +SUGAR_AND_SWEETENERS +TAPIOCA_PEARLS +TOMATO_PASTE +UNFLAVORED_GELATIN +VINEGAR +WAFFLE_AND_PANCAKE_MIXES +YEAST +DIPS_AND_SPREADS +APPLE_BUTTER +CHEESE_DIPS_AND_SPREADS +CREAM_CHEESE +GUACAMOLE +HUMMUS +JAMS_AND_JELLIES +NUT_BUTTERS +SALSA +TAPENADE +VEGETABLE_DIP +GRAINS_RICE_AND_LEGUMES +AMARANTH +BARLEY +BUCKWHEAT +MILLET +QUINOA +RICE +RYE +WHEAT +DRIED_BEANS +DRIED_LENTILS_AND_PEAS +CEREAL_AND_GRANOLA +OATS_GRITS_AND_HOT_CEREAL +CEREAL_OATMEAL_AND_GRANOLA +FOOD_PREPARED_FOOD +INSTANT_MEALS +PASTA_AND_NOODLES +SEASONINGS_AND_SPICES +CANNED_PRODUCE_AND_SOUPS +CANNED_AND_PREPARED_BEANS_LENTILS_AND_PEAS +CANNED_AND_JARRED_FRUITS +CANNED_MEAT_AND_SEAFOOD +CANNED_AND_BOXED_SOUP_STOCKS_AND_BROTHS +FOOD_AND_NON-ALCOHOLIC_BEVERAGE_COMBO +FOOD_AND_ALCOHOLIC_BEVERAGE_COMBO +FURNITURE +HARDWARE +HEALTH_AND_BEAUTY +HEALTH_AND_BEAUTY_FAMILY_PLANNING +MATURE_CONDOMS +MATURE_LUBRICANTS +HEALTH_AND_BEAUTY_FIRST_AID +VITAMINS_AND_SUPPLEMENTS +MEDICAL_SUPPLIES_AND_MONITORS +HEALTH_AND_BEAUTY_MEDICINE_AND_DRUGS +HEALTH_AND_BEAUTY_MEDICINE_AND_DRUGS_ANTACIDS_AND_ANTIFLATULENTS +HEALTH_AND_BEAUTY_MEDICINE_AND_DRUGS_COUGH_SUPRESSANTS +HEALTH_AND_BEAUTY_MEDICINE_AND_DRUGS_COLD_AND_FLU_MEDICINES +HEALTH_AND_BEAUTY_MEDICINE_AND_DRUGS_PRESCRIPTION_DRUGS +BATH_AND_BODY +COTTON_SWABS_AND_BALLS +DEODORANT_AND_ANTIPERSPIRANT +EYE_AND_EAR_CARE +FEMININE_CARE +HAIR_CARE +HAND_AND_FOOT_CARE +JEWELRY_CLEANING_AND_CARE +MAKEUP_NAILS_AND_COSMETIC_TOOLS +ORAL_CARE +PERFUME_AND_COLOGNE +SHAVING_AND_GROOMING +SKINCARE +SLEEP_AND_MASSAGE +HOUSEHOLD_ESSENTIALS +HOUSEHOLD_CLEANING_SUPPLIES +HOUSEHOLD_PAPER_PRODUCTS +LAUNDRY_SUPPLIES +TRASH_BINS_AND_BAGS +FOOD_STORAGE_AND_ACCESSORIES +AIR_FRESHENERS_CANDLES_AND_FRAGRANCE +INSECT_AND_PEST_CONTROL +SHOE_CARE_AND_TOOLS +LIGHTERS_AND_MATCHES +HOME_AND_GARDEN +HOME_AND_GARDEN_BATHROOM_ACCESSORIES +HOME_AND_GARDEN_DECOR +ORGANIZERS +HOME_AND_GARDEN_KITCHEN_AND_DINING +HOME_AND_GARDEN_LAWN_AND_GARDEN +HOME_AND_GARDEN_LIGHTING_AND_LIGHTING_ACCESSORIES +HOME_AND_GARDEN_LINENS_AND_BEDDING +HOME_AND_GARDEN_POOL_AND_SPA +HOME_AND_GARDEN_SMOKING_ACCESSORIES +HOME_AND_GARDEN_GARDEN_CHEMICALS +HOME_AND_GARDEN_FUEL +FLOWERS_AND_PLANTS +FLOWERS_AND_BOUQUETS +HOME_AND_GARDEN_PLANTS +LUGGAGE_AND_BAGS +MATURE +MATURE_ADULT_GAMES +MATURE_VIBRATORS +LINGERIE +LOTTERY +MEDIA +MEDIA_BOOKS +MEDIA_DVD’S_AND_VIDEOS +MEDIA_MAGAZINES_AND_NEWSPAPERS +MEDIA_MUSIC_AND_SOUND_RECORDINGS +OFFICE_SUPPLIES +SOFTWARE +SPORTING_GOODS +TOBACCO +TOBACCO_CIGARETTES +TOBACCO_CIGARS +TOBACCO_E-CIGARETTE +TOBACCO_VAPES +TOBACCO_SMOKELESS_TOBACCO +TOYS_AND_GAMES +VEHICLES_AND_PARTS +`; + +const PRODUCT_TYPES = Array.from( + new Set( + RAW_PRODUCT_TYPES.split(/\r?\n/) + .map((item) => item.trim()) + .filter(Boolean) + ) +); + +const MIXIN_TYPES = ["CONTAINS_ALCOHOL", "CONTAINS_CANNABIS"]; + +module.exports = { + PRODUCT_TYPES, + MIXIN_TYPES +}; diff --git a/src/modules/proxy/proxy.controller.js b/src/modules/proxy/proxy.controller.js index 14bd381..ff779ab 100644 --- a/src/modules/proxy/proxy.controller.js +++ b/src/modules/proxy/proxy.controller.js @@ -1,5 +1,6 @@ const { z } = require("zod"); const proxyService = require("./proxy.service"); +const { PRODUCT_TYPES, MIXIN_TYPES } = require("../../config/uberProductCatalog"); const genericSchema = z.object({ merchantId: z.string().min(1).optional(), @@ -749,6 +750,16 @@ async function deliveryListPromotions(req, res) { return res.json({ success: true, data }); } +async function getUberProductTypes(req, res) { + return res.json({ + success: true, + data: { + product_types: PRODUCT_TYPES, + mixin_types: MIXIN_TYPES + } + }); +} + module.exports = { genericProxy, upsertMenu, @@ -794,5 +805,6 @@ module.exports = { deliveryCreatePromotion, deliveryRevokePromotion, deliveryGetPromotion, - deliveryListPromotions + deliveryListPromotions, + getUberProductTypes }; diff --git a/src/routes/proxy.routes.js b/src/routes/proxy.routes.js index 8cbcdc2..faf221d 100644 --- a/src/routes/proxy.routes.js +++ b/src/routes/proxy.routes.js @@ -17,6 +17,19 @@ const router = express.Router(); */ router.post("/uber/request", asyncHandler(controller.genericProxy)); +/** + * @openapi + * /api/v1/uber/catalog/product-types: + * get: + * summary: Return Uber Product Types and Uber Mixin Types catalog + * tags: + * - Uber Menu + * responses: + * 200: + * description: Catalog returned + */ +router.get("/uber/catalog/product-types", asyncHandler(controller.getUberProductTypes)); + /** * @openapi * /api/v1/uber/menu/upsert: