Перейти к основному содержимому

Как контент попадает из Strapi на витрину

Витрина никогда не ходит в Strapi напрямую. Между ними стоит снимок (snapshot) в базе Medusa. Это даёт две вещи: витрина не падает, если Strapi недоступен, и покупатель никогда не видит полуопубликованное состояние.

Путь публикации

  1. Редактор нажимает «Опубликовать» в Strapi.
  2. Strapi вызывает вебхук POST /cms/webhook на backend.
  3. Backend перечитывает опубликованный контент из Strapi (STRAPI_INTERNAL_URL, STRAPI_READ_TOKEN), собирает целостный снимок и кладёт его в таблицу модуля cms-snapshot.
  4. Витрина читает GET /catalog/content/homepage — отдаёт снимок, Cache-Control: no-store.

Вебхук подписан: заголовки x-cms-timestamp и x-cms-signature, HMAC на STRAPI_WEBHOOK_SECRET, сравнение постоянного времени. Неверная подпись — 401, снимок не обновляется.

Подстраховка раз в минуту

Задача cms-snapshot-refresh (apps/backend/src/jobs/cms-snapshot-refresh.ts) выполняется каждую минуту и делает то же, что вебхук. Поэтому потерянный или не дошедший вебхук — не авария: контент приедет в течение минуты. Вебхук нужен ради скорости, а не ради доставки.

Защита от отката и гонок

persistCmsSnapshot сравнивает не только ревизию, но и content_updated_at источника:

  • ревизия совпала — ничего не пишем;
  • снимок в базе новее приходящего контента — отказываемся записывать;
  • запись идёт условным update по текущей ревизии и content_updated_at <= source, при проигранной гонке операция повторяется один раз.

Смысл: минутная задача и вебхук постоянно работают параллельно, и более старый ответ Strapi не должен затирать более свежий снимок.

Битые ссылки в баннерах

Перед сохранением снимок прогоняется через filterInvalidCmsBanners: баннеры, ведущие на несуществующие товары или категории, из снимка вычищаются. Редактор в Strapi при этом видит их опубликованными — расхождение ожидаемое, витрина осознанно строже.

Предпросмотр

POST /cms/preview-link — вызывается самим Strapi, авторизуется общим секретом CMS_PREVIEW_INTERNAL_SECRET в заголовке x-cms-internal-secret. В ответ — ссылка вида {STOREFRONT_PUBLIC_URL}/preview?token=…, токен подписан CMS_PREVIEW_SIGNING_SECRET и живёт 10 минут. Витрина по такой ссылке показывает черновик, минуя снимок.

Переменные

ПеременнаяГде нужна
STRAPI_INTERNAL_URLbackend читает контент
STRAPI_READ_TOKENтокен чтения Strapi
STRAPI_WEBHOOK_SECRETподпись вебхука, одинаковый в Strapi и backend
CMS_PREVIEW_INTERNAL_SECRETStrapi → backend, запрос ссылки предпросмотра
CMS_PREVIEW_SIGNING_SECRETподпись самого токена предпросмотра
STOREFRONT_PUBLIC_URLбаза для ссылки предпросмотра