Skip to content

[Feature] Category-scoped PUT для inline-edit product data (вместо body category_id) #455

Description

@Ibochkarev

Описание функции

Добавить category-scoped REST-маршрут для мутаций product data из грида «Товары категории», чтобы scope брался из URL, а не из опциональных полей тела запроса.

Предлагаемый контракт:

PUT /api/mgr/categories/{categoryId}/products/{productId}/data

Query/body: nested (0|1) + поля для обновления (как сейчас в PUT /product-data/{id}).

Проблема, которую решает

#444 / #454 закрывают IDOR inline-edit через opt-in проверку: клиент шлёт category_id + nested в body PUT /api/mgr/product-data/{id}.

Недостатки текущего подхода:

  • Scope client-controlled — backend доверяет body-параметрам, а не контексту маршрута.
  • Общий endpoint смешивает два сценария: category grid inline-edit и global save из ProductDataFields.vue (без category).
  • Асимметрия с остальными мутациями грида (/categories/{id}/products/multiple, sort, publish), которые уже category-scoped.

Category-scoped route делает границу категории структурной: нельзя «забыть» передать scope или подменить только id товара.

Предлагаемое решение

  1. Маршрут в config/routes/manager.php:
    • PUT /categories/{id}/products/{productId}/data → новый метод (например CategoryProductsController::updateProductData или thin delegate).
  2. Scope: обязательный {categoryId} из URL + nested из query/body; проверка через CategoryProductsListService::isProductInCategoryScope() (уже в fix(product-data): category scope для inline-edit PUT #454).
  3. Vue: useCategoryProductsInlineEdit переключить на новый URL вместо /product-data/{id} + body category_id.
  4. Legacy: PUT /product-data/{id} оставить для ProductDataFields.vue без category scope (global msproduct_save).
  5. Опционально: deprecate category_id/nested в body глобального product-data после миграции клиентов.

Альтернативы

Примеры использования

Inline-edit в гриде категории 42 (nested):

PUT /api/mgr/categories/42/products/1001/data
Content-Type: application/json

{ "nested": 1, "price": 1990 }

Карточка товара (без category context) — без изменений:

PUT /api/mgr/product-data/1001
Content-Type: application/json

{ "price": 1990, "article": "SKU-1" }

Критерии приёмки

  • Новый маршрут с PermissionMiddleware (msproduct_save) и scope-check по URL categoryId.
  • 403 + lexicon при product вне scope (direct / nested).
  • Vue category grid использует category-scoped route; ProductDataFields — старый route.
  • Smoke/regression test на scope policy (можно расширить CategoryProductScopePolicyTest).
  • Документация Manager API (если есть) обновлена.

Дополнительный контекст

  • Follow-up к #444, реализуется после merge #454.
  • Связано с #418 (scope для bulk mutations) — общая policy, разные endpoints.
  • Unscoped PUT /product-data/{id} намеренно остаётся для global product edit; этот issue не про его удаление.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions