Skip to content

ТЗ — Маркетплейс модулей (cms/marketplace-modules)

Слой: 🔵 инфра-модуль · Зрелость доноров: ★ · Донор: — Статус: ТЗ к разработке

Назначение и возможности

Каталог модулей и тем студии внутри админки: просмотр доступных по лицензии сайта пакетов и запуск установки через cms/updates, а не напрямую composer-командой из UI. Сейчас маркетплейс закрытый — витрина показывает только модули студии, сторонние разработчики не публикуют пакеты.

  • Каталог модулей/тем с описанием, зрелостью, зависимостями (requires/suggests);
  • отметка «доступно по лицензии сайта» / «требует лицензию» на каждый пакет;
  • установка модуля — это заявка в cms/updates (сам маркетплейс composer не вызывает);
  • лицензии выдаются per-сайт (домен привязан к токену Satis, см. update-center);
  • поиск и фильтрация каталога (по слою инфра/функц, по зрелости, по зависимостям);
  • статус «уже установлен» / «установлен, но выключен» для каждого пакета парка;
  • предупреждение об истекающей лицензии до того, как пакет станет недоступен;
  • каталог — зеркало Satis: модуль не является источником истины по лицензиям и ценам.

Зависимости и выключение

requires: cms/updates · provides: marketplace-catalog

При выключении каталог модулей недоступен из UI — установка новых модулей возможна только вручную через cms/updates/composer напрямую, без витрины.

Стоимость внешних API. Satis студии — внутренняя инфраструктура (собственный Composer-репозиторий), не тарифицируемый внешний сервис: обращения к нему не считаются квотой и не имеют денежной стоимости за запрос. Отдельно от этого — сами лицензии на платные пакеты имеют коммерческую модель студии (продажа/продление доступа к пакету), но это бизнес-условие, не API-квота: истёкшая лицензия — не «исчерпание лимита запросов», а прекращение доступа к конкретному пакету (см. «Крайние случаи»).

Модель данных

ТаблицаКлючевые поляПримечание
cms_marketplace_catalogpackage, name, layer, maturity, description, requires (json)Кеш каталога, синхронизируется с Satis студии
cms_marketplace_licensessite_id, package, licensed_at, expires_atЛицензии, доступные текущему сайту

requires — JSON-столбец с cast array; layer/maturity — enum → PHP Enum. site_id в cms_marketplace_licensesconstrained('cms_sites')->index() (если cms/multisite включён, иначе — единственный сайт установки); уникальный индекс (site_id, package). cms_marketplace_catalog.package — уникальный индекс (по нему формируется заявка на установку и проверяется существование пакета).

ПДн-паспорт: обе таблицы не содержат персональных данных — site_id, package, licensed_at/expires_at и поля каталога (name, layer, maturity, description, requires) — идентификаторы инсталляции и коммерческие атрибуты лицензии/пакета, не данные субъекта. Участия в «выгрузить всё по субъекту» / «забыть по запросу» (152-ФЗ) модуль не несёт, UserDeleted не слушает.

Входные и выходные данные

Входы

ИсточникДанные/поляЧем валидируется
Filament: фильтр/поиск каталогаlayer, maturity, q (поисковая строка)FormRequest whitelist параметров, неизвестный фильтр → 422
Filament: кнопка «Установить» / POST .../{package}/installpackage (slug пакета)permission marketplace-modules.manage; package — обязан существовать в cms_marketplace_catalog и быть лицензирован для текущего сайта
Манифест Satis студиисписок пакетов, зрелость, зависимости, слойHTTPS + токен (тот же канал доступа, что у cms/updates), схема каталога валидируется перед разбором
Реестр лицензий студии (внешний источник, синк с Satis)site_id, package, expires_atпривязка домена к токену, как в update-center; протухшая лицензия — пакет помечается недоступным

Выходы

