Учебное руководство
Главы руководства
На этой странице

Практикум: от первого изменения до интеграции CMS

Оглавление · Далее: словарь

Упражнения ниже — задания для читателя. Они не реализованы появлением этой главы. Проводите их в отдельной Git-ветке и с локальными учебными данными. Каждое завершённое задание оформляйте отдельным коммитом с результатом проверки в описании.

1. Найти путь данных одной новости

Цель: проследить данные через реальные границы системы.

Создайте учебную News в Content Manager, задайте slug и publishedOn. Найдите соответствующие query/populate в CMS client, Zod schema в contracts, adapter, Server Component и tRPC procedure. Сравните draft и опубликованный ответ REST.

Результат: можете объяснить, почему Publish меняет страницу без rebuild, почему read-only token находится только на сервере и почему неизвестный slug отличается от недоступной CMS. Не исправляйте ошибки добавлением JSON fallback.

2. Добавить событие и проверить даты

Цель: сохранить смысл времени при разных UTC offsets.

Создайте Event в CMS с startsAt и без endsAt. Publish и проверьте одно время вместо диапазона. Добавьте конец, republish. Сравните запись 2025 и 2026 годов, затем два ISO timestamps с разными offsets. Объясните сортировку по Date.parse и отображение через Europe/Moscow. Не подставляйте полночь/конец, которых не было в источнике.

3. Изменить тему Mantine

Цель: отделить токены темы от геометрии конкретной страницы.

Измените один оттенок палитры или default button radius в packages/ui/src/theme.ts. Проверьте, какие компоненты изменились, а какие остались с цветом из соседних *.module.css и общих CSS variables. Согласуйте тему/CSS, если хотите глобального результата.

Результат: можете объяснить два источника цвета, не заменяя все размеры inline styles. Вручную проверьте мобильное меню, keyboard focus и контраст текста.

Вопрос: зачем ColorSchemeScript расположен в head, а provider — внутри body?

4. Добавить безопасную процедуру tRPC

Цель: изучить общий вход, серверный caller и HTTP-клиент.

Добавьте news.byCategory с входом { category: string }, валидируемым Zod. Верните отфильтрованный массив, не добавляя mutation или доступ к CMS. Напишите серверный тест: существующая категория, неизвестная, неправильный тип.

Результат: вызов работает через caller и tRPC client, а неверный вход отвергается до бизнес-фильтрации. Клиент получает тип новой процедуры без отдельного интерфейса.

Вопрос: где добавить права доступа, если процедура станет отдавать закрытые записи?

5. Ужесточить runtime-контракт

Цель: увидеть разницу между типом и проверкой данных.

В новой схеме для отдельного учебного события потребуйте documentId и ограничьте model списком news | event. Сначала покажите, какие payload текущая schema ещё принимает. Затем добавьте проверки положительного/отрицательного случая.

Результат: invalid JSON не доходит до отправки события; consumer также проверяет вход. Изменение проверено сборкой и typecheck обоих потребителей.

Вопрос: как обновить producer и consumer без окна несовместимости? Можно ли поддержать старую и новую версию события одновременно?

6. Зарегистрировать новую функцию

Цель: пройти trigger → queue → worker → result.

Используйте учебный learning-echo из главы воркера. Зарегистрируйте его в том же worker и отправьте событие через SDK с тестовым payload. Затем создайте свою схему входа вместо повторного использования StrapiWebhook.

Результат: в Hatchet видны отдельное определение и успешное выполнение; остановленный воркер не мешает producer принять событие, а после запуска задача выполняется. Не выдавайте остановку локального worker за проверку production HA.

Вопрос: что ограничивают slots, и что произойдёт при запуске двух экземпляров?

7. Исследовать повторы и идемпотентность

Цель: перестать считать retry гарантией однократного эффекта.

На локальной задаче запланируйте детерминированную ошибку и наблюдайте retries. Смоделируйте побочный эффект на учебном ресурсе. Разделите ошибку до эффекта и после. Затем спроектируйте idempotency key и атомарное сохранение факта обработки.

Результат: объясняете, почему повтор входа может повторить эффект и почему in-memory Set теряется при рестарте. Положительные и отрицательные сценарии проверены без отправки настоящих писем, платежей или изменения production.

Вопрос: что делать, если внешняя система выполнила операцию, но ответ потерялся?

8. Проверить runtime-контракт CMS

Цель: проверить runtime-контракт внешнего REST ответа.

