docs реклама за показы: заход 2 закрыт — кусок переписки готов, ход работ и снимок состояния
This commit is contained in:
@@ -606,3 +606,77 @@ v9.07. Чинить не в этой ветке.
|
||||
догоняющая миграция с `GRANT SELECT` по образцу
|
||||
`2026_07_27_100100_grant_admin_ad_campaign_banners.php`. 🪤 Без неё админский экран
|
||||
увидит ноль строк молча, без единой ошибки. Записано в CHANGELOG v9.10.
|
||||
|
||||
---
|
||||
|
||||
# Заход 2 закрыт 28.07.2026 — кусок 1 «переписка» готов целиком
|
||||
|
||||
Клиент теперь видит, за что Яндекс отклонил его рекламу, и отвечает прямо в портале.
|
||||
|
||||
## Что сделано, задача за задачей
|
||||
|
||||
**Задача 5 — ручка списка сообщений** (`5e5a9c7f`). `GET /api/advertising/campaigns/{id}/messages`
|
||||
отдаёт ленту своей кампании по порядку. Чужая — 404, без входа — 401.
|
||||
🪤 Красный был правильный не сразу очевидно: на незнакомый адрес отвечает страница сайта
|
||||
с кодом 200, поэтому в тестах проверяется содержимое, а не голый `assertOk()`.
|
||||
|
||||
**Задача 6 — ответ клиента с документом** (`27367245`). `POST .../messages` принимает
|
||||
текст и файл (pdf/jpg/png, до 10 МБ) на закрытый диск; `GET .../messages/{id}/file`
|
||||
отдаёт файл только своему тенанту.
|
||||
|
||||
**Задача 7 — экран переписки** (`31709d01`). `CampaignMessages.vue` встал в карточку
|
||||
кампании **вместо мёртвого списка объявлений**: он читал `ad_campaign_ads` — таблицу
|
||||
старой модели, которую поток «за показы» не заполняет, поэтому клиент всегда видел
|
||||
«Объявлений пока нет» рядом с красным ярлыком.
|
||||
|
||||
**Задача 8 — подпись под ярлыком** (`958204d2`). `index()` кампаний отдаёт
|
||||
`moderation_reason`, в списке под чипом «Отклонено» видна первая строка причины.
|
||||
|
||||
**Добор** (`fb63994a`). Отдельный тест на строку приёмочного листа «в переписке нет
|
||||
наценки»: ручка отдаёт строго перечисленные поля.
|
||||
|
||||
## Что проверено вырезанием
|
||||
|
||||
- Фильтр по тенанту в списке ленты → чужой клиент получил 200 вместо 404. ✅
|
||||
- Правила `mimes` и `max:10240` → покраснели оба файловых теста. ✅
|
||||
- Условие «пусто и без файла» → покраснел свой тест. ✅
|
||||
- Фильтр по тенанту в отдаче файла → покраснел свой тест. ✅
|
||||
- Перехват упавшей загрузки ленты → покраснел «упавшая загрузка не роняет экран». ✅
|
||||
- Подпись под ярлыком → покраснел «видна первая строка причины». ✅
|
||||
- Отдача модели целиком вместо перечня полей → покраснел «в ленте нет наценки». ✅
|
||||
|
||||
## Три находки, которых не было в плане
|
||||
|
||||
1. 🪤 **План забыл `ensureCsrfCookie()`** в отправке ответа. Все прочие POST'ы модуля
|
||||
её берут; без неё кнопка «Отправить» молча ломалась бы в браузере при зелёных тестах.
|
||||
2. 🪤 **Фильтр по тенанту на самом сообщении ничего не сторожит** — его вырезание
|
||||
оставило всё зелёным, потому что настоящий замок стоит на поиске кампании. Оставлен
|
||||
вторым ремнём, но доказуемая защита там одна.
|
||||
3. 🪤 **Диалог отчёта падал, если лента не загрузилась** — поймали существующие тесты
|
||||
фронта. Теперь упавшая лента пишет строчку и не роняет остальной отчёт.
|
||||
Заодно вскрылось: наивная отдача модели целиком утекла бы `file_path` — путь файла
|
||||
на боевом сервере.
|
||||
|
||||
## Приёмочный лист замысла §8, кусок 1 — закрыт целиком
|
||||
|
||||
| Строка | Чем закрыта |
|
||||
|---|---|
|
||||
| Пояснение Яндекса появляется в переписке само | `SyncCampaignModerationJobTest` (заход 1) |
|
||||
| Текст сохраняется целиком, длиннее 255 | `CampaignMessageServiceTest` |
|
||||
| Повторный опрос второго сообщения не создаёт | `CampaignMessageServiceTest` |
|
||||
| Клиент видит свою переписку и не видит чужую | `CampaignMessageEndpointsTest` |
|
||||
| Ответ с файлом, файл на закрытом диске | `CampaignMessageEndpointsTest` |
|
||||
| Файл отдаётся только своему тенанту | `CampaignMessageEndpointsTest` |
|
||||
| Не тот тип или тяжелее 10 МБ — 422, на диск ничего | `CampaignMessageEndpointsTest` |
|
||||
| Письмо и колокольчик на слова Яндекса | `CampaignMessageNotifyTest` (заход 1) |
|
||||
| Ярлык «Отклонено» показывает первую строку причины | `advertising-campaign-list.spec.ts` |
|
||||
| В переписке нет наценки | `CampaignMessageEndpointsTest` |
|
||||
|
||||
## Зелёная отметка на конец захода 2, коммит `fb63994a`
|
||||
|
||||
- портал: **322/322, 1090 проверок, ~54 с** (было 311/311 — 11 новых тестов);
|
||||
- фронт: **227 файлов, 1668 тестов** зелёные (единственная посторонняя ошибка —
|
||||
в чужом `autopodbor`, к этой работе отношения не имеет);
|
||||
- `npm run type-check` — те же **6 чужих** ошибок, ни одной новой;
|
||||
- робот: **60/60**;
|
||||
- вызовов снятия заморозки — **четыре**, пятого не появилось.
|
||||
|
||||
@@ -3,27 +3,28 @@
|
||||
Файл пишется в конце каждого захода. Читать его **первым делом** после компакта: всё,
|
||||
что нужно следующей сессии, лежит здесь, а не в переписке.
|
||||
|
||||
Обновлено: 28.07.2026, конец захода 1.
|
||||
Обновлено: 28.07.2026, конец захода 2.
|
||||
|
||||
---
|
||||
|
||||
## Где мы
|
||||
|
||||
- Worktree `.claude/worktrees/reklama-pokazy`, ветка `feat/reklama-yandex-pokazy`.
|
||||
- Последний коммит — **`4ddfa7b0`**.
|
||||
- **Заход 1 закрыт целиком** (задачи 1–4). Заходы 2, 3, 4 не начинались.
|
||||
- Последний коммит — **`fb63994a`**.
|
||||
- **Заходы 1 и 2 закрыты целиком** (задачи 1–8). **Кусок 1 замысла «переписка» готов.**
|
||||
Заходы 3 и 4 не начинались.
|
||||
- Никуда не пушили, на боевой не выкатывали, рубильник Директа выключен, в живой кабинет
|
||||
Яндекса никто не заходил.
|
||||
|
||||
Коммиты захода 1:
|
||||
Коммиты захода 2:
|
||||
|
||||
```text
|
||||
4ddfa7b0 feat реклама за показы: письмо клиенту и колокольчик на ответ Яндекса
|
||||
70b421e1 feat реклама за показы: джоб модерации кладёт пояснение Яндекса в ленту кампании целиком
|
||||
83753fc0 feat реклама за показы: сервис ленты сообщений и защита от повторов одной и той же причины
|
||||
9c332cc2 feat реклама за показы: лента сообщений по кампании — таблица и модель
|
||||
bae6b95f docs реклама за показы: план работ по отказам модерации — четыре захода с точками компакта
|
||||
bb39703e docs реклама за показы: замысел — окно передачи между Яндексом и клиентом по отказам модерации
|
||||
fb63994a test реклама за показы: в ленте не может быть наценки и пути файла на сервере
|
||||
958204d2 feat реклама за показы: под ярлыком Отклонено видна первая строка причины
|
||||
31709d01 feat реклама за показы: экран переписки в карточке кампании вместо мёртвого списка объявлений
|
||||
27367245 feat реклама за показы: ответ клиента с документом и отдача файла только своему тенанту
|
||||
5e5a9c7f feat реклама за показы: ручка списка сообщений кампании — только своя лента
|
||||
ffeffe35 docs реклама за показы: заход 1 закрыт — ход работ и снимок состояния
|
||||
```
|
||||
|
||||
Не закоммичено — только три устаревших промта (`chast4-v3`, `chast4-v4`,
|
||||
@@ -31,7 +32,11 @@ bb39703e docs реклама за показы: замысел — окно пе
|
||||
|
||||
## Зелёные числа на этом коммите
|
||||
|
||||
- портал: **311/311, 1054 проверки, ~51 с** — `cd app && DB_DATABASE=liderra_testing_reklama php artisan test --filter=Advertising`
|
||||
- портал: **322/322, 1090 проверок, ~54 с** — `cd app && DB_DATABASE=liderra_testing_reklama php artisan test --filter=Advertising`
|
||||
- фронт: **227 файлов, 1668 тестов** — `cd app && npx vitest run`
|
||||
🪤 единственная посторонняя ошибка в выводе — `autopodbor.ts::fetchProposalGroups`,
|
||||
чужая и давняя, к этой работе отношения не имеет.
|
||||
- `npm run type-check` — **6 чужих** ошибок, ни одной новой. Чужие не чинить.
|
||||
- робот: **60/60** — `cd bots/yandex-creatives && node --test`
|
||||
- мест снятия заморозки денег — **четыре**: `AdvertisingCampaignController:392`,
|
||||
`SyncCampaignModerationJob:165`, `PauseCampaignsOnAdStop:48`, `CampaignImpressionCharger:96`.
|
||||
@@ -39,45 +44,57 @@ bb39703e docs реклама за показы: замысел — окно пе
|
||||
|
||||
## Что теперь работает
|
||||
|
||||
Яндекс отклонил объявление → джоб опроса модерации кладёт пояснение модератора **целиком**
|
||||
в ленту кампании (`ad_campaign_messages`) → клиенту уходит письмо и загорается колокольчик.
|
||||
Повторный обход с тем же текстом второго сообщения не создаёт. Обрезанное до 255 знаков
|
||||
по-прежнему живёт в `ad_campaign_banners.moderation_reason` — оно для ярлыка.
|
||||
Круг «Яндекс → клиент → Яндекс» замкнут в одну сторону целиком:
|
||||
|
||||
**Клиент ленту пока не видит:** экранов и ручек ещё нет, это заход 2.
|
||||
1. Яндекс отклоняет объявление;
|
||||
2. джоб опроса модерации кладёт пояснение модератора **целиком** в `ad_campaign_messages`;
|
||||
3. клиенту уходит письмо и загорается колокольчик;
|
||||
4. клиент открывает карточку кампании и **видит переписку**, а в списке кампаний под
|
||||
красным ярлыком «Отклонено» — первую строку причины;
|
||||
5. клиент отвечает текстом и может приложить документ (pdf/jpg/png до 10 МБ);
|
||||
6. документ лежит на закрытом диске и отдаётся только своему тенанту.
|
||||
|
||||
## Что дальше — заход 2, задачи 5–8
|
||||
**Чего пока нет:** документ никуда не едет — робот его не носит (кусок 3), а кнопка
|
||||
«Исправить» по-прежнему ведёт в тупик (кусок 2, заход 3).
|
||||
|
||||
## Что дальше — заход 3, задачи 9–12
|
||||
|
||||
По плану `docs/superpowers/plans/2026-07-28-yandex-otkazy-okno-peredachi.md`:
|
||||
|
||||
- **Задача 5** — ручка списка сообщений `GET /api/advertising/campaigns/{id}/messages`.
|
||||
- **Задача 6** — ответ клиента с файлом: `pdf/jpg/png`, до 10 МБ, закрытый диск.
|
||||
- **Задача 7** — отдача файла только своему тенанту.
|
||||
- **Задача 8** — экраны: лента `CampaignMessages.vue` в карточке кампании и подпись
|
||||
под ярлыком «Отклонено» в списке кампаний.
|
||||
|
||||
К концу захода 2 кусок 1 замысла закрыт целиком.
|
||||
- **Задача 9** — `YandexDirectClient::deleteAds()`, удаление отклонённых объявлений.
|
||||
- **Задача 10** — `CampaignReviveService`: возврат кампании в черновик, обнуление номеров
|
||||
креатива и объявления у отклонённых баннеров; **номера кампании, группы и сегмента
|
||||
уцелевают** — на них держится возобновляемый запуск.
|
||||
- **Задача 11** — ручка «Исправить» и **узкое** исключение в замке правки: по статусу
|
||||
`rejected`, а не по «есть ли номер кампании», иначе исключение откроет правку
|
||||
и работающей рекламе.
|
||||
- **Задача 12** — кнопка «Исправить» на экране.
|
||||
|
||||
🔴 **Заход 4 не начинать, пока не снята живая разметка кабинета** (задача 13, вместе
|
||||
с владельцем). Код по воображаемым экранам даст зелёные тесты и мёртвую программу.
|
||||
|
||||
## Свежие грабли этого захода
|
||||
## Свежие грабли захода 2
|
||||
|
||||
- 🪤 **Тест-пустышка про «беду с лентой».** Первая редакция брала одну принятую кампанию
|
||||
и проходила одинаково с защитой и без неё. Настоящая беда — обрыв разбора кампании
|
||||
на первом баннере: остальные остаются без вердикта, деньги молча не возвращаются.
|
||||
Урок прежний: **защиту проверять вырезанием, а не глазами** — иначе тест зелёный,
|
||||
а защиты нет.
|
||||
- 🪤 **`crm_admin_user` не получил гранта** на `ad_campaign_messages`. Админского экрана
|
||||
по ленте нет, поэтому по least privilege грант не выдан. Когда появится список
|
||||
«ждёт разбора» — нужна догоняющая миграция `GRANT SELECT` по образцу
|
||||
- 🪤 **План забыл `ensureCsrfCookie()`** в отправке ответа клиента. Все прочие POST'ы
|
||||
модуля её берут; без неё кнопка «Отправить» молча ломалась бы в браузере при зелёных
|
||||
тестах. Проверять это на каждой новой мутации фронта.
|
||||
- 🪤 **Фильтр по тенанту на самом сообщении ничего не сторожит:** его вырезание оставило
|
||||
всё зелёным, потому что настоящий замок — на поиске кампании. Урок общий: вырезание
|
||||
показывает не только «защита есть», но и «эта защита ничего не держит».
|
||||
- 🪤 **Мутация фронта ломает чужие тесты не там, где ждёшь.** Встроенная в диалог отчёта
|
||||
лента уронила два чужих spec'а: их `vi.mock` не знал новых функций api-модуля.
|
||||
Диалог теперь переживает упавшую ленту, а специи получили заглушки.
|
||||
- 🪤 **Наивная отдача модели целиком утекает `file_path`** — путь файла на боевом
|
||||
сервере. Ручка обязана перечислять поля вручную; тест это ловит.
|
||||
- 🪤 **`\n` внутри heredoc-питона превращается в настоящий перенос строки** и ломает
|
||||
`.vue` разбором «Unterminated string constant». Такие строки править инструментом Edit.
|
||||
- 🪤 `crm_admin_user` гранта на `ad_campaign_messages` **не получил** — когда появится
|
||||
админский список «ждёт разбора», нужна догоняющая миграция `GRANT SELECT` по образцу
|
||||
`2026_07_27_100100_grant_admin_ad_campaign_banners.php`, иначе экран увидит ноль
|
||||
строк молча.
|
||||
- 🪤 В worktree нет `lefthook` — каждый коммит печатает `Can't find lefthook in PATH`.
|
||||
Это нормально; gitleaks гоняется руками:
|
||||
`"…/Документация/bin/gitleaks.exe" protect --staged --no-banner`.
|
||||
- 🪤 Помощник тестов джоба называется `makeModeratedCampaignWithBanners($adIds, $yandexCampaignId)`
|
||||
и уже есть в файле — своего писать не нужно. Http-заглушка ставится на `'*/json/v5/ads'`.
|
||||
|
||||
## Красные линии, которые действуют всегда
|
||||
|
||||
|
||||
Reference in New Issue
Block a user