ПотребительДанныеФормат
Filament UIкарточки каталога, статус «доступно»/«требует лицензию»/«установлен»Blade через сервис модуля
/api/v1/admin/marketplace-modules/*конверт data/metaJSON REST, keyset-пагинация каталога
cms/updatesзаявка на установку пакетасервис-вызов (requires, канал 4)
cms_marketplace_catalog/cms_marketplace_licensesсинхронизированный каталог и лицензииБД

Телеметрия к Satis (прозрачность для клиента). При запросе каталога и при проверке лицензии на Satis студии уходит: домен/идентификатор сайта, токен доступа сайта (тот же канал, что у cms/updates), запрашиваемый package. Содержимое сайта клиента — контент, посетители, их данные — не передаётся: обмен ограничен метаданными установки и лицензирования. Это не персональные данные посетителей сайта (субъект обмена — инсталляция, не человек); состав передаваемых данных задокументирован в docs/module.md как факт интеграции, не скрытый сбор.

Настройки (группа marketplace-modules)

КлючТипДефолтaffectsPageCacheОписание
marketplace-modules.catalog_sync_interval_hoursint24Периодичность синхронизации каталога с Satis
marketplace-modules.show_unlicensedbooltrueПоказывать в каталоге пакеты без лицензии (как «недоступно»)
marketplace-modules.license_expiry_warning_daysint14За сколько дней до истечения лицензии показывать предупреждение в карточке
marketplace-modules.catalog_page_sizeint24Размер страницы каталога (keyset-пагинация)
marketplace-modules.sync_kill_switchboolfalseАварийное отключение автосинхронизации каталога с Satis: джоба по расписанию не запускается, ручная кнопка «Синхронизировать сейчас» тоже блокируется

Лимиты и квоты. catalog_page_size — единственный количественный лимит модуля (размер страницы каталога), дефолт задан; при достижении — обычная keyset-пагинация (следующая страница по курсору), не деградация и не ошибка. sync_kill_switch — не лимит, а аварийный выключатель на случай проблем на стороне Satis (см. «Фоновая работа» и «Крайние случаи»), чтобы не долбить его повторными запросами.

API

МетодПутьДоступНазначение
GET/api/v1/admin/marketplace-modules/catalogmarketplace-modules.manageКаталог модулей/тем с фильтрами (keyset-пагинация)
GET/api/v1/admin/marketplace-modules/catalog/{package}marketplace-modules.manageКарточка пакета (описание, зависимости, лицензия)
POST/api/v1/admin/marketplace-modules/catalog/{package}/installmarketplace-modules.manageЗаявка на установку (делегирует в cms/updates)

Компоненты

Filament: страница каталога (карточки модулей, фильтр по слою/зрелости, бейдж лицензии), кнопка «Установить» (создаёт заявку в центре обновлений, не выполняет установку сама). Команды: cms:marketplace-modules:sync --json (синхронизация каталога с Satis), cms:marketplace-modules:doctor --json.

Демо-сидеры: не применимо — модуль не имеет собственных блоков/виджетов, каталог и лицензии не участвуют в demo-галерее блоков и playground.

События и обмен

СобытиеКогдаPayload
MarketplaceCatalogSyncedКаталог обновлён с Satispackages_count
MarketplaceInstallRequestedЗаявка на установку пакета отправлена в cms/updatespackage, user_id

requires: cms/updates — установка пакета всегда идёт через публичный сервис центра обновлений (MarketplaceInstallRequested → заявка в cms/updates), сам маркетплейс composer не вызывает. Реализует контракт marketplace-catalog. FilterBus не используется. Слушает только собственное расписание синхронизации каталога с Satis.

Таблица взаимодействий

Сущность/модульКаналНаправлениеЧто происходит
cms/updatesrequires → сервис-вызов (канал 4)outЗаявка на установку пакета делегируется в пайплайн cms:upgrade
Satis студиивнешняя интеграция (HTTPS, вне пяти каналов)inСинхронизация каталога и лицензий по расписанию
Реестр модулей ядрачтение через cms/core-contractsinСтатус «уже установлен» / «установлен, но выключен» для каждого пакета

Фоновая работа

Очередь marketplace-modules: синхронизация каталога с Satis — идемпотентная джоба по расписанию (catalog_sync_interval_hours) через ScheduleRegistrar ядра. Заявка на установку — тоже job, делегируется в очередь cms/updates.

Kill-switch sync_kill_switch=true останавливает джобу синхронизации целиком (не запускается по расписанию) и блокирует ручную кнопку «Синхронизировать сейчас» в Filament — каталог остаётся как есть до выключения свитча обратно. Сценарий: проблемы на стороне Satis — не долбить его повторными запросами, пока не починят.

Метрики и алерты: возраст последней успешной синхронизации каталога (относительно catalog_sync_interval_hours), доля заявок на установку, отклонённых из-за истёкшей лицензии, длительность джобы синхронизации. Алерт — синхронизация не проходила дольше catalog_sync_interval_hours × 2 (пока включён sync_kill_switch алерт подавляется — это осознанная пауза, не сбой).

Ранбук (типовые инциденты):

СимптомЧто проверитьКоманда-лечение
Каталог пуст / не синхронизированстатус очереди marketplace-modules, лог последней джобы, не включён ли sync_kill_switchcms:marketplace-modules:sync --json (вручную, если свитч выключен)
Лицензия истекла посреди установкиexpires_at в cms_marketplace_licenses на момент заявки vs на момент старта cms:upgradeне чинится автоматически — заявка отклоняется, клиенту нужна новая лицензия от студии
Рассинхрон с Satis (локальный каталог устарел)возраст MarketplaceCatalogSynced, статус последнего запроса к Satis в логеcms:marketplace-modules:sync --json; не помогает → cms:marketplace-modules:doctor --json
Недоступность Satis при синхронизациисетевой доступ до Satis студии, не протух ли токенджоба сама деградирует до «оставить прежний каталог» — ручных действий не требуется; при длительной недоступности включить sync_kill_switch, чтобы не спамить попытками

Бэкап/рестор: cms_marketplace_catalog и cms_marketplace_licenses — обе таблицы кеш/зеркало Satis, не источник истины: при потере (не попали в бэкап или рестор сломан) восстанавливаются без даунтайма — каталог прогоном cms:marketplace-modules:sync --json, лицензии — повторной синхронизацией из реестра студии (тем же каналом, что у update-center). Специального шага в процедуре рестора сайта не требуется.

Производительность и кеш

Объёмы: закрытый каталог студии — десятки-сотни пакетов (модули + темы), не тысячи; лицензий — по одной-две записи на сайт на используемый платный пакет. Горячий путь — рендер каталога в Filament (карточки с фильтрами) — должен строиться без N+1 по requires/зависимостям каждого пакета (JSON-столбец читается разом, не отдельным запросом на карточку). Тег marketplace-modules.catalog — кешированный каталог (зеркало Satis) инвалидируется событием MarketplaceCatalogSynced; на page-cache сайта не влияет, только на админ-каталог. Проверка лицензии при заявке на установку — прямой запрос к cms_marketplace_licenses по индексу (site_id, package), не через кеш (цена устаревшей лицензии выше цены лишнего запроса).

Безопасность

Filament/API — marketplace-modules.manage на просмотр каталога и заявку на установку. Лицензия проверяется на сервере при формировании заявки, не только визуально в UI — недоступный по лицензии пакет не устанавливается даже прямым вызовом API. Закрытость маркетплейса обеспечивается источником каталога (Satis), не обходится клиентской настройкой show_unlicensed. Специфичные векторы: подмена package в теле запроса на пакет вне каталога/чужого слоя — whitelist по существующей записи cms_marketplace_catalog, не по свободной строке; попытка установить пакет с истёкшей expires_at — сервер отклоняет заявку с понятным сообщением, а не полагается на то, что UI её не покажет.

Права: marketplace-modules.view, marketplace-modules.manage.

Матрица ролей:

Роль.view (каталог).manage (установка)
Studio (админ студии)естьесть
Администратор сайтаестьесть
Менеджересть (только просмотр)нет
Редактор контента клиентанетнет

Установка модулей — решение уровня владельца инсталляции, не редакторской роли: .manage выдаётся администратору сайта и studio; менеджер видит каталог для оценки, но кнопка «Установить» ему недоступна ни в UI, ни при прямом вызове API — сервер отклоняет запрос без permission, не полагается на скрытие кнопки. Редактору контента клиента каталог не показывается вовсе (вне его зоны ответственности).

UX-требования

Админ: пустое состояние «каталог пуст» с подсказкой «последняя синхронизация — …» и кнопкой ручной синхронизации; карточка пакета с истекающей лицензией — заметное предупреждение за license_expiry_warning_days до истечения, не сюрприз в день X; ошибка установки — человеческое сообщение («лицензия на этот пакет истекла — свяжитесь со студией», не «403 Forbidden»); статус «уже установлен, но выключен» отличим от «не установлен» одним взглядом на карточку; массового действия по установке нет — установка всегда одного пакета за раз через cms/updates, что стоит явно показывать пользователю (не создавать иллюзию батч-установки).

Крайние случаи и типовые баги

  • двойной клик «Установить» → идемпотентность заявки: повторный клик до ответа сервера не создаёт вторую заявку в cms/updates — кнопка блокируется на клиенте и сервер игнорирует дублирующий MarketplaceInstallRequested в короткое окно;
  • лицензия истекает посреди установки (заявка отправлена, пока лицензия ещё действовала, но cms:upgrade стартует позже) → проверка лицензии выполняется и на формировании заявки, и повторно перед стартом установки в cms/updates — окно между заявкой и фактическим применением не должно давать установку без действующей лицензии;
  • рассинхрон каталога (Satis обновил список, локальный кеш устарел) → каталог показывает пометку возраста данных («обновлено N часов назад»), health-чек модуля фиксирует протухшую синхронизацию отдельно от общего статуса;
  • недоступность Satis при синхронизации → джоба синхронизации деградирует до «оставить прежний каталог», не затирает его пустым ответом; ошибка логируется, не падает молча;
  • отсутствие cms/updates (requires не удовлетворён — некорректная инсталляция) → манифест-гейт §3 стандарта не даёт модулю включиться без своей зависимости — сам сценарий не должен возникать в проде, но self-test enable обязан явно на это указывать;
  • выключение модуля посреди установки → уже отправленная заявка в cms/updates продолжает обрабатываться независимо (владение процессом установки — у cms/updates, не у маркетплейса) — выключение витрины не прерывает уже идущий cms:upgrade;
  • пустой каталог (первая установка, синхронизация ещё не прошла) → «каталог синхронизируется, зайдите позже» вместо пустого экрана без объяснения;
  • огромный каталог (гипотетический рост числа пакетов студии) → keyset-пагинация и индексы по layer/maturity уже заложены — фильтрация не деградирует до full scan;
  • измерения locale/city/site — каталог и лицензии не мультиязычны; лицензия привязана к site_id (при cms/multisite) — на инсталляции с несколькими сайтами каждый сайт видит только свои лицензии, общий каталог пакетов один на всех;
  • противоречивая комбинация настроек: show_unlicensed=false, но лицензия истекает во время сессии администратора (пакет был виден, потом пропал из каталога без объяснения) → карточка не должна резко исчезать — переход в статус «лицензия истекла» нагляднее, чем исчезновение записи.

Донорский код

Донор: — (новая разработка)

Миграция legacy-данных (§16 стандарта) — не применимо: у модуля нет донора, каталог и лицензии — новая сущность студии, данных на старых площадках для переноса не существует. Команда cms:marketplace-modules:import-legacy не создаётся.

Тесты и приёмка

  • [ ] Контрактные тесты: установка пакета из каталога создаёт заявку в cms/updates, а не вызывает composer напрямую;
  • [ ] health-чек модуля проверяет актуальность синхронизации каталога (не протух дольше интервала);
  • [ ] деградация при выключении — установка модулей остаётся возможной вручную через cms/updates;
  • [ ] лицензия проверяется на сервере при заявке на установку, не только визуально в UI;
  • [ ] лицензия перепроверяется повторно перед фактическим применением в cms/updates, не только на формировании заявки;
  • [ ] двойной клик «Установить» не создаёт две заявки на один пакет;
  • [ ] права на установку отделены от простого просмотра каталога;
  • [ ] закрытость маркетплейса (только пакеты студии) не может быть обойдена изменением настройки клиентом;
  • [ ] недоступность Satis при плановой синхронизации не затирает существующий каталог;
  • [ ] контрактный набор cms-testing зелёный, пакет протестирован в testbench-изоляции;
  • [ ] feature-тест на каждый роут API; тестовая БД только marketplace-modules_test, migrate:fresh/refresh/reset запрещены;
  • [ ] sync_kill_switch=true останавливает джобу синхронизации по расписанию и блокирует ручную кнопку «Синхронизировать сейчас» — и в UI, и при прямом вызове команды/API;
  • [ ] роли разведены: менеджер видит каталог, но не может инициировать установку ни через UI, ни через API; редактор контента клиента каталог не видит вовсе;
  • [ ] состав телеметрии к Satis (домен/токен/package) задокументирован в docs/module.md и не содержит содержимого сайта клиента или персональных данных посетителей.

Внутренняя база знаний студии