ci / build-test (push) Canceled after 0s
SaaS-мониторинг Telegram: ядро (модули Cards/Kanban/Pipeline/Tenants/Settings/ Discovery, Api, Infrastructure), сервисы telegram/ai/ml/storage, фронт Vue, контракты и grpc-hosting, деплой-конфиги (dev/prod/observability/CI-раннер), Gitea Actions CI, документация (ТЗ, техдок, api-map, код-стайл, планы, бэклог). Текущее состояние: все этапы роадмапа 0–12 закрыты, сборка 5 sln 0/0, тесты 1340/130/52/38/9 зелёные.
85 lines
5.4 KiB
Markdown
85 lines
5.4 KiB
Markdown
# Task 2 — Отчёт: BanGuard (квоты, паузы, flood)
|
||
|
||
Статус: **DONE**
|
||
|
||
## Что сделано по шагам
|
||
|
||
### Step 1: Модуль `backend/app/services/ban_guard.py` (создан)
|
||
Реализован по интерфейс-спеке брифа (в брифе «дословного» кода нет — только сигнатуры
|
||
и поведение, см. Concerns #1):
|
||
|
||
- `joins_today_auto() -> int` — `count(*)` из `disc_log` по `event='join_auto'`
|
||
с `created_at >= начало текущих UTC-суток` (`datetime.now(timezone.utc)
|
||
.replace(hour=0,minute=0,second=0,microsecond=0)` → ms).
|
||
- `can_auto_join() -> bool` — `joins_today_auto() < discJoinLimit` И `not flood_today()`
|
||
И `not global_paused()`.
|
||
- `async wait_join_delay() -> None` — `asyncio.sleep(random.uniform(discJoinDelayMin,
|
||
discJoinDelayMax))` (значения из `store.get_setting`).
|
||
- `note_flood() -> None` — `store.set_setting("discFloodDay", <start_of_day_ms>)`.
|
||
- `flood_today() -> bool` — `discFloodDay == start_of_day_ms` (вчерашний флуд-день
|
||
автоматически «протухает» в полночь UTC).
|
||
- `global_paused() -> bool` / `set_global_pause(v: bool) -> None` — setting `discPaused`.
|
||
- `search_pause() -> float` — `random.uniform(2.0, 4.0)`.
|
||
|
||
Детали реализации:
|
||
- Хелпер `_start_of_day_ms()` общий для квоты, флуда и паузы.
|
||
- Ключи настроек вынесены в модульные константы (`_KEY_*`) — в коде нет «голых» строк.
|
||
- `discPaused`/`discFloodDay` не в `DEFAULT_SETTINGS` (рантайм-настройки): `get_setting`
|
||
возвращает `None`, который трактуется как «не взведено» (`False`/`0`).
|
||
- Стиль модуля — как в соседних сервисах: `from ..db import store`, русские докстринги,
|
||
`from __future__ import annotations`.
|
||
|
||
### Step 2: Проверка на временной БД в контейнере
|
||
Первая попытка упала: `ImportError: cannot import name 'ban_guard'` — код копируется в
|
||
образ при сборке (`backend/Dockerfile`: `COPY backend/app ./app`), исходники не
|
||
монтируются, а образ был собран до создания файла. Выполнена пересборка
|
||
`docker compose build app` (2.6s, слой кода — единственный не из кэша), затем команда
|
||
из брифа прошла (вывод ниже).
|
||
|
||
## Изменённые файлы
|
||
- `backend/app/services/ban_guard.py` (создан)
|
||
|
||
## Вывод проверок
|
||
|
||
### 1. `py_compile`
|
||
```
|
||
$ cd /c/telbase && python -m py_compile backend/app/services/ban_guard.py
|
||
COMPILE_OK # без ошибок
|
||
```
|
||
|
||
### 2. Временная БД в контейнере (команда из брифа Step 2)
|
||
```
|
||
$ docker compose build app # необходимо, т.к. код запекается в образ
|
||
$ docker compose run --rm --no-deps -e PYTHONPATH=/srv -e LEADRADAR_DATA=/tmp/lr_bg --entrypoint python app -c "
|
||
from app.db import store; store.init()
|
||
from app.services import ban_guard as bg
|
||
assert bg.can_auto_join() is True
|
||
assert bg.joins_today_auto() == 0
|
||
bg.note_flood(); assert bg.flood_today() is True
|
||
bg.set_global_pause(True); assert bg.can_auto_join() is False
|
||
print('BANGUARD OK')
|
||
"
|
||
BANGUARD OK
|
||
```
|
||
Все 4 assert'а из брифа прошли. ✔
|
||
|
||
### Дополнительно
|
||
- Статическая проверка (diagnostics Zed): ошибок и предупреждений нет.
|
||
|
||
## Concerns
|
||
1. **«Весь код — в брифе» — фактически неверно**: в `task-2-brief.md` (и в плане)
|
||
нет ни одного блока кода модуля, только интерфейс-спека (~10 сигнатур с описанием
|
||
поведения) и команда проверки. «Транскрибировать дословно» было нечего; модуль
|
||
реализован по спеке. Очевидных ошибок/противоречий в спеке не нашёл — править было
|
||
нечего. Если планировался эталонный код — его нужно добавить в бриф.
|
||
2. `wait_join_delay()` и `search_pause()` не покрыты проверкой Step 2 (оба — случайные
|
||
паузы; дефолт паузы вступления 50–70 сек, поэтому в проверку они не входили).
|
||
Логика тривиальная (`random.uniform` + `asyncio.sleep`), но «живого» прогона нет.
|
||
3. `can_auto_join()` при `discJoinLimit <= 0` всегда `False` (осторожная сторона:
|
||
лимит 0 = «не вступать»).
|
||
4. Клампы пауз (5..600) независимы — `min > max` теоретически возможно через UI
|
||
(унаследованный concern из Task 1; в `wait_join_delay` тогда диапазон
|
||
«вывернется», но не упадёт). Перекрёстную проверку бриф не требовал.
|
||
5. Для проверок последующих задач нужна пересборка образа после каждого изменения
|
||
backend-кода (образ не монтирует исходники).
|