# РестоМесто

Портал-агрегатор заведений с онлайн-бронированием столов, интегрированный с
[Restoplace](https://restoplace.ws). Пользователь выбирает город, находит ресторан списком или на
карте, фильтрует по свободным столам, бронирует стол, ведёт избранное и оставляет отзывы после
посещения.

Restoplace — источник истины по составу каталога, доступности столов и броням. «РестоМесто» не
ведёт собственный учёт столов и резервов.

## Стек

Laravel 13 · PHP 8.3 · MySQL 8.4 · Blade + Livewire 4 + Alpine · Tailwind CSS 4 · Vite ·
Filament 5 для админки `/developer` (Этап 7)

Кеш, очереди и блокировки работают на MySQL — отдельный сервер кеша проекту не нужен.

## Требования

- PHP 8.3+ с расширениями `pdo_mysql`, `mbstring`, `intl`, `bcmath`, `gd`, `exif`, `fileinfo`,
  `openssl`, `curl`, `zip`, `sodium`
- MySQL 8.x (InnoDB, utf8mb4)
- Composer 2.x
- Node 22+

## Установка

```bash
git clone git@gitverse.ru:restomesto/restomesto.git
cd restomesto
composer install
cp .env.example .env
php artisan key:generate
```

Создать базы:

```sql
CREATE DATABASE restomesto CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE DATABASE restomesto_test CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
```

Прописать доступы к базе в `.env`, затем:

```bash
php artisan migrate --seed
php artisan storage:link
npm install
npm run build
```

## Запуск

```bash
composer dev
```

Поднимает сервер, обработчик очереди, просмотр логов и Vite одной командой. По отдельности:

```bash
php artisan serve
php artisan queue:listen --tries=1
npm run dev
```

### Laragon на Windows

`php`, `composer` и `node` не всегда попадают в PATH. В PowerShell перед работой:

```powershell
$env:PATH = "D:\laragon\bin\php\php-8.3.30-Win32-vs16-x64;D:\laragon\bin\composer;D:\laragon\bin\nodejs\node-v22;" + $env:PATH
```

## Наполнение каталога

Заведения приходят только из Restoplace — вручную их не заводят (ТЗ §1). Первое наполнение:

```bash
php artisan restoplace:sync --full
```

Дальше синхронизация идёт сама: инкрементально каждые 5 минут, полный обход раз в сутки. Там же
раз в 10 минут идёт сверка незавершённых броней. Для этого нужен работающий планировщик
(`php artisan schedule:work` локально) и обработчик очереди.

Полезные варианты:

```bash
php artisan restoplace:sync --venue=123
```

`--venue` синхронизирует один адрес, `--city=10` ограничивает обход городом, `--full` делает
полный обход вместо инкрементального.

## Проверки

```bash
composer check
```

Прогоняет всё сразу; по отдельности:

```bash
composer lint      # Laravel Pint — проверка стиля
composer fix       # Pint — исправление
composer analyse   # PHPStan / Larastan, level 6
composer test      # PHPUnit
```

Тесты идут против базы `restomesto_test` в MySQL, а не sqlite: длины индексов, внешние ключи и
JSON-колонки должны вести себя так же, как в production.

Наборы тестов: `tests/Unit` (чистая логика), `tests/Feature` (HTTP и база),
`tests/Contract` (контракт партнёрского API Restoplace).

## Работа без боевого API Restoplace

Партнёрский API Restoplace ещё не готов, поэтому по умолчанию включён драйвер `fake`:

```dotenv
RESTOPLACE_DRIVER=fake
```

`FakeRestoplaceClient` реализует те же интерфейсы и те же DTO, что и боевой клиент, и работает на
контрактных fixtures из `tests/Fixtures/restoplace/`. Переключение на боевой API не требует правок
в бизнес-логике:

```dotenv
RESTOPLACE_DRIVER=http
RESTOPLACE_PARTNER_TOKEN=…
```

Fake-клиент умеет воспроизводить отказы:

```php
/** @var \App\Domain\RestoplaceIntegration\Fake\FakeRestoplaceClient $client */
$client = app('restoplace.client');

$client->failNextWith('slot_unavailable');    // стол заняли между просмотром и отправкой
$client->failNextWith('venue_not_bookable');  // адрес потерял право принимать брони
$client->failNextWith('timeout');             // сеть недоступна
$client->failNextWith('server_error');        // 500
$client->failNextWith('payment_url_missing'); // депозит нужен, платёжной ссылки нет
```

Вторым аргументом сбой привязывается к конкретному вызову — это нужно, когда сценарий проходит
через несколько обращений подряд. Например, перед созданием брони сначала выполняется точная
проверка слотов, и сбой без привязки съел бы её вместо самого создания:

```php
$client->failNextWith('slot_unavailable', 'createReserve');
```

## Вход гостя без телефонии и OAuth-приложений

Тот же приём применён к входу гостя. По умолчанию звонок не совершается, а код фиксирован:

```dotenv
FLASH_CALL_DRIVER=manual
FLASH_CALL_DEV_CODE=0000
```

На `/login` подойдёт любой номер и код `0000`. Боевой обратный звонок включается сменой драйвера:

```dotenv
FLASH_CALL_DRIVER=voicepassword
VOICEPASSWORD_API_KEY=…
```

Вход через VK и Яндекс требует зарегистрированных приложений, поэтому для локальной проверки
кабинета есть заглушка — она не ходит к провайдеру и сразу возвращает гостя на колбэк:

```dotenv
SOCIAL_AUTH_DRIVER=fake
```

При `SOCIAL_AUTH_DRIVER=oauth` кнопка провайдера появляется только когда у него заполнены
`*_CLIENT_ID` и `*_REDIRECT_URI`: кнопка без них ведёт на страницу ошибки провайдера. Адреса
колбэков — `/login/vk/callback` и `/login/yandex/callback`.

## Модерация отзывов без обращения к модели

Тот же приём и здесь. По умолчанию отзывы проверяет детерминированная заглушка:

```dotenv
AI_DRIVER=fake
```

Она публикует всё, кроме текстов со словами-маркерами: `оскорбление`, `мат`, `реклама`,
`персональные данные` дают отклонение, `спорно` — ручную модерацию. Внешнего вызова нет, но
очередь, запись решения, пересчёт рейтинга и письмо ресторану работают по-настоящему.

Модерация идёт в очереди `ai`, поэтому для проверки нужен воркер:

```bash
php artisan queue:work --queue=ai,notifications --stop-when-empty
```

Боевая проверка — `AI_DRIVER=deepseek` плюс `DEEPSEEK_API_KEY`. `AI_DRIVER=null` отключает
автоматическую проверку: все отзывы уходят на ручную модерацию, но не публикуются.

## Кабинет заведения

`/restaurant/login`, вход по одноразовому коду на контактный email адреса — тот, что указан в
Restoplace. Пароля нет, регистрации нет: доступ производен от данных источника.

На фикстурах подойдёт `drova@example.ru` (два адреса), `pushkin@example.ru`, `sahalin@example.ru`.
При `MAIL_MAILER=log` код лежит в `storage/logs/laravel.log`.

Обработка загруженных фотографий идёт в очереди `images`, поэтому для проверки галереи нужен
воркер — без него фотография останется со статусом «в очереди на обработку». Файлы отдаются с
диска `public`, так что нужен симлинк:

```bash
php artisan storage:link
```

## Административный кабинет

`/developer` на Filament 5. Регистрации в панели нет: первый администратор
создаётся из консоли, остальных заводит он сам.

```bash
php artisan admin:create --email=you@example.com --name="Имя" --role=super_admin
```

Пароль команда спросит скрытым вводом — аргументом он не принимается, чтобы не
оставаться в истории команд и в списке процессов. Двухфакторная авторизация
обязательна и настраивается в профиле при первом входе (ТЗ §20.1).

Роли и права заводит сидер:

```bash
php artisan db:seed --class=AdminRoleSeeder
```

## Структура

```
app/
├── Domain/          доменные модули (каталог, брони, отзывы, интеграция Restoplace …)
├── Http/            контроллеры и middleware
├── Providers/       регистрация доменов и интеграции
└── Support/         телефоны, email, slug, маскирование логов, очереди
database/
├── migrations/      схема
├── factories/       фабрики по доменам
├── data/            справочные датасеты (города России)
└── seeders/         справочники: города, кухни, особенности, роли, SEO
docs/                архитектура, контракт API, схема БД, деплой, допущения
resources/views/     Blade-шаблоны
tests/               Unit, Feature, Contract + fixtures
design/              скриншоты прежнего MVP — визуальный референс
```

## Документация

| Документ | О чём |
|---|---|
| [docs/progress.md](docs/progress.md) | **что готово, что следующее, что отложено** — с этого начинать |
| [docs/architecture.md](docs/architecture.md) | стек, версии, модульный монолит, guard-ы, слой интеграции |
| [docs/restoplace-api-contract.md](docs/restoplace-api-contract.md) | **спецификация партнёрского API для разработчика Restoplace** — самодостаточный документ |
| [docs/database-schema.md](docs/database-schema.md) | схема БД и её инварианты |
| [docs/deployment.md](docs/deployment.md) | production, обновление версии, CI/CD |
| [docs/assumptions.md](docs/assumptions.md) | принятые решения и открытые вопросы |

## Секреты

В репозитории их нет. `.env` в `.gitignore`; все ключи — только через окружение или secret
storage. `.env.example` содержит имена переменных без значений.
