# Архитектура

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

## Стек

| Слой | Технология |
|---|---|
| Backend | **PHP 8.3**, **Laravel 13** |
| БД | **MySQL 8.4** (через Laragon) |
| Веб-сервер (локально) | **Apache + mod_php** (Laragon), домен `placeo.ru` |
| Frontend | Server-rendered **Blade** + **Alpine.js**, **Tailwind CSS v4** (Vite) |
| Дизайн | **Material Design 3** (схема из seed `#87C540`) |
| Карты | **Leaflet + OpenStreetMap**, геокодинг **Nominatim** |
| Realtime | **polling** (чат, без WebSocket) |
| Очереди/планировщик | драйвер `database` |
| Авторизация виджета | **Laravel Sanctum** (Bearer-токены) |
| Админка | **Filament 5** (`/admin`) |
| Категории | **kalnoy/nestedset** (вложенные множества) |
| Изображения | **intervention/image v4** (GD) → WebP |
| OAuth | **laravel/socialite** + `socialiteproviders/yandex` |

## Структура проекта

```
app/
  Console/Commands/ImportCategories.php   — импорт дерева категорий из samples
  Filament/                               — админка (Resources, Pages, Widgets)
  Http/Controllers/
    Auth/         — регистрация, подтверждение почты кодом (EmailVerification), вход, Яндекс OAuth
    Cabinet/      — кабинет (Dashboard, Ad, Profile, Chat, Notification, Widget)
    Widget/       — публичный API виджета + его авторизация
    *             — Home, Category, Ad, Seller, Feed, Search, Favorite, Follow, Review, City, Blog, Page, Sitemap
  Http/Middleware/ShareLayoutData.php     — общие данные для layout (город, избранное, счётчики)
  Jobs/           — ModerateAd, Process*, SendBatchedNewAdNotifications, SendTelegramAlert
  Models/         — User, Category, Ad, AdPhoto, City, Favorite(пивот), Review,
                    ChatThread, ChatMessage, Notification, Widget, Report, Setting, CategoryView,
                    Page, Article, AiUsage
  Services/       — DeepSeekClient (общий вызов+учёт), ModerationService, ImageService, NotificationService, TelegramService
public/widget/    — placeo-widget.js (загрузчик), placeo-widget.css (namespaced)
resources/
  css/app.css     — токены MD3 + компоненты + маппинг на Tailwind
  css/_md3-colors.css — сгенерированная цветовая схема (light+dark)
  js/app.js       — Alpine-компоненты (лента, чат, карта, виджет-пикеры…)
  views/          — layouts, partials, components, страницы
routes/
  web.php         — публичные + кабинет
  widget.php      — API виджета (CORS, без CSRF)
  console.php     — планировщик (батч уведомлений каждые 2ч)
scripts/gen-md3-theme.mjs — генерация цветовой схемы MD3
database/migrations, seeders, data/ru_cities.php
```

## Соглашения

- **Модели Laravel 13** используют PHP-атрибуты `#[Fillable([...])]` / `#[Hidden([...])]` вместо свойств.
- **Дизайн-система**: переменные `--md-sys-color-*` в `_md3-colors.css`; роли MD3 замаплены на утилиты Tailwind в `app.css` (`@theme`), поэтому в разметке используются обычные классы (`bg-brand-500`, `text-muted` и т.п.), но рендерятся цвета MD3. Компоненты: `.btn-brand`, `.btn-tonal`, `.btn-ghost`, `.input`, `.card`.
- **Интерактив** — Alpine-компоненты, зарегистрированные в `resources/js/app.js` (`infiniteFeed`, `feedMap`, `addressMap`, `chat`, `favBtn`, `followBtn`, `phoneReveal`, `gallery` (+лайтбокс), `adForm`, `catPicker`, `authModal`, `chatWidget`, `phoneMask`, `sellerReviews`).
- **Лента** везде идёт через единый `FeedController` (см. [catalog.md](catalog.md)).

## Схема БД (основные таблицы)

| Таблица | Назначение |
|---|---|
| `users` | + `company_name, description, phone, avatar, yandex_id, role, is_blocked, rating_cache, reviews_count` |
| `categories` | nested set (`parent_id, _lft, _rgt`), `icon, is_active`, ~1133 узла |
| `category_attributes` | характеристики категории (`key, label, type, options, unit, is_filterable`) |
| `category_views` | трекинг интересов для персонализации ленты |
| `cities` | справочник городов РФ (`lat, lng`) |
| `ads` | объявление: `category_id, root_category_id, city_id, slug(уникальный), price, price_is_from, lat, lng, attributes(json), status, block_reason, widget_id, published_at`, soft-deletes |
| `ad_photos` | `path_large` (WebP 1200px), `path_preview` (WebP) |
| `favorites` | пивот user↔ad |
| `follows` | подписки user→seller |
| `chat_threads` / `chat_messages` | переписки (`is_support`, `read_at`) |
| `reviews` | отзывы (`author_id, target_user_id, thread_id, rating`) |
| `notifications` | `type` (price_drop / new_ads_batch / platform_news / review), `data(json)`, `read_at` |
| `pending_new_ad_notifications` | буфер для агрегации новых объявлений за 2ч |
| `widgets` | `public_key`, `config(json)` |
| `reports` | жалобы на объявление или продавца (`ad_id?, target_user_id?, reason, status`) |
| `pages` | статические контентные страницы (`slug, title, content, is_published`) |
| `articles` | статьи блога (`slug, title, excerpt, content, cover_path, author_name, is_published, published_at`) |
| `ai_usages` | учёт вызовов DeepSeek (`type, user_id?, ad_id?, model, *_tokens, cost`) |
| `settings` | key-value (ключи DeepSeek/Telegram, флаги) |
| `personal_access_tokens` | Sanctum (токены виджета) |

Полный порядок пересборки БД — в [setup.md](setup.md).
