Спецификация 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, который:
- Запускается в Contentful UI при открытии/редактировании Video entry.
- Читает
youtubeUrlи извлекает video ID. - Вызывает YouTube Data API v3 (
videos.list → snippet.publishedAt). - Записывает полученный
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 v3 —
snippet.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 |
Что важно для нашего разбора
-
Спека лежала в нашей Jira (ICUS-211/212) с обновлением 2026-03-12. PR открыт 2026-03-31 — через 19 дней после обновления спеки.
-
Спека прямо требовала валидной VideoObject (это весь смысл — иметь корректные данные для Google rich results). Реализация изначально выводила невалидный объект (без
uploadDate) — это прямое расхождение со спекой. -
Спека упоминала только "новые/обновлённые" entries. Что делать с историческими данными — в спеке не описано явно. Это скорее gap в самой спеке, чем ошибка реализации Ивана. Это нужно отметить честно — у Stevens в этом отношении тоже был пробел в продумывании.
-
Sync trigger: спека позволяла оба варианта (Publish или URL change), но предпочитала Publish. Реализация выбрала URL change — формально допустимо по спеке, но не оптимально и приводит к UX-проблеме "Not synced yet".
-
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 Ивана — нормальное решение.