Добавление всех наработок за период практики.

This commit is contained in:
SkyForces
2026-07-24 00:46:19 +03:00
parent 24943b8a73
commit ae4141ba28
56 changed files with 3987 additions and 136 deletions
+64
View File
@@ -0,0 +1,64 @@
# ADR-001: разделение приложения на сайт, Telegram-бот и базу данных
**Статус:** Принято
**Дата:** 2026-07-23
## Контекст
Приложение включает страницу привязки пользователя Битрикс24, Telegram-интерфейс
менеджера и хранилище интеграционных данных. HTTP-сайт обрабатывает короткие
входящие запросы, тогда как Telegram-бот выполняет длительный polling и
параллельные REST-операции.
## Решение
Архитектура разделена на три основные структурные единицы: сайт привязки,
Telegram-бот и база данных, которая обслуживает остальные компоненты. Зона
ответственности каждого модуля определена отдельно. Сайт и бот не делят общий
код, а их права на уровне базы данных ограничены.
HTTP-приложение `site` обслуживает только инициацию привязки и взаимодействует с
REST API и OAuth Битрикс24. Telegram-приложение `bot` обрабатывает команды
менеджера и взаимодействует как с REST API и OAuth Битрикс24, так и с Telegram
Bot API. PostgreSQL предоставляет обоим процессам устойчивый версионированный
контракт в виде `SECURITY DEFINER`-функций и поддерживает применение миграций.
Программное решение регистрируется администратором портала как локальное
приложение с указанием ссылки на страницу привязки. Локальное приложение
отправляет на HTTPS-адрес `/bitrix/bind` идентификационные данные пользователя и
refresh-токен. Сайт обменивает его на новую пару OAuth-токенов, извлекает
доверенные `member_id`, `user_id` и `client_endpoint` из ответа Битрикс24,
проверяет пользователя методом `user.current` и только после этого выпускает
одноразовую ссылку Telegram.
![UML-диаграмма компонентов программного решения](assets/report/architecture-components.png)
*Рисунок ADR-001/1. UML-диаграмма компонентов программного решения*
На схеме также обозначен сервис `migrate`. Он не является постоянно запущенным
модулем, но отвечает за миграции схемы БД. При перезапуске Docker Compose сервис
последовательно выполняет необходимые SQL-скрипты. Сайт имеет право выполнять
только функцию выпуска ссылки `binding.issue_v1`. Бот погашает ссылку, получает
привязку и обращается к OAuth-функциям. Прямые операции `SELECT`, `INSERT` и
`UPDATE` над таблицами для ролей приложений запрещены, поэтому граница базы
данных одновременно является границей доступа.
| Компонент | Ответственность | Внешний интерфейс |
|------------|----------------------------------------------------------|------------------------------------------|
| nginx | Завершение TLS и проксирование только к сайту | HTTPS → 127.0.0.1:8000 |
| apps.site | OAuth-проверка пользователя и выпуск одноразовой ссылки | POST /bitrix/bind, GET /health |
| apps.bot | Команды Telegram, карточки и операции со сделками | Telegram Bot API, Битрикс REST |
| PostgreSQL | Привязки, токены, транзакционная синхронизация | binding.\* и oauth.\* |
| migrate | Однократное применение SQL-миграций до старта приложений | db/migrations/\*.sql |
| Битрикс24 | Источник CRM-данных и OAuth-контекста | oauth/token, user.current, crm.\* |
| Telegram | Пользовательский канал и доставка callback-событий | getUpdates, sendMessage, editMessageText |
*Таблица ADR-001/1. Ответственность компонентов архитектуры*
## Последствия
Разделение уменьшает связанность и позволяет перезапускать или масштабировать
процессы независимо. Для коротких входящих запросов используется синхронный
Flask/Gunicorn, а для длительного polling и параллельных REST-операций —
asyncio/aiogram. Связь приложений формализована версионированными функциями
PostgreSQL вместо общего программного модуля.
+46
View File
@@ -0,0 +1,46 @@
# ADR-002: привязка пользователей Битрикс24 и Telegram
**Статус:** Принято
**Дата:** 2026-07-23
## Контекст
Локальное приложение CRM отправляет на HTTPS-адрес `/bitrix/bind`
идентификационные данные пользователя и refresh-токен. Идентификаторам портала и
пользователя из входной формы доверять нельзя: контекст должен быть получен от
OAuth-сервера Битрикс24 и подтверждён методом `user.current`.
## Решение
Сайт использует refresh-токен для получения новой OAuth-пары, доверенных
`member_id`, `user_id` и `client_endpoint`. После этого `BitrixClient` сверяет
`user_id` с результатом `user.current`.
Процесс привязки учётных записей представлен на диаграмме последовательности.
![Диаграмма последовательности привязки Битрикс24 к Telegram](assets/report/binding-sequence.png)
*Рисунок ADR-002/1. Диаграмма последовательности привязки Битрикс24 к Telegram*
После проверки пользователя функцией `secrets.token_urlsafe(32)` формируется
одноразовый токен привязки. В БД записывается только SHA-256-хеш, поэтому
компрометация базы не позволяет восстановить действующую ссылку. Срок жизни
задаётся переменной окружения `BINDING_TOKEN_TTL_SECONDS`, ограничен диапазоном
от 60 до 3600 секунд и по умолчанию равен 600 секундам. При повторном выпуске
прежние непогашенные токены того же пользователя отзываются.
Пользователь переходит по одноразовой ссылке в чат с Telegram-ботом. Бот
повторно вычисляет SHA-256-хеш и сверяет его с активными токенами. Если токен
существует, не истёк, не отозван и ещё не погашен, он помечается использованным,
а в таблице привязок создаётся или обновляется связь пользователя Битрикс24 с
аккаунтом Telegram.
Погашение выполняется только в личном чате. Проверка токена и изменение привязки
выполняются функцией `binding.consume_v1` в одной транзакции.
## Последствия
Привязка не использует идентификаторы пользователя из недоверенной входной
формы. В базе хранится только хеш одноразового токена, а повторный выпуск ссылки
отзывает предыдущие непогашенные токены. Атомарное погашение не позволяет двум
запросам одновременно использовать одну ссылку.
@@ -0,0 +1,53 @@
# ADR-003: защита и обновление OAuth-токенов
**Статус:** Принято
**Дата:** 2026-07-23
## Контекст
Для выполнения REST-запросов приложение хранит `access_token` и `refresh_token`.
Битрикс24 возвращает новую пару токенов при каждом обновлении, поэтому
одновременное использование одного refresh-токена несколькими воркерами может
привести к потере актуальной пары.
## Решение
До передачи в PostgreSQL `access_token` и `refresh_token` шифруются алгоритмом
Fernet. Общий `TOKEN_ENCRYPTION_KEY` передаётся контейнерам `site` и `bot` через
переменные окружения, но не записывается в базу. Бот расшифровывает access-токен
непосредственно перед REST-запросом и не включает OAuth-параметры в тексты
ошибок.
Принятые меры защиты сведены в таблицу.
| Риск | Реализованная мера | Остаточный контроль |
|---------------------------------|--------------------------------------------|--------------------------------------|
| Утечка одноразовой ссылки из БД | Хранение SHA-256-хеша | Короткий TTL и однократное погашение |
| Чтение OAuth-токенов из БД | Fernet-шифрование до INSERT/UPDATE | Секретный ключ вне БД |
| Подмена пользователя | member_id/user_id из OAuth + user.current | Проверка HTTPS endpoint |
| Гонка refresh token | Версия и аренда refresh_locked_until | Повторное чтение до версии N+1 |
| Избыточные права приложений | Разные роли и EXECUTE только на функции | REVOKE для PUBLIC |
| Долгая транзакция | Сетевые запросы выполняются вне транзакции | Короткие контексты Psycopg |
*Таблица ADR-003/1. Риски и меры защиты от них*
При получении `expired_token`, `invalid_token` или `no_auth_found` клиент
пытается обновить пару токенов. Поле `version` реализует оптимистическую
проверку, а `refresh_locked_until` — короткую аренду продолжительностью 30
секунд. Это предотвращает одновременное использование одного refresh-токена
несколькими воркерами.
После успешного обновления токенов воркер освобождает аренду. Если она занята,
другой воркер ожидает обновления, после чего повторяет обращение к API с новой
парой токенов.
![Диаграмма последовательности обновления OAuth-токена](assets/report/oauth-refresh-sequence.png)
*Рисунок ADR-003/1. Диаграмма последовательности обновления OAuth-токена*
## Последствия
Сетевой запрос к OAuth выполняется вне транзакции PostgreSQL. Версия и аренда
координируют обновление между воркерами, а повторное чтение позволяет продолжить
работу с версией `N+1`. Дальнейшее развитие механизма защиты предусматривает
ротацию ключей шифрования.
+61
View File
@@ -0,0 +1,61 @@
# ADR-004: слоистая организация Telegram-бота
**Статус:** Принято
**Дата:** 2026-07-23
## Контекст
Telegram-бот принимает команды и callback-запросы, проверяет привязку
пользователя, обращается к PostgreSQL и REST API Битрикс24, а затем формирует
HTML-сообщения и inline-клавиатуры.
## Решение
Бот организован по слоям: обработчики принимают события Telegram, middleware
добавляет проверенную привязку в контекст, сервисы реализуют прикладные
сценарии, `BitrixClient` отвечает за OAuth и HTTP, а классы представления
формируют HTML-тексты и inline-клавиатуры.
![UML-диаграмма основных классов решения](assets/report/bot-class-diagram.png)
*Рисунок ADR-004/1. UML-диаграмма основных классов решения*
Классы `Binding`, `OAuthCredentials`, `ClientInfo`, `DealStageFilter` и
`DealPage` являются dataclass-моделями передачи данных. Они отделяют словари
REST-ответов и строки БД от интерфейсов сервисов. `DealPage` дополнительно
вычисляет признак `has_next`, который используется при построении кнопок
пагинации.
Основные команды и callback-действия Telegram-бота приведены в таблице.
| Ввод | Обработчик | Результат |
|-----------------------------------------|---------------------------------|--------------------------------------------|
| /start, /help | StartBotHandlers.start | Справка или состояние привязки |
| /start bind_<token> | StartBotHandlers.bind | Погашение одноразовой ссылки в личном чате |
| /deals, /leads | DealBotHandlers.deals | Первая страница сделок начальной стадии |
| /deal ID, /lead ID | DealBotHandlers.deal_by_command | Карточка сделки по идентификатору |
| deals:page:<stage>:<page> | deals_page | Фильтрация по стадии и пагинация |
| deal:view:<id> | deal_by_button | Карточка выбранной сделки |
| deal:assign:<id>:<expected> | assign_responsible | Назначение текущего Битрикс-пользователя |
| deal:remind:<id> | remind_to_call | Создание дела на звонок через час |
| deal:history:<id> | show_history | Пять последних переходов по стадиям |
*Таблица ADR-004/1. Пользовательские команды и callback-действия*
Навигация по страницам списка, переход к карточке и возврат выполняются кнопками
клавиатуры с редактированием исходного сообщения бота. Список сделок также
поддерживает фильтрацию, которая задаётся дополнительными кнопками на странице
просмотра списка.
Перед выполнением CRM-команд `BindingRequiredMiddleware` ищет привязку по
Telegram user id. При успешной проверке объект `Binding` помещается в словарь
`data` и передаётся именованным параметром обработчика. Если привязки нет,
цепочка прерывается до REST-запроса, а пользователь получает инструкцию открыть
приложение в Битрикс24.
## Последствия
Конструкторы принимают зависимости явно, поэтому сервисы можно тестировать с
имитационными репозиториями и REST-клиентами. Проверка middleware ограждает
пользователя от ошибочного поведения и гарантирует наличие привязки перед
обращением к CRM.
+83
View File
@@ -0,0 +1,83 @@
# ADR-005: получение списка и карточки сделки из Битрикс24
**Статус:** Принято
**Дата:** 2026-07-23
## Контекст
Приложение не копирует CRM-данные в локальную базу. Сведения о сделках,
контактах, компаниях, стадиях и истории запрашиваются через REST API
непосредственно в момент действия пользователя. PostgreSQL хранит только данные,
необходимые для идентификации пользователя и выполнения авторизованных запросов.
Реализация работает с сущностью сделки и методами `crm.deal.*`. Команды `/leads`
и `/lead` используются как пользовательские псевдонимы `/deals` и `/deal`.
## Решение
Для получения и изменения данных применяются следующие методы REST API
Битрикс24.
| Метод | Назначение | Ключевые параметры |
|-----------------------|-------------------------------|----------------------------------------|
| crm.deal.list | Список и пагинация | filter, select, order, start |
| crm.deal.get | Карточка и контроль состояния | id |
| crm.deal.update | Ответственный и стадия | id, fields, REGISTER_HISTORY_EVENT |
| crm.status.list | Стадии воронки и источники | ENTITY_ID, STATUS_ID |
| crm.contact.get | ФИО и телефон контакта | id |
| crm.company.get | Название и телефон компании | id |
| crm.stagehistory.list | История переходов | entityTypeId=2, OWNER_ID |
| crm.activity.todo.add | Отложенный звонок | ownerTypeId=2, deadline, responsibleId |
*Таблица ADR-005/1. Используемые методы REST API Битрикс24*
### Формирование списка
Названия стадий не зашиты в интерфейсе. Метод `crm.status.list` получает
актуальную конфигурацию воронки, после чего первая стадия трактуется как
псевдофильтр `new`. Карта стадий кэшируется в памяти на 300 секунд отдельно для
портала, пользователя и категории. Дополнительно добавляется фильтр «Все», не
передающий `STAGE_ID` в Битрикс24.
Размер страницы Telegram равен пяти сделкам, тогда как Битрикс24 может
возвращать другое количество элементов за запрос. `DealService` собирает
REST-страницы по полю `next` до тех пор, пока не сможет выделить диапазон
`[page * limit; page * limit + limit)`. Значение `total` используется для
расчёта общего числа страниц. Список сделок запрашивается методом
`crm.deal.list` с параметрами фильтрации.
![Список сделок в интерфейсе Telegram](assets/report/deal-list-ui.png)
*Рисунок ADR-005/1. Список сделок в интерфейсе Telegram*
### Формирование карточки
Получение карточки сделки продолжает сценарий работы со списком.
![Диаграмма последовательности просмотра списка и карточки сделки](assets/report/deal-list-card-sequence.png)
*Рисунок ADR-005/2. Диаграмма последовательности просмотра списка и карточки
сделки*
Карточка загружается методом `crm.deal.get`, затем обогащается данными связанных
сущностей. Для контакта составляется ФИО и выбирается первый телефон; при
отсутствии телефона контакта проверяется компания. Идентификаторы источника и
стадии преобразуются в человекочитаемые названия. В итоговое сообщение
включаются сумма, валюта, ответственный, дата создания и комментарий.
Все динамические строки перед включением в HTML-ответ Telegram проходят
`html.escape`. Длина карточки ограничена 3900 символами, что оставляет запас до
ограничения Telegram и предотвращает ошибку отправки из-за длинного комментария.
Кнопка назначения отображается только для новой сделки, если текущий
пользователь ещё не является ответственным.
![Карточка сделки в интерфейсе Telegram](assets/report/deal-card-ui.png)
*Рисунок ADR-005/3. Карточка сделки в интерфейсе Telegram*
## Последствия
Битрикс24 остаётся источником актуальных CRM-данных, а локальная база не требует
синхронизации сделок и связанных сущностей. В качестве дальнейшего развития
предусмотрен переход с устаревающих методов `crm.deal.*` на универсальные методы
`crm.item.*`.
+60
View File
@@ -0,0 +1,60 @@
# ADR-006: изменение сделки с проверкой актуального состояния
**Статус:** Принято
**Дата:** 2026-07-23
## Контекст
Состояние сделки может измениться в Битрикс24 после формирования карточки в
Telegram, но до нажатия callback-кнопки. Используемый метод `crm.deal.update` не
предоставляет условный `UPDATE`, поэтому перед изменением требуется проверить,
что показанное пользователю состояние остаётся актуальным.
## Решение
### Назначение ответственного и изменение стадии
Callback-кнопка назначения содержит не только id сделки, но и `ASSIGNED_BY_ID`,
который был показан пользователю:
`deal:assign:<deal_id>:<expected_responsible_id>`. Перед изменением сервис
повторно загружает сделку и сравнивает фактического ответственного с ожидаемым.
Если карточка устарела, REST-обновление не выполняется.
Внутри одного процесса операции по паре `(member_id, deal_id)` последовательно
выполняются под `asyncio.Lock`. После `crm.deal.update` сервис повторно читает
сделку и убеждается, что ответственным стал `bitrix_user_id` привязанного
пользователя.
![Диаграмма последовательности взятия сделки в работу](assets/report/deal-assignment-sequence.png)
*Рисунок ADR-006/1. Диаграмма последовательности взятия сделки в работу*
При обновлении одновременно передаются `ASSIGNED_BY_ID` связанного пользователя,
рабочая `STAGE_ID` и параметр `REGISTER_HISTORY_EVENT=Y`. После REST-запроса
выполняется контрольное чтение сделки.
### Планирование звонка и просмотр истории
Действие «Позвонить позже» создаёт в Битрикс24 дело типа `todo` с крайним сроком
через один час. Владельцем является сделка (`ownerTypeId=2`), а ответственным —
связанный пользователь Битрикс24. Массив `pingOffsets=[0]` включает напоминание
в момент наступления срока.
История загружается методом `crm.stagehistory.list` с фильтром `OWNER_ID` и
сортировкой по убыванию идентификатора. В интерфейс выводятся первые пять
событий. Для каждого события идентификатор стадии преобразуется в название с
учётом `CATEGORY_ID`, после чего `DealFormatter` формирует защищённый HTML-текст
и клавиатуру возврата к карточке или списку.
![Диаграмма последовательности планирования звонка и просмотра истории](assets/report/reminder-history-sequence.png)
*Рисунок ADR-006/2. Диаграмма последовательности планирования звонка и просмотра
истории*
## Последствия
Локальный `asyncio.Lock` защищает только один процесс `bot`. При горизонтальном
масштабировании на несколько контейнеров потребуется распределённая блокировка
либо серверная условная операция. Повторная проверка REST-результата сохраняет
защиту от внешних изменений, но не делает два удалённых вызова одной транзакцией
Битрикс24.
+46
View File
@@ -0,0 +1,46 @@
# ADR-007: развёртывание приложения с помощью Docker Compose
**Статус:** Принято
**Дата:** 2026-07-23
## Контекст
Программное решение состоит из PostgreSQL, сервиса миграций, Telegram-бота и
Flask-сайта. База данных должна быть готова до запуска приложений, а
SQL-миграции должны выполняться последовательно с сохранением истории
применения.
## Решение
Для развёртывания используется Docker Compose из четырёх основных сервисов:
`db`, `site`, `bot` и `migrate`. Сервисы базы данных и миграций используют
готовые образы на основе Alpine Linux. Модули приложения собираются с помощью
Dockerfile на базе среды выполнения Python 3.13 и включают необходимые
библиотеки.
Первым запускается контейнер `db` с PostgreSQL, который также импортирует
первичные настройки ролей. Контейнер PostgreSQL не публикует порт на хост и
остаётся доступным только внутри сети Compose.
Затем запускается контейнер `migrate`. Он последовательно применяет SQL-скрипты
из `db/migrations` и сохраняет историю их применения в таблице
`public.schema_migrations`. Для каждой миграции хранится контрольная сумма. Если
уже применённый файл был изменён, выполнение завершается с ошибкой.
После успешного завершения миграций запускаются Telegram-бот и Flask-сайт.
Условиями их запуска являются нормальное состояние контейнера базы данных и
успешное завершение контейнера `migrate`.
Сайт доступен только через loopback-адрес `127.0.0.1`. Внешний nginx принимает
HTTPS-трафик и передаёт заголовки `X-Forwarded-*`. Бот не имеет входящего порта
и получает обновления методом long polling.
Контейнеры приложения выполняются от системного пользователя `runtime`, а не от
`root`.
## Последствия
Миграции выполняются до запуска прикладных процессов. PostgreSQL не публикуется
наружу, сайт доступен извне только через HTTPS-прокси, а Telegram-бот не требует
входящего сетевого порта. Изменение уже применённой миграции обнаруживается по
несовпадению контрольной суммы.
+70
View File
@@ -0,0 +1,70 @@
# ADR-008: хранение интеграционных данных и разграничение доступа
**Статус:** Принято
**Дата:** 2026-07-23
## Контекст
Основное назначение базы данных — хранение авторизационных данных, токенов
привязки, OAuth-токенов и связей между пользователями Битрикс24 и Telegram.
Сделки, контакты, компании, стадии и история остаются в Битрикс24 и в локальной
базе не дублируются.
## Решение
Модель хранения нормализована вокруг портала Битрикс24.
![Схема базы данных](assets/report/database-schema.png)
*Рисунок ADR-008/1. Схема базы данных*
| Таблица | Ключевые данные | Назначение и ограничения |
|------------------------|-------------------------------------------------|-------------------------------------------------------|
| binding.portals | member_id, domain | Справочник порталов; member_id уникален |
| binding.tokens | token_hash, expires_at, consumed_at, revoked_at | Одноразовые ссылки; токен хранится только как хеш |
| binding.user_bindings | portal_id, bitrix_user_id, telegram_user_id | Однозначная привязка пользователей в пределах портала |
| oauth.user_credentials | access_token, refresh_token, version, lock | Зашифрованные OAuth-данные и координация обновления |
*Таблица ADR-008/1. Назначение таблиц базы данных*
Поле `member_id` является устойчивым внешним идентификатором портала, а числовой
`bitrix_user_id` имеет смысл только вместе с `portal_id`. Связи с `portals`
используют `ON DELETE CASCADE`: удаление портала автоматически удаляет его
ссылки, привязки и OAuth-данные. Составной первичный ключ
`oauth.user_credentials(portal_id, bitrix_user_id)` исключает две конкурирующие
записи учётных данных одного пользователя. Поля `consumed_at` и `revoked_at`
разделяют два независимых основания недействительности одноразовой ссылки.
Для работы с данными в PostgreSQL созданы две роли: `site_role` и `bot_role`.
Они имеют разные права доступа к хранимым функциям и не могут редактировать
таблицы напрямую.
| Функция | Вызывающая роль | Назначение |
|-----------------------------|-----------------|-------------------------------------------------------------------------------------|
| binding.issue_v1 | site_role | Сохранить OAuth-данные, отозвать старые ссылки и выпустить новую в одной транзакции |
| binding.consume_v1 | bot_role | Однократно погасить ссылку и создать привязку |
| binding.find_by_telegram_v1 | bot_role | Получить актуальную привязку Telegram |
| oauth.get_credentials_v1 | bot_role | Получить учётные данные связанного пользователя |
| oauth.claim_refresh_v1 | bot_role | Получить короткую аренду на обновление токена |
| oauth.finish_refresh_v1 | bot_role | Атомарно записать новую пару при совпадении версии |
| oauth.release_refresh_v1 | bot_role | Освободить аренду после ошибки |
*Таблица ADR-008/2. Контракт хранимых функций PostgreSQL*
Сайт использует роль `site_role` и может вызывать только функцию привязки.
Telegram-бот использует `bot_role` и может вызывать функции погашения токенов,
получения привязки и работы с OAuth. Роль `site_role` не имеет доступа к схеме
`oauth` и не может читать сохранённые токены; роль `bot_role` не может выпускать
новые ссылки от имени сайта.
Все функции объявлены `SECURITY DEFINER` и фиксируют `search_path` в
`pg_catalog`, что уменьшает риск подмены объектов. Права `PUBLIC` на таблицы и
функции отозваны.
## Последствия
Конкурирующие вызовы `binding.consume_v1` для одного хеша не смогут одновременно
пройти условие `consumed_at IS NULL`: `UPDATE` блокирует строку, а после
завершения первой транзакции второй вызов видит уже установленное время
погашения. Проверка и изменение не разделены между приложением и базой, поэтому
отсутствует окно гонки между `SELECT` и `UPDATE`.
Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 213 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

