# Модуль: Каталог, лента и поиск

[← К оглавлению](../README.md)

Дерево категорий, главная с лентой, листинг раздела (3 вида + карта), поиск и персонализация.

## Категории (дерево)

- `app/Models/Category.php` — **kalnoy/nestedset** (`NodeTrait`), `rootCategory()`, скоупы `active()/roots()`, routeKey = `slug`.
- Импорт: `app/Console/Commands/ImportCategories.php` (`php artisan placeo:import-categories`) — парсит `samples/категории список.txt` (Avito sitemap) через `DOMDocument`, линейный обход якорей: `top-category` → корень, `sub-category+<strong>` → 2-й уровень, `sub-category+<span>` → лист. Сборка дерева через `rebuildTree()`. **1133 узла** (11 корней → 93 подкатегории → 1029 листьев), slug-дедуп.
- `category_attributes` — характеристики для формы и фильтров (`is_filterable`).

## Характеристики категорий (схемы)

Модель `CategoryAttribute` (`category_id, key, label, type[text|number|select|bool], options, unit, is_required, is_filterable, sort`).

- **Наследование вниз по дереву**: `Category::effectiveAttributes()` — собственные атрибуты + унаследованные от предков (общие, частные позже; глубже переопределяет ключ). Используется во всех точках выдачи: форма подачи (`CategoryController@attributes`), карточка (`AdController@show`), редактирование (`Cabinet/AdController@edit`), фильтры листинга (`CategoryController@show`), виджет-API (`WidgetApiController@adDetail`). Дерево Avito дробится до марок (412 листьев под «Автомобили») — поэтому общие авто-поля задаются раз на группе «Автомобили», а листья-марки их наследуют.
- **Источник данных — конвейер генерация → ревью → импорт**:
  - `App\Services\AttributeSchemaService::generate($path)` — запрос к DeepSeek (тот же конфиг/паттерн, что у модерации; `json_object`, temp 0), возвращает нормализованную схему.
  - `php artisan placeo:generate-attributes` — пишет схемы в `database/data/category_attributes.json` (инкрементально, идемпотентно). Опции: `--root=` (по листьям раздела; топ-4 по умолчанию), `--category=` (конкретные категории/группы — для веток с наследованием, напр. `--category=Автомобили`), `--limit`, `--force`, `--sleep`.
  - Ревью/правка JSON вручную → `php artisan placeo:import-attributes` (или `db:seed CategoryAttributeSeeder`) — upsert по `(category_id, key)`.
- **Админка**: relation manager «Характеристики» на `CategoryResource` (Filament) — ручная доводка/добавление, reorder по `sort`.

## Единая лента — FeedController

`app/Http/Controllers/FeedController.php` — общий конструктор запроса для главной, листинга, поиска, продавца:
- параметры (читаются из `input()`): `category` (ветка по `_lft/_rgt`), `q` (поиск title/description), `attr[key]` (характеристики через JSON-колонку), `price_min/max`, `city`+`city_first` (приоритет), `seller`+`status` (вкладки продавца), `sort` (новые/дешевле/дороже);
- **персонализация**: на главной для авторизованных приоритет отдаётся `root_category_id` из топ-просматриваемых (`category_views`);
- `index()` → JSON `{html, next}` для бесконечной прокрутки (Alpine `infiniteFeed`);
- `points()` → JSON маркеров для режима «на карте» (Alpine `feedMap`);
- `page()` → серверный рендер 1-й страницы (SEO), `nextUrl` указывает на `/feed`.

Партиалы: `partials/feed.blade.php` (контейнер + сентинел), `partials/ad-grid.blade.php` (карточки), компоненты `components/ad-card.blade.php` (плитка) и `components/ad-row.blade.php` (строка).

## Главная

`app/Http/Controllers/HomeController.php` + `home.blade.php`: плитки корневых разделов (иконки-emoji), блок «Мои объявления» (3 последних, авторизованным), персонализированная лента.

## Листинг раздела

`app/Http/Controllers/CategoryController@show` + `category/show.blade.php`:
- хлебные крошки, левое меню (**текущая + соседние ветки** + подкатегории), фильтры (цена + `is_filterable`-характеристики), чекбокс «Сначала из города X»;
- **3 вида**: плитки / список / карта (Leaflet с маркерами);
- всё в одной GET-форме с авто-сабмитом; на мобильном сайдбар скрыт за кнопкой «Разделы и фильтры».

## Поиск

`app/Http/Controllers/SearchController@index` + `search.blade.php`: поиск по заголовку/описанию + сортировки. Форма поиска — в шапке (`partials/header.blade.php`).

## Город

`app/Http/Controllers/CityController@select` (`POST /city/select`) — хранит выбранный город в сессии; модалка `partials/city-modal.blade.php` (поиск по списку `allCities`). Дефолт — «Вся Россия».

## Маршруты

```
GET /                 home
GET /feed             feed (JSON)
GET /feed/points      feed.points (JSON)
GET /search           search
GET /c/{category}     category.show
POST /city/select     city.select
```

См. также: [ads.md](ads.md), [seller.md](seller.md).
