Тема
Blueprint: Блог / новости (Blog)
Когда применять
«Новости», «статьи», «полезные материалы», «кейсы» — датируемый контент с рубриками и лентой. Один модуль; разные разделы («новости» и «статьи») — это PostCategory или поле type, не две модели.
Не применять для: страниц (см. pages), сущностей каталога с ценой.
Состав решения
- Модели
Post,PostCategory+ миграции + фабрики - Контроллеры:
PostController@index/show, категория —indexс фильтром - Filament:
PostResource,PostCategoryResource - Blade: лента (карточки + пагинация), статья, компонент «последние посты» для главной
- Sitemap-интеграция, RSS (
spatie/laravel-feed) — если по ТЗ - Тесты
PostTest
Схема данных
posts
| Столбец | Тип | Индекс | Cast / примечание |
|---|---|---|---|
| id | id | PK | |
| category_id | FK nullable → post_categories | constrained + index | nullOnDelete |
| title | string | ||
| slug | string | unique | |
| excerpt | text nullable | анонс для карточек; fallback — обрезка content | |
| content | longText | RichEditor HTML | |
| status | enum(draft, published) | index | PHP Enum; черновики обязательны |
| published_at | timestamp nullable | index (+status composite) | отложенная публикация |
| meta_title / meta_description | string nullable | ||
| views_count | unsignedInteger default 0 | если ТЗ хочет «популярное» | |
| timestamps |
Обложка — spatie/laravel-medialibrary (коллекция cover, конверсии card, hero), не столбец с путём.
post_categories: id, name, slug unique, sort_order, is_active, timestamps.
Правила реализации
- Scope
published:status = published AND published_at <= now()— единственный источник «что видно»; использовать везде включая sitemap/RSS/виджеты. - Роуты:
/blog(лента),/blog/{category:slug}и/blog/{category:slug}/{post:slug}либо плоско/blog/{post:slug}— по ТЗ; выбранную схему зафиксировать 301-редиректами при изменении. - Лента:
->with('category')+ medialibrary eager (->with('media')), пагинация из config, не хардкод. published_atв вывод —translatedFormat('d F Y').- SEO: OG-теги с обложкой; Schema.org
Article(headline, datePublished, image); посты и категории в sitemap. views_count— инкремент без событий (increment), лучше через debounce-джоб или Redis-счётчик с периодическим сбросом в БД, чтобы не писать в таблицу на каждый просмотр под трафиком.- Кеш: тег
posts, flush вafterSaveобоих ресурсов.
Пакеты
spatie/laravel-medialibrary, spatie/laravel-sluggable (опц.), spatie/laravel-feed (RSS, если нужен).
Шаги
- Миграции + модели + Enum + scopes + фабрики.
- Filament-ресурсы (обложка через SpatieMediaLibraryFileUpload, отложенная публикация).
- Контроллеры + роуты с именами.
- Blade: лента
@forelse, статья, виджет для главной. - Sitemap + (опц.) RSS.
- Тесты: лента 200; черновик и будущая дата → 404 и нет в ленте; опубликованный → 200
- h1/title; несуществующий slug → 404; sitemap содержит только published.
Чеклист приёмки
- [ ] Черновики и отложенные посты не видны нигде (лента, прямая ссылка, sitemap, RSS, виджеты)
- [ ] Лента без N+1 (категория и обложка загружены eager)
- [ ] Обложки — конверсии medialibrary, не оригинал 4000px в карточке
- [ ] Даты русские через translatedFormat
- [ ] Article-разметка и OG с обложкой на странице поста