# `modules/` — бизнес-модули Каждый модуль — вертикальный срез фичи, оформленный по одному шаблону: ``` modules// mod.rs # реэкспорт controller/service controller.rs # axum-роуты: парсинг запроса, вызов service, сериализация ответа service.rs # бизнес-логика; принимает/возвращает доменные типы, не знает про HTTP ``` `controller.rs` намеренно тонкий — там нет ветвлений бизнес-правил, только HTTP-контракт (DTO, статус-коды через `AppError`, middleware-гварды). Вся логика и все инварианты (гонки, лимиты, права) — в `service.rs`, который работает поверх [`db::repository::AppRepository`](../db/README.md) и ничего не знает об axum. Все сервисы `Clone` (обычно дешёвый клон `Arc`) и живут внутри [`ApiState`](../api.rs), который передаётся в роутер и в Telegram-бота — то есть один и тот же `BillingService`/`AuthService`/`UserService` обслуживает и веб, и бота. ## Модули | Модуль | Ответственность | README | |---|---|---| | [`auth`](auth/README.md) | Вход через Telegram Login Widget, анонимный Ghost Protocol (seed) и bootstrap-мост из бота; единая cookie-сессия (access + ротируемый refresh), guards | [подробнее](auth/README.md) | | [`billing`](billing/README.md) | Тарифы, инвойсы, оплата через TON, триал, реферальный revshare | [подробнее](billing/README.md) | | [`nodes`](nodes/README.md) | Control Plane VPN-нод: провижининг по SSH, публичные ручки для клиента/лендинга | [подробнее](nodes/README.md) | | [`referrals`](referrals/README.md) | Статистика по рефералам и применение промокодов | [подробнее](referrals/README.md) | | [`users`](users/README.md) | Профиль (агрегированный) и мини-игра Cyberhack | [подробнее](users/README.md) | ## Сквозные зависимости между модулями - `auth::process_login` при регистрации нового юзера дёргает `referrals::create_referral` напрямую через репозиторий (без сервиса) — реферальная связь создаётся сразу при первом входе по `ref_code`. - `billing::confirm_payment` после успешной оплаты сам начисляет реферальный бонус (`repo.reward_referrer`) — то есть логика вознаграждения за реферала физически лежит в billing, а не в referrals. - `nodes::controller::get_node_routing` читает `billing_service.get_subscription` из `ApiState`, чтобы не выдавать одноразовый session-токен ноды юзерам без активной подписки — единственная точка, где `nodes` смотрит на состояние другого модуля (через уже собранный стейт, не импортируя `billing::` напрямую).