- Transfer negative balances on merge (debt must not vanish)
- Validate OAuth state was initiated for account linking flow
- Transfer secondary's referrer to primary when primary has none
- Type MergePreviewSubscription schema (replace dict[str, Any])
- Cap restore_merge_token TTL to prevent clock-skew extension
- Add 4 new tests (negative balance, referrer transfer scenarios)
- Add restore_merge_token() to re-store consumed token if execute_merge
or db.commit fails, allowing the user to retry instead of being stuck
- Fix partner_status priority: PENDING (2) now beats REJECTED (1), so
an active application is not lost during merge
- Add tests for pending-vs-rejected edge cases (47 tests total)
Prevents data corruption when merging accounts that have mutual referral
relationships. Cross-referral ReferralEarning rows are now deleted before
any bulk UPDATE to avoid self-referral records. Secondary's referred_by_id
is cleared during cleanup to prevent orphaned FK references.
- Add User.id != primary.id filter to referred_by_id reassignment to
prevent self-referral loops when primary was referred by secondary
- Clear primary.referred_by_id if it pointed to secondary
- Add exclusion filter to ReferralEarning.referral_id reassignment to
prevent user_id == referral_id rows
- Invalidate refresh tokens for BOTH primary and secondary during merge
(primary gets a fresh session after merge)
- Fix duplicate step 4 comment numbering in execute_merge_endpoint
- Add referred_by_id field to test fixture _make_user
- Rename _compute_auth_methods to compute_auth_methods (public API)
- Add Literal type to _handle_subscription_merge param
- Add Literal type to keep_from in route handler
- Add Path(min_length=32, max_length=64) on merge_token params
- Import Path and Literal in account_linking routes
- Clear ALL unique constraint fields on secondary user after merge
(telegram_id, OAuth IDs, email, referral_code, remnawave_uuid)
- Add Literal type + runtime validation for keep_subscription_from
- Reject merge when primary user is deleted
- Validate OAuth state user_id matches authenticated user in link callback
- Replace leaked ValueError messages with generic error detail
- Fix exc_info usage for idiomatic structlog
- Fix _get_remnawave_api return type to AsyncIterator
- Remove unnecessary from __future__ import annotations
- Add 3 new tests (42 total, all passing)
Add OAuth provider linking/unlinking endpoints, merge token service
(Redis-backed, 30-min TTL), and atomic account merge executor that
transfers OAuth IDs, telegram_id, email, balance, subscriptions,
transactions, payments, referral data, and partner status between
two user accounts. Unchosen subscription is deleted from RemnaWave
with disable as fallback.
Includes 39 unit tests covering all merge scenarios.
- Multi-channel subscription enforcement via middleware, events, and cabinet API
- 3-layer cache architecture: Redis -> PostgreSQL -> rate-limited Telegram API
- ChatMemberUpdated event-driven tracking with automatic VPN access control
- Admin management via bot FSM handler and REST API with full CRUD
- Channel ID normalization: @username resolved to numeric ID at creation time
- Fail-closed error handling: API errors deny access (security-first)
- Background reconciliation with keyset pagination (100 per batch)
- Per-user rate limiting on subscription check button (5s cooldown)
- Redis connection pooling via cache singleton (no per-request connections)
- Database: channel_id index, multi-row upsert optimization
- Localization: en, ru, zh, fa, ua translations for all new strings
- Frontend blocking UI with channel list and subscription status
- Admin channel management page with toggle, delete, and create
Remove all modem purchase/management code:
- Delete modem handler, service, and tests
- Remove modem button from keyboards and admin panel
- Remove modem pricing from calculations
- Remove modem REST API endpoint and schemas
- Remove modem decorator, config settings, and notification formatting
- Keep DB column and migration for backwards compatibility
Add last_webhook_update_at timestamp to Subscription model. When a webhook
handler modifies a subscription, it stamps this field. Auto-sync, monitoring,
and force-check services skip subscriptions updated by webhook within the
last 60 seconds, preventing stale panel data from overwriting fresh
real-time changes.
- Add last_webhook_update_at column + migration
- Stamp all 8 webhook handlers with commit in every code path
- Add is_recently_updated_by_webhook() guard in 12 sync/monitoring paths
- Add REMNAWAVE_WEBHOOK_* variables to .env.example
- Add webhook setup documentation to README with Caddy/nginx examples
- Fix pre-existing yookassa webhook test (mock AsyncSessionLocal)
Remove AUTO_ACTIVATE_AFTER_TOPUP and SHOW_ACTIVATION_PROMPT_AFTER_TOPUP
features from all payment providers, config, system settings, and tests.
Cart auto-purchase (AUTO_PURCHASE_AFTER_TOPUP) is preserved.
Bug fixes:
- fix KeyError 'months' in devices.py for custom locale overrides
- fix IntegrityError on trial subscription retry (update existing PENDING instead of INSERT)
- fix PendingRollbackError cascade by adding db.rollback() before recovery
- fix TelegramForbiddenError not caught in photo_message.py
- fix "query is too old" spam in required_sub_channel_check
- add missing trial locale keys (TRIAL_PAYMENT_DESCRIPTION, TRIAL_REFUND_DESCRIPTION, TRIAL_ACTIVATION_ERROR)
- Add pyproject.toml with uv and ruff configuration
- Pin Python version to 3.13 via .python-version
- Add Makefile commands: lint, format, fix
- Apply ruff formatting to entire codebase
- Remove unused imports (base64 in yookassa/simple_subscription)
- Update .gitignore for new config files
Проблема: у некоторых пользователей реферальный код из deep link терялся,
потому что pending_start_payload сохранялся только в FSM state, который
мог быть недоступен (state=None) в edge cases.
Исправления:
- Добавлен Redis fallback для хранения payload (TTL 1 час)
- _capture_start_payload() теперь сохраняет в FSM state И в Redis
- cmd_start() и required_sub_channel_check() проверяют Redis если FSM state
пуст
- Добавлено логирование warning при state=None
- Изменён уровень лога успешного сохранения с debug на info
Изменённые файлы:
- app/middlewares/channel_checker.py — Redis-функции и улучшенное логирование
- app/handlers/start.py — Redis fallback в обработчиках
Добавлены тесты:
- tests/middlewares/test_channel_checker_payload.py (14 тестов)
Новый функционал:
- Быстрая проверка (TRAFFIC_FAST_CHECK_*) — отслеживает дельту трафика за интервал через snapshot
- Суточная проверка (TRAFFIC_DAILY_CHECK_*) — анализирует трафик за 24 часа через bandwidth API
- Фильтрация по нодам (TRAFFIC_MONIT
- Добавлен get_user_by_id в импорты
- Перезагрузка user через get_user_by_id после subtract_user_balance
- Восстановление связи user_promo_groups, сбрасываемой после db.refresh() в payment-сервисах
- Добавлен мок get_user_by_id в тесте
Рефакторинг архитектуры управления модемом:
- Создан сервис app/services/modem_service.py:
- ModemService с бизнес-логикой подключения/отключения
- ModemError enum для типизации ошибок
- ModemPriceInfo, ModemOperationResult dataclass'ы
- Константы MODEM_WARNING_DAYS_* для уровней предупреждений