- inline.py: open_subscription_link/subscription_connect callbacks now include
:{subscription_id} suffix in multi-tariff mode. Main menu uses subscription_connect
(picker) instead of bare open_subscription_link.
- guest_purchase_service: activate_purchase non-tariff path uses proper ordering
(non-daily, max days_left) instead of arbitrary _active[0]
- monitoring_service: _send_expired_day1_notification and discount notification
keyboards use se:{subscription.id} in multi-tariff (2 more hardcoded callbacks fixed)
- admin/tariffs: delete_tariff_confirmed now checks active subscription count
before deletion (RESTRICT FK). Prompt shows blocking message when active subs exist.
New CRUD function get_active_subscriptions_count_by_tariff_id.
Inactive tariffs with is_trial_available=True can now be used for trial
activation across bot, miniapp, and cabinet. This enables dedicated trial
tariffs with custom limits (traffic, devices, servers) without exposing
them in the regular purchase flow. Paid trial paths now properly resolve
trial tariff parameters instead of using global settings defaults.
Add a per-tariff visibility flag (show_in_gift) that controls whether
a tariff appears in the /gift section. Enforced server-side in gift
config query, gift purchase endpoint, and landing page gift purchases.
Includes Alembic migration with idempotency guard and server_default.
- Add external_squad_uuid column to Tariff model with Alembic migration
- Add external_squad_uuid parameter to RemnaWave API create_user/update_user
- Pass external squad from tariff to RemnaWave on subscription creation/update
- Sync external squad in monitoring service, sync service, admin user management
- Clear external squad when tariff has none (consistent across all call sites)
- Add GET /available-external-squads endpoint with UUID validation and response model
- Update tariff schemas with UUID pattern validation
- Fix db.refresh to include tariff relationship for async safety
- Add ContextVarsMiddleware for automatic user_id/chat_id/username binding
via structlog contextvars (aiogram) and http_method/http_path (FastAPI)
- Use bound_contextvars() context manager instead of clear_contextvars()
to safely restore previous state instead of wiping all context
- Register ContextVarsMiddleware as outermost middleware (before GlobalError)
so all error logs include user context
- Replace structlog.get_logger() with structlog.get_logger(__name__) across
270 calls in 265 files for meaningful logger names
- Switch wrapper_class from BoundLogger to make_filtering_bound_logger()
for pre-processor level filtering (performance optimization)
- Migrate 1411 %-style positional arg logger calls to structlog kwargs
style across 161 files via AST script
- Migrate log_rotation_service.py from stdlib logging to structlog
- Add payment module prefixes to TelegramNotifierProcessor.IGNORED_LOGGER_PREFIXES
and ExcludePaymentFilter.PAYMENT_MODULES to prevent payment data leaking
to Telegram notifications and general log files
- Fix LoggingMiddleware: add from_user null-safety for channel posts,
switch time.time() to time.monotonic() for duration measurement
- Remove duplicate logger assignments in purchase.py, config.py,
inline.py, and admin/payments.py
- tickets.py: guard against non-text messages in waiting_for_title FSM state
- payments.py: fix Wata webhook using wrong field name (order_id vs orderId),
add full payload to error log
- tariff.py: stop overwriting admin tariff settings on every bot restart,
sync_default_tariff_from_config now only creates if no tariff exists
- start.py: catch TelegramBadRequest specifically for "message is not modified"
instead of bare except with useless retry
- admin/tickets.py: downgrade ticket notification log from error to warning
for expected case of OAuth/email users without telegram_id
- pricing.py, countries.py, purchase.py: guard against expired FSM state
causing KeyError on 'period_days'
- blacklist_service.py: add 5-min in-memory cache to is_user_blacklisted()
to reduce DB load from per-request checks
- remnawave_service.py: fix "Session is closed" race condition — create
new RemnaWaveAPI instance per get_api_client() call instead of reusing
shared instance whose aiohttp session gets overwritten by parallel coroutines
Add PUT /cabinet/admin/tariffs/order endpoint for drag-and-drop
tariff sorting in admin cabinet. Move db.commit() from CRUD to
route level for consistency.
- 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
- Introduced fields for custom days and traffic in the tariff model, including enabling flags, pricing, and limits.
- Updated relevant routes and schemas to handle new tariff features.
- Implemented logic for purchasing and managing custom days and traffic in subscriptions.
- Added database migration scripts to accommodate new columns for tariffs and subscriptions.
- Реализована возможность докупки трафика для тарифов с новыми параметрами: traffic_topup_enabled, traffic_topup_packages и max_topup_traffic_gb.
- Обновлены схемы и маршруты для управления тарифами и трафиком.
- Добавлены новые эндпоинты для работы с докупкой трафика в мини-приложении.
- Обновлены настройки и логика для проверки доступности докупки трафика в зависимости от тарифа.
- Внедрены улучшения в обработку платежей через Freekassa.
Обновлён .env.example с новыми параметрами для режима тарифов.
- Обновлены схемы и маршруты для поддержки покупки тарифов и управления трафиком.
- Реализована синхронизация тарифов и серверов из RemnaWave при запуске.
- Добавлены новые параметры в тарифы: server_traffic_limits и allow_traffic_topup.
- Обновлены настройки и логика для проверки доступности докупки трафика в зависимости от тарифа.
- Внедрены новые эндпоинты для работы с колесом удачи и обработка платежей через Stars.
Обновлён .env.example с новыми параметрами для режима продаж подписок.