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 зелёные.
This commit is contained in:
@@ -0,0 +1,84 @@
|
||||
# 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-кода (образ не монтирует исходники).
|
||||
Reference in New Issue
Block a user