# Laravel + Vue Ads

Каталог, карточка и создание объявления через same-origin API. Исправленные контракты и сессионная изоляция позволяют безопасно показывать полный CRUD-сценарий.

Статус: passed

Версия: `54f1be9aa1057163edd9d095d27e8c5451935de7`. Backend и Vue-клиент: апрель 2024. Обновление публичного демо: 7 сентября 2026.

## Задача

Список и карточка должны одинаково понимать поля API, а создание объявления — проверять ввод и не затрагивать данные другого посетителя.

## Аудитория

Пользователи каталогов и разработчики Laravel/Vue-приложений.

## Личный вклад

- Laravel API и отдельный Vue-клиент каталога.

- Согласование пагинации, карточки, описания и изображений.

- Переход на Laravel 12, проверка полей, signed cookie и ограниченный demo CRUD.

## Решения

- Клиент и API работают на одном origin без внешнего backend URL.

- 24 исходных объявления общие, созданные посетителем записи фильтруются по владельцу.

- Фото выбирается из фиксированных локальных SVG, произвольные URL и mass assignment исключены.

- Цена хранится DECIMAL(10,2), API возвращает согласованный scalar meta для пагинации.

## Границы и ограничения

- Все объявления и изображения учебные. Продажи и произвольные загрузки отключены.

- 20 собственных объявлений на сессию, 1000 суммарно; TTL один час и cleanup timer каждые пять минут.

- Legacy инструменты Vue-сборки имеют известные dev-зависимости; полный переход frontend tooling не выполнялся.

## Что попробовать

- Откройте каталог, измените сортировку и перейдите на следующую страницу.

- Посмотрите карточку и создайте объявление с локальным изображением.

- Проверьте валидацию пустого/ошибочного ввода.

- Откройте чужой ID во второй сессии: получите 404. Удалите своё объявление и сбросьте пример.

## Проверки

- Sorting, pagination, search, empty state

- Required form validation

- Signed Secure HttpOnly cookie; cross-session GET/DELETE both 404

- Server rejects negative price, short text and external image URL

- Cancel, delete and restored seed count

- Подтверждённого GitHub CI для этого SHA нет; перечислены выполненные локальные проверки и сценарии опубликованной версии. Исторические CI-ссылки, если есть, отмечены отдельно.

## Результаты

- Сохранено 13 содержательных кадров фактического интерфейса.

- Дата, сценарий и версия указаны у каждого кадра.

- Максимум RAM контейнеров в коротком тесте: 39.76 MiB. Общие службы учитываются отдельно; магазин включает свою БД.

- Измерено 6 повторений сценария; медиана 1605.5 мс, максимум 2981 мс. Время включает браузер и HTTPS; методика и ограничения приложены.

## Запуск и эксплуатация

- Кейс объединяет laravel-sandbox и laravel-sandbox-ui. Это один проект с двумя репозиториями; SHA обоих сохраняются в release metadata.

- Сборка выполняется вне VPS через Dockerfile.portfolio и отдельный build context ads-ui. Версии фиксируются composer.lock и package-lock.json.

- Nginx → localhost:18402 → Apache/Laravel. БД ads и пользователь отдельные. Проверять /healthz, GET /api/ad, POST /api/ad, чтение/удаление своего ID. Команда php artisan demo:cleanup вызывается системным таймером; сброс не удаляет seed и чужие записи.

- Общие правила эксплуатации: готовые файлы и закреплённые образы собираются вне VPS; секреты не входят в артефакты. Общесерверный docker prune не применяется. Логи backend ограничены ротацией, изменения log options требуют пересоздания контейнера.

- Перед изменением пройти code-map от функции к экрану/API, модулю и данным. После изменения обновить карту и схемы, выполнить связанные сценарии, включая ошибку, повтор и сброс. Источник истины — конкретная версия кода, а не старая диаграмма.

- Откат проверен 2026-09-07T01:00:33.586236+00:00: 20260907-verified → 20260907b → 20260907-verified. После каждого переключения подтверждены HTTPS, readiness и SHA. Это проверка конфигурации и артефактов на одном проверенном SHA; обратная миграция БД не выполнялась.

## Стек и роль

- **Laravel / Eloquent**: Валидация, owner scope, CRUD и expiry.
- **Vue**: Маршруты каталога, карточки, формы, сортировки и пагинации.
- **MySQL**: Объявления, DECIMAL-цены и срок хранения.

## Code-map

|Функция|Экран / API|Модуль|Данные / проверка|
|---|---|---|---|
|Каталог, поиск, сортировка|AdList / GET api/ad|AdController::index|ads, owner scope|
|Карточка|AdItem / GET api/ad/id|AdController::show|seed или своя запись|
|Создание|AdNew / POST api/ad|Validator, Eloquent|allowlist + session_id + expires_at|
|Удаление и сброс|AdItem / DELETE, POST demo/reset|AdController, DemoSession|только своя сессия|
|Очистка|CLI demo:cleanup|scheduled command|expired rows|

