Спецификация ICUS-211 / ICUS-212 — YouTube publish date sync (Contentful App)
Источник: Jira-задачи (одинаковый текст в обоих):
Связь с PR и зоны ответственности:
| Слой | Что | Кто |
|---|---|---|
| Contentful App (отдельное React-приложение в Contentful UI) | Тригерится при работе с Video entry → читает youtubeUrl → дёргает YouTube Data API → пишет youtubeUploadDate в Contentful | Olexandr (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, который:
- Запускается в 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, не в коде | Проверено: в PR #842 и репо Contentful App в git нет | ✓ |
| Существующие записи: спека описывает поведение 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) — это прямое расхождение со спекой. Адресат: Иван (фронт-guard "если нет даты — не выводить schema"). -
Спека упоминала только "новые/обновлённые" entries. Что делать с историческими данными — в спеке не описано явно. Это скорее gap в самой спеке + scope-решение Contentful App, чем ошибка фронта. Адресат: Саша (миграция existing 700 entries — на стороне App).
-
Sync trigger: спека позволяла оба варианта (Publish или URL change), но предпочитала Publish. Реализация App выбрала URL change — формально допустимо, но не оптимально и приводит к UX-проблеме "Not synced yet". Адресат: Саша (это решение в Contentful App, не во фронте).
-
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_ctfappsPR #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 построчно перед коммитом фронт-части.