В CMS client test добавьте fixture с неверной датой, недопустимым Blocks link или неожиданным media origin. Добейтесь явного CmsUnavailableError всего ответа. Затем смоделируйте отказ второй страницы pagination: первая не должна стать «полным списком». Сравните гарантии TypeScript и Zod.

Результат: transport boundary покрыт поведением, тест не зависит от live CMS.

9. Подключить Strapi как реальный источник

Цель: подтвердить редакторский вертикальный сценарий уже работающей интеграции.

Через обычный Content Manager: draft → publish → edit/republish → unpublish. Проверьте список, detail, metadata и фотографию без новой сборки. Не заменяйте этот процесс mock-тестом. Затем временно остановите локальную CMS: сравните уже закешированную страницу с /api/content-health, который должен вернуть 503. Верните CMS. Автоматических UI-тестов не добавляйте.

Результат: записи и изображения показывают настоящий state CMS, token не виден в browser data, unpublish убирает detail. Укажите отдельно, какие шаги реально проверены руками, какие покрыты серверными tests и что осталось непроверенным.

10. Проверить резервную копию и восстановление

Цель: получить проверяемую копию, а не просто файл dump.

Создайте тестовую запись и media в локальном Strapi. Снимите копию. Восстановите базу в отдельную learning_restore и файлы в отдельный volume/экземпляр CMS. Не подключайте production-приложение к учебной базе.

Результат: запись и файл открываются в восстановленной CMS; вы знаете, какие секреты и ключи требуются дополнительно к SQL. Запишите время восстановления и версию релиза. Не удаляйте оригинальные тома ради доказательства восстановления.

Вопрос: может ли восстановление Hatchet выполнить старую задачу повторно?

11. Усовершенствовать deploy readiness

Цель: отличить готовый HTTP-сервер от рабочего асинхронного сервиса.

Спроектируйте проверку worker connection и выполнения специального безопасного smoke-события с deadline. Добавьте проверку exp токена и понятную ошибку до запуска приложений. Изолируйте smoke от бизнес-обработки.

Результат: неисправная очередь/воркер не дают ложный успешный deploy, а успешная проверка не создаёт реальные побочные эффекты. Проверьте локально и в тестовом окружении, прежде чем менять критерий production-отката.

Вопрос: какой false positive возможен, если задачу выполнил старый воркер?

12. Проследить SSG → кеш → ISR

Цель: отличить генерацию страницы от её обновления.

Запустите node scripts/check-static-site.mjs и найдите проверки prerender manifest, cache HIT и webhook. На локальной CMS создайте новость с новым slug: убедитесь, что адрес появляется без rebuild. Сначала откройте отсутствующий адрес, затем опубликуйте запись с этим slug и проверьте сброс закешированного 404.

Результат: можете объяснить роль generateStaticParams, тега cms, revalidatePath и страховочной часовой ревалидации. Не отключайте SSG для обхода ошибки CMS и не смешивайте React cache с межзапросным кешем Next.js.

13. Собрать страницу состояния на UI kit

Цель: собрать интерфейс из темы, примитивов и CSS Module.

Прочитайте app/not-found.tsx. Измените пояснение и вторую ссылку, сохранив семантический h1 и HTTP 404. Проверьте произвольный URL и отсутствующий slug CMS, клавиатурный фокус, ширину 320 px и режим без JavaScript. Зафиксируйте отдельно ограничение ISR fallback текущего Next.js, описанное в главе SSG.

Результат: объясняете, что задаёт тема, что — CSS Module, и почему ошибка сети не должна превращаться в сообщение об удалённой странице.

Контрольные вопросы

  1. Почему contracts нужен build перед dev?
  2. Почему tRPC caller не использует URL /api/trpc?
  3. Почему import type важен для клиентского кода и native Node TypeScript?
  4. Почему 200 webhook не означает, что новый HTML уже сгенерирован?
  5. Почему .env и named volume живут дольше Docker-контейнера?
  6. Почему смена .env не меняет пароль существующей роли PostgreSQL?
  7. Почему Docker restart не применяет новое environment?
  8. Почему robots.txt не защищает закрытые данные?
  9. Что восстановится при image rollback, а что останется новым?
  10. Каких проверок не выполняет зелёный pnpm check?

Ответы можно найти по соответствующим главам. Если ответ состоит только из названия инструмента, попробуйте описать конкретный путь данных или отказ — это главный учебный результат этого проекта.