Retro

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

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

Связь с PR и зоны ответственности:

СлойЧтоКто
Contentful App (отдельное React-приложение в Contentful UI)Тригерится при работе с Video entry → читает youtubeUrl → дёргает YouTube Data API → пишет youtubeUploadDate в ContentfulOlexandr (Sasha) Fedorenko
Frontend / PR #842 (stevens_main_nextjs)Next.js читает youtubeUploadDate из Contentful через Delivery API → рендерит <VideoObject> JSON-LDИван

На встрече разделять адресатов: scope-вопросы про Contentful App (trigger, поведение поля, миграция existing entries, pre-merge tasks для deploy App) — к Саше. Frontend-вопросы (как читается поле, есть ли guard на пустое значение, чистота коммита) — к Ивану.

В тексте Jira был YouTube API key (AIzaSy... — в этом файле обрезан). Рабочий ключ только в Jira / Contentful App config. Проверено: в репозитории Next.js (PR #842) и в репо Contentful App ключ не закоммичен — отдельная срочная ротация по этому поводу не нужна; при любой будущей утечке в git или публичный канал — ротировать и при необходимости переписать историю.


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, не в кодеПроверено: в PR #842 и репо Contentful App в git нет
Существующие записи: спека описывает поведение 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) — это прямое расхождение со спекой. Адресат: Иван (фронт-guard "если нет даты — не выводить schema").

  3. Спека упоминала только "новые/обновлённые" entries. Что делать с историческими данными — в спеке не описано явно. Это скорее gap в самой спеке + scope-решение Contentful App, чем ошибка фронта. Адресат: Саша (миграция existing 700 entries — на стороне App).

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

  5. API key: спека требует не коммитить в git. Проверено — в PR #842 (фронт) и репо Contentful App (Саша) ключ не попадал.

Действия

Все вопросы ниже — общекомандные. Разделение по слою (Contentful App / фронт) только подсказывает, на чьём стороне scope-знание; ответы — совместные.

Вопросы по Contentful App (scope-уровень)

  • Почему выбран trigger on URL change, а не on Publish (предпочтение спеки)?
  • Реализовано ли youtubeUploadDate как disabled для manual editing (но writable из App) согласно спеке?
  • Какой план был по миграции existing 700 entries на момент завершения App? Backfill script появился по запросу Michael — почему не был спроектирован сразу?
  • Где живёт код Contentful App (репо)? — найден: stevens_main_ctfapps PR #1.
  • Pre-merge tasks для deploy App + Contentful field develop→master — почему не были озвучены до открытия фронт-PR?

Вопросы по фронту PR #842

  • Почему console.log попал в PR? Pre-commit hooks / lint — есть?
  • Почему изначально не было guard "если youtubeUploadDate пуст — не выводить <VideoObject>"? Спека прямо требует валидной schema.
  • При написании PR description — переспрашивали ли владельца scope, что нужно для deploy этого PR (pre-merge tasks для Contentful App / поле в Contentful)?

Совместное / процесс

  • Утечка YouTube API key в исходники — не выявлена (проверка закрыта).
  • Зафиксировать: gap про existing entries — это частично пробел спеки + scope-решение по App, не только реализации. Backfill script — нормальное закрывающее решение.
  • Ввести product-checkpoint: владелец scope + автор фронта + менеджер проходят acceptance criteria построчно перед коммитом фронт-части.