+38 -8
View File
@@ -1,13 +1,43 @@
# Архитектурные решения (ADR)
# Архитектурные решения BitrixDealsBot
## Введение
В ходе производственной практики разработано серверное приложение
BitrixDealsBot, которое предоставляет менеджеру по продажам интерфейс Telegram
для работы со сделками CRM Битрикс24. Разработанное решение не копирует
CRM-данные в локальную базу: все сведения о сделках, контактах, компаниях,
стадиях и истории запрашиваются через REST API непосредственно в момент действия
пользователя. PostgreSQL хранит только данные, необходимые для идентификации
пользователя Битрикс24 и его привязки к пользователю Telegram-бота, включая
OAuth-токены.
Данный раздел содержит архитектурные решения (ADR) для проекта.
Архитектурные решения описывают ключевые решения, принятые в процессе разработки
системы, включая выбор технологий, подходов и структурных решений.
В индивидуальном задании используется термин «лид», однако реализация работает с
сущностью сделки и методами `crm.deal.*`. Команды `/leads` и `/lead` сохранены
как пользовательские псевдонимы `/deals` и `/deal`, поэтому интерфейс остаётся
совместимым с формулировкой задания, а в документации используется технически
точное понятие «сделка».
## Оглавление
## Технологический стек
**В данном разделе представлены следующие архитектурные решения:**
| Уровень | Технология | Назначение |
|---------------|---------------------------|--------------------------------------------------------------------------|
| Язык | Python 3.13 | Серверная логика сайта и Telegram-бота |
| Telegram | aiogram 3 | Асинхронная маршрутизация команд, callback-запросов и опроса сервера |
| HTTP-сервер | Flask 3 + Gunicorn | Страница привязки пользователя Битрикс24 к конкретному аккаунту Telegram |
| Запросы к API | httpx | Синхронные и асинхронные запросы к OAuth и REST API Битрикс24 |
| Хранилище | PostgreSQL 17 + Psycopg 3 | Транзакции, хранимые функции и пулы соединений |
| Защита | Fernet + SHA-256 | Шифрование OAuth-токенов и хеширование одноразовых ссылок |
| Развёртывание | Docker Compose + nginx | Изоляция процессов, миграции, HTTPS и обратное проксирование |
_(В процессе разработки будут добавляться новые решения)_
*Таблица 1. Технологический стек решения*
## Состав группы ADR
| ADR | Архитектурное решение | Статус |
|----------------------------------------------|-----------------------------------------------------------|---------|
| [ADR-001](001-apps-and-database.md) | Разделение приложения на сайт, Telegram-бот и базу данных | Принято |
| [ADR-002](002-oauth-telegram-binding.md) | Привязка пользователей Битрикс24 и Telegram | Принято |
| [ADR-003](003-oauth-credential-lifecycle.md) | Защита и обновление OAuth-токенов | Принято |
| [ADR-004](004-bot-layers.md) | Слоистая организация Telegram-бота | Принято |
| [ADR-005](005-bitrix-deal-read-model.md) | Получение списка и карточки сделки из Битрикс24 | Принято |
| [ADR-006](006-guarded-deal-mutations.md) | Изменение сделки с проверкой актуального состояния | Принято |
| [ADR-007](007-container-deployment.md) | Развёртывание приложения с помощью Docker Compose | Принято |
| [ADR-008](008-data-storage-and-access.md) | Хранение интеграционных данных и разграничение доступа | Принято |