## Схемы

- Компоненты опубликованного демо — `diagrams/components.mmd`
- Основной сценарий — `diagrams/scenario.mmd`
- Размещение опубликованной версии — `diagrams/deployment.mmd`
- Состав данных и границы хранения — `diagrams/data.mmd`

## Индекс изображений

- 01-catalog: Каталог из 24 синтетических объявлений; 2026-09-06T23:26:16.393Z; SHA 54f1be9aa1057163edd9d095d27e8c5451935de7
- 02-sorted: Сортировка по возрастанию цены; 2026-09-06T23:26:16.873Z; SHA 54f1be9aa1057163edd9d095d27e8c5451935de7
- 03-page-two: Вторая страница каталога; 2026-09-06T23:26:17.373Z; SHA 54f1be9aa1057163edd9d095d27e8c5451935de7
- 04-search: Поиск камер: шесть совпадений; 2026-09-06T23:26:18.289Z; SHA 54f1be9aa1057163edd9d095d27e8c5451935de7
- 05-empty: Пустой результат поиска; 2026-09-06T23:26:18.821Z; SHA 54f1be9aa1057163edd9d095d27e8c5451935de7
- 06-new-form: Ограниченная форма нового объявления; 2026-09-06T23:26:19.282Z; SHA 54f1be9aa1057163edd9d095d27e8c5451935de7
- 07-filled: Заполненная форма с локальной иллюстрацией; 2026-09-06T23:26:19.820Z; SHA 54f1be9aa1057163edd9d095d27e8c5451935de7
- 08-created: Созданная карточка собственной сессии; 2026-09-06T23:26:20.487Z; SHA 54f1be9aa1057163edd9d095d27e8c5451935de7
- 09-isolation: Другая сессия не видит созданную карточку; 2026-09-06T23:26:21.248Z; SHA 54f1be9aa1057163edd9d095d27e8c5451935de7
- 10-delete-confirm: Подтверждение удаления только своего объявления; 2026-09-06T23:26:21.720Z; SHA 54f1be9aa1057163edd9d095d27e8c5451935de7
- 11-mobile-catalog: Мобильный каталог; 2026-09-06T23:26:22.357Z; SHA 54f1be9aa1057163edd9d095d27e8c5451935de7
- 12-mobile-card: Мобильная карточка; 2026-09-06T23:26:22.786Z; SHA 54f1be9aa1057163edd9d095d27e8c5451935de7
- 13-mobile-form: Мобильная форма; 2026-09-06T23:26:23.218Z; SHA 54f1be9aa1057163edd9d095d27e8c5451935de7

## Подробности runtime из репозитория

# Laravel + Vue Ads — публичное демо

Подготовлено 07.09.2026. Laravel обновлён с 11 до поддерживаемой 12; PHP 8.4, Eloquent и MySQL работают реально. Vue-клиент использует same-origin API. Каталог и иллюстрации синтетические, внешние продажи и загрузки отключены.

24 исходных объявления доступны всем. Изменяемые данные привязаны к подписанной HttpOnly/Secure/SameSite=Lax cookie, срок сессии один час. В БД хранится хеш идентификатора сессии. Запросы чтения/удаления ограничены владельцем; чужой идентификатор возвращает 404. До 20 объявлений на сессию, до 1000 изменяемых объявлений суммарно. Удаление просроченных записей выполняется при запросе и системным таймером. Сброс затрагивает только свою сессию.

POST принимает только проверенные title, description, price и photo из фиксированного списка локальных SVG. Произвольные фото/URL и mass assignment request->all() исключены. Суммы хранятся DECIMAL(10,2). API пагинации возвращает scalar meta.total/per_page; карточка получает согласованные photo/main_photo/description.

| Сценарий | Экран/API | Модуль | Данные |
|---|---|---|---|
| Каталог, поиск, сортировка | AdList / GET api/ad | AdController::index | ads, owner scope |
| Карточка | AdItem / GET api/ad/id | AdController::show | seed или своя запись |
| Создание | AdNew / POST api/ad | Validator, Eloquent | allowlist + session_id + expires_at |
| Удаление и сброс | AdItem / DELETE, POST demo/reset | AdController, DemoSession | только своя сессия |
| Очистка | CLI demo:cleanup | scheduled command | expired rows |

Перед изменением контракта сопоставить Controller → JSON → Vue-компонент; после изменения проверить создание, просмотр, две независимые сессии, удаление/сброс и пагинацию. Демо не является торговой площадкой для реальных пользователей.

Сборка Docker выполняется вне VPS с отдельным контекстом ads-ui. Точные Composer/npm версии находятся в lock-файлах. Готовый образ передаётся на VPS. Apache за Nginx/HTTPS, служебные порты закрыты наружу, healthcheck /healthz, лимиты ресурсов и ротация логов. Предыдущий релиз сохраняется для проверяемого отката.
