Stevens / Ivan Issue Report

Спецификация ICUS-211 / ICUS-212 — YouTube publish date sync (Contentful App)

Источник: Jira-задачи (одинаковый текст в обоих):

Связь с PR: PR #842 (репозиторий stevens_main_nextjs) реализует читающую часть — Next.js использует сохранённое в Contentful поле youtubeUploadDate для генерации VideoObject schema. Сама синхронизация с YouTube (запись поля) делается отдельным Contentful App, который должен жить в отдельном репозитории.

⚠️ В тексте задачи был YouTube API key (AIzaSy...). Здесь он редактирован — реальный ключ хранится только в Jira / Contentful App config. Если ключ когда-то попал в код / коммит / публичный канал — необходимо ротировать.


Updated scope (2026-03-12)

Option 2 (Contentful App) per Michael Forbes 2026-03-10. Previous webhook/serverless scope replaced.

Context

Для VideoObject schema нужен реальный YouTube publish date (uploadDate). YouTube Data API v3 отдаёт его как:

"snippet": { "publishedAt": "datetime" }

Вместо вызова YouTube API на каждый build/render — дата получается один раз при создании/обновлении контента в CMS и сохраняется в Contentful. Сайт читает её через Delivery API. Никаких runtime-вызовов YouTube, стабильно, без quota issues.

Decision (Michael Forbes, 2026-03-10)

Custom Contentful App в редакторе Contentful. Никакого CMA-токена в Next.js — App получает доступ через стандартную конфигурацию Contentful App.

Scope — Contentful App (Option 2)

Маленький custom Contentful App, который:

  1. Запускается в Contentful UI при открытии/редактировании Video entry.
  2. Читает youtubeUrl и извлекает video ID.
  3. Вызывает YouTube Data API v3 (videos.list → snippet.publishedAt).
  4. Записывает полученный publishedAt в выделенное поле Contentful: youtubeUploadDate.

Sync behaviour (per Michael)

  • Только автоматический — никакой ручной "Sync" кнопки (чтобы редакторы не забывали синкать после смены URL).
  • Trigger: предпочтительно on Publish (минимальное использование API). Если не feasible — on URL change (paste = несколько вызовов; избегать запуска на каждый keystroke).

Field behaviour (per Michael)

  • youtubeUploadDate должно быть disabled для ручного редактирования в Contentful UI.
  • Contentful App при этом должно иметь право записи в это поле — значение всегда из YouTube.

Schema usage

  • VideoObject schema на сайте использует сохранённое youtubeUploadDate из CMS. Никаких runtime-запросов к YouTube.

При смене URL (video ID)

  • Триггер запускается снова (на следующий Publish или URL change — в зависимости от реализации).
  • App перетягивает publishedAt с YouTube и обновляет youtubeUploadDate.

Contentful constraints

  • Сохранённый YouTube publish date не должен быть редактируемым вручную в Contentful.
  • Значение всегда из YouTube API — данные синхронизированы с YouTube.

Technical requirements

  • YouTube Data API v3snippet.publishedAt по video ID.
  • YouTube API key — предоставляется клиентом, хранится только в App configuration (Contentful App config UI), никогда не коммитится в исходники.
  • Repo — TBD с клиентом, где будет жить исходник Contentful App (iX repo, Stevens repo или отдельный репо для Contentful apps). App = маленький React-app, билдится и заливается в Contentful; репо нужен, чтобы не потерять код.

Сопоставление спека ↔ что мы видим в PR #842

Требование спекиЧто в PR / в комментариях ревьюСоответствие
Использовать сохранённое youtubeUploadDate из CMS, не дёргать YouTube в рантаймеPR #842 читает поле из Contentful, пишет VideoObject — ✅
VideoObject должен быть валиднымComment #3: schema выводилась без uploadDate (невалидный VideoObject)❌ → исправлено после ревью
Sync на Publish (предпочтительно), иначе на URL change — но только автоматически, без ручной кнопкиИз Comment #6 (Michael): чтобы заполнить поле, ему пришлось вручную добавить-удалить символ в URL → значит, триггер на URL change, не на Publish; UX-сообщение "Not synced yet — add a video URL to trigger automatic sync" — путает🟡 Реализован fallback-вариант (URL change), не предпочтительный (Publish). Сообщение в UI вводит в заблуждение.
youtubeUploadDate disabled для ручного редактирования, но писаемое из App(требует проверки в Contentful)TBD
API key хранится только в App config, не в коде(требует проверки кода — ключ нигде не закоммичен?)TBD — обязательно проверить
Существующие записи: спека описывает поведение only "при создании/обновлении"~700 существующих entries не синканы (Comment #6); миграция не была предусмотрена изначально❌ — gap в плане миграции; решено через ad-hoc backfill script

Что важно для нашего разбора

  1. Спека лежала в нашей Jira (ICUS-211/212) с обновлением 2026-03-12. PR открыт 2026-03-31 — через 19 дней после обновления спеки.

  2. Спека прямо требовала валидной VideoObject (это весь смысл — иметь корректные данные для Google rich results). Реализация изначально выводила невалидный объект (без uploadDate) — это прямое расхождение со спекой.

  3. Спека упоминала только "новые/обновлённые" entries. Что делать с историческими данными — в спеке не описано явно. Это скорее gap в самой спеке, чем ошибка реализации Ивана. Это нужно отметить честно — у Stevens в этом отношении тоже был пробел в продумывании.

  4. Sync trigger: спека позволяла оба варианта (Publish или URL change), но предпочитала Publish. Реализация выбрала URL change — формально допустимо по спеке, но не оптимально и приводит к UX-проблеме "Not synced yet".

  5. API key: в Jira задача явно говорит "never committed to source control". Нужно проверить, что в PR #842 / Contentful App ключ действительно не закоммичен.

Действия

  • Срочно: проверить, не попал ли YouTube API key в исходники (PR #842, репо Contentful App). Если попал — ротировать ключ + удалить из git history.
  • Уточнить у Ивана — почему выбран trigger on URL change, а не on Publish (предпочтение в спеке)?
  • Уточнить у Ивана — какое поведение youtubeUploadDate (disabled для ручного редактирования)? Соответствует ли спеке?
  • Зафиксировать в позиции встречи: gap про существующие entries — это частично пробел спеки, не только реализации. Backfill script Ивана — нормальное решение.