Commit Graph

108 Commits

Author SHA1 Message Date
c0mrade 565c08366b feat: add Remnawave panel 2.7.0 API support
- Add MONTH_ROLLING traffic reset strategy (enum, UI, mapping)
- Fix squad remove-users HTTP method POST → DELETE
- Add forceRestart param to restart_all_nodes (API, service, routes)
- Replace removed /bandwidth-stats/nodes/realtime with /system/nodes/metrics
- Fix parse_bytes suffix matching bug (B was catching GB/MB/KB/TB)
- Add torrent_blocker.report webhook event with admin notifications
- Add subpage_config_changed → auto-invalidate app config cache
- Pass webhook meta field to handlers (notConnectedAfterHours)
- Improve CRM/login webhook formatting (providerName, loginAttempt)
- Update bandwidth display: remove /s suffix, show inbound traffic totals
2026-03-28 12:34:15 +03:00
c0mrade a12ffb1d6c fix: delete subscription from RemnaWave panel + prevent phantom webhook notifications
- Replace disable_remnawave_user() with delete_remnawave_user() on subscription deletion
  so the panel stops sending webhooks for deleted subscriptions
- Add early return in all webhook handlers when subscription is None (already deleted from DB):
  expired, disabled, enabled, limited, traffic_reset, revoked, expiring reminders
- Add "Delete subscription" button in Telegram bot for expired/disabled subscriptions
  with confirmation step and full cleanup (panel delete + server counts + DB hard delete)
2026-03-26 15:59:36 +03:00
Fringg c6bedc6a06 fix: multi-tariff Stage 2 HIGH fixes — 18 issues across 12 files
Bot handlers (H1-H5):
- confirm_extend_subscription: error alert instead of wrong sub fallback
- open_subscription_link/subscription_connect: startswith registration
- handle_subscription_settings: multi-tariff guard
- confirm_reset_traffic: FSM state check in multi-tariff

Services (H6-H12):
- subscription_service: 5 UUID fallback fixes — no user.remnawave_uuid in
  multi-tariff, return None if subscription.remnawave_uuid missing
- auto_purchase: use cart subscription_id for tariff match
- remnawave_service: migrate_squad_users checks subscription.remnawave_uuid
- campaign_service: extend existing sub or create new in multi-tariff
- broadcast_service: check ALL subs for paid-subscription guard
- blocked_users_service: remnawave_uuids list, iterate in cleanup
- user_service: log sub.remnawave_uuid in multi-tariff

Admin (H13-H16):
- grant_trial/paid_subscription: allow in multi-tariff mode
- promo_offers: pick sub with URL, aggregate squads from all subs

CRUD/Frontend (H17-H18):
- get_users_list: .unique() for outerjoin dedup
- refreshTraffic: withSubId in params instead of body
2026-03-26 08:09:07 +03:00
c0mrade d87fb47e88 fix: multi-subscription UUID resolution and ownership validation
- Add _resolve_panel_uuid helper for per-subscription UUID in multi-tariff mode
- Add user ownership validation (user_id check) to all subscription queries
- Add unique partial index on (user_id, tariff_id) for active subscriptions
- Generate remnawave_short_id for new subscriptions in all creation paths
- Fix trial endpoints to check all user subscriptions, not just first
- Fix channel member handler to enable/disable per-subscription UUIDs
- Fix channel checker middleware for multi-subscription iteration
- Fix tariff switch, traffic, and device endpoints to use correct panel UUID
- Fix monitoring, auto-purchase, renewal services for multi-subscription
- Fix user_service, miniapp, subscriptions and users webapi routes
2026-03-23 18:45:46 +03:00
c0mrade d071269b8c fix: comprehensive multi-subscription audit fixes across routes, handlers, and services
- Fix UUID resolution in monitoring and webhook services for multi-tariff mode
- Update cabinet routes to properly resolve per-subscription UUIDs
- Fix account merge service for multi-tariff subscription transfers
- Update admin handlers (users, promo_offers, servers) for multi-subscription
- Fix traffic, devices, servers, daily subscription modules
- Update payment handlers (stars, yookassa) and purchase services
- Fix broadcast, promocode, and subscription auto-purchase services
- Add multi-subscription support to keyboards and localization
- Fix CRUD operations for subscription queries
- Resolve code quality issues (ruff linting)
2026-03-23 18:40:23 +03:00
c0mrade 18f31c565c fix: validate_and_clean_subscription uses per-subscription UUID in multi-tariff mode, not user-level UUID 2026-03-23 18:40:23 +03:00
c0mrade 335be66980 feat: multi-subscription support (1 user = N subscriptions)
- Add MULTI_TARIFF_ENABLED feature flag for gradual rollout
- Migration 0041: remove unique constraint on subscriptions.user_id
- Migration 0042: add remnawave_short_id (NOT NULL, UNIQUE) to subscriptions
- Each subscription gets its own Remnawave user (user_{tg_id}_{short_id})
- Add _resolve_subscription() to all 30+ bot handlers for per-subscription routing
- Add my_subscriptions.py with list/detail views and delegation handlers
- Refactor cabinet subscription.py (4687 lines -> 10 focused modules)
- Add subscription_id parameter to all cabinet API endpoints
- Adapt all services: autopay, wheel, contests, campaign, guest purchase,
  monitoring, blocked users, account merge, promo codes, payments
- Replace all user.subscription (singular) with user.subscriptions iteration
- IDOR protection via get_subscription_by_id_for_user on all endpoints
- Full backward compatibility: MULTI_TARIFF_ENABLED=False = legacy behavior
2026-03-23 18:37:17 +03:00
Fringg 48265f1cd4 chore: remove redundant comments from DISABLED status fix 2026-03-21 09:07:43 +03:00
Fringg 3b9568fcc1 style: format long lines in monitoring and subscription services 2026-03-21 09:06:01 +03:00
Fringg 79cfcbcece fix: send DISABLED instead of EXPIRED status to RemnaWave API
RemnaWave API only accepts ACTIVE/DISABLED for user status updates —
EXPIRED and LIMITED are managed internally. The bot was sending EXPIRED
status and past expireAt dates, causing 400 validation errors.

- Change UserStatus.EXPIRED → UserStatus.DISABLED in all 5 call sites
- Add 1-minute buffer to expire_at for inactive subscriptions to avoid
  "expiration date in the past" rejections (matches _safe_expire_at_for_panel)
- Include TRIAL status in is_actually_active checks (consistent with
  remnawave_service.py sync_users_to_panel)
2026-03-21 09:00:59 +03:00
Fringg 3d68db0a51 fix: не пересылать externalSquadUuid в рутинных обновлениях RemnaWave
Стейловый externalSquadUuid (c6c0a338-062d-4d3a-826d-7015a24d681c) из тарифа
не существует в таблице ExternalSquads панели → FK violation → A039.
Теперь externalSquadUuid отправляется только при sync_squads=True (создание подписки).
2026-03-16 03:47:13 +03:00
Fringg 4aaf0ddd25 fix: не пересылать activeInternalSquads в рутинных обновлениях RemnaWave (A039)
Стейловые UUID сквадов в connected_squads вызывали FK violation в RemnaWave → A039.
- update_remnawave_user: добавлен параметр sync_squads (default=False)
- Сквады шлются только при явном sync_squads=True (promo_offer, countries)
- monitoring_service: убрана пересылка сквадов в рутинном sync
- Расширен лог PATCH payload для диагностики
2026-03-16 03:41:56 +03:00
Fringg e453521098 fix: устранена отправка externalSquadUuid=null в RemnaWave API (A039) и исправлен reduce_devices
- reduce_devices: убрано молчаливое проглатывание ошибки RemnaWave, теперь при неудаче делается rollback и возвращается HTTP 502
- Убрана отправка external_squad_uuid=None в 8 местах: subscription_service, monitoring_service, remnawave_service, admin/users, cabinet/admin_users
2026-03-16 03:25:24 +03:00
Fringg 8d3cd50098 refactor: централизация всех расчётов цен в PricingEngine
- Мигрирован confirm_purchase() на calculate_classic_new_subscription_price()
- Мигрирован compute_simple_subscription_price на делегацию в PricingEngine
- Мигрирован handle_custom_confirm на calculate_tariff_purchase_price()
- Мигрированы daily confirm handlers (confirm_daily_tariff_purchase,
  confirm_daily_tariff_switch, confirm_instant_switch daily path)
- Мигрирован gift.py на calculate_tariff_purchase_price()
- Мигрированы FSM cache prices (select_period, select_devices, toggle_country)
- Добавлен lock_user_for_pricing в admin_buy_tariff_execute (TOCTOU fix)
- Добавлен lock + recompute в _auto_add_devices и _auto_add_traffic
- Исправлено двойное применение promo-offer в simple_subscription (критический баг)
- Унифицирован daily price display (group+offer) на всех 6 поверхностях
- PricingEngine.get_addon_discount_percent: добавлен promo_group= kwarg
- PricingEngine._calculate_switch_to/from_daily: добавлен promo-offer discount
- Удалён мёртвый код из common.py (_get_addon_discount_percent_for_user)
- Miniapp period_discounts: исправлен доступ через get_discount_percent()
2026-03-16 03:10:22 +03:00
Fringg f80912e444 fix: убрана отправка externalSquadUuid=null в RemnaWave API и исправлен ложный лог синхронизации рулетки
- Не отправляем externalSquadUuid: null — RemnaWave отвечал 500 (A039)
- Проверяем результат update_remnawave_user вместо ложного " синхронизировано"
2026-03-15 17:34:31 +03:00
Fringg 04697fd4cb style: ruff format 8 files 2026-03-13 05:53:02 +03:00
Fringg c9f2dffabf fix: address 6-agent review findings for PricingEngine
H1: log error when tariff_id set but tariff relationship not loaded
H2: warn on CLASSIC_PERIOD_PRICES→PERIOD_PRICES fallback
M1: fix float division in miniapp tariff purchase (use PricingEngine.apply_discount)
M2: fix format_period Russian pluralization for teen-hundreds (111-119, etc.)
M3: deduplicate _resolve_discount_percent — import from pricing_utils
M4: fix N+1 queries in compute_simple_subscription_price (batch fetch)
M5: add period_days validation tests (negative, zero, float)
M6: add user=None tests for tariff and classic modes
M7: fix float division in calculate_prorated_price (use // instead of /)
L1: add context to _calculate_servers_price error log
L2: add comment clarifying ClassicBreakdown.group_discount_pct type
L3: add test for original_total property
L4: inline _apply_percentage_discount wrapper in subscription_purchase_service
L5: replace global _server_id_counter with itertools.count() in tests
2026-03-13 05:45:46 +03:00
Fringg 08bea704de fix: address review findings from 5-agent audit
- Add period_days validation (> 0) in PricingEngine
- Add int() cast for tariff period_prices (prevent JSON type errors)
- Fix structlog.get_logger(__name__) in pricing_engine
- Use pricing.original_total property instead of manual reconstruction
- Add CryptoBot price decrease audit logging
- Remove stale cart price fallback in auto-purchase (fail instead)
- Fix _apply_promo_discount_for_tariff to use PricingEngine.apply_discount
- Remove dead code: _get_tariff_price_for_period, _get_countries_price,
  calculate_addon_price_with_remaining_period, _resolve_addon_discount_percent
2026-03-13 05:11:35 +03:00
Fringg c9a9816daa refactor: remove dead pricing code and fix miniapp classic mode
- Remove SubscriptionService.calculate_renewal_price (zero callers, replaced by PricingEngine)
- Remove SubscriptionService.calculate_renewal_price_with_months (zero callers)
- Remove _calculate_subscription_renewal_pricing wrapper in miniapp (zero callers)
- Fix miniapp classic mode: pass PricingEngine result directly to finalize() instead of old wrapper
- Fix potential NameError: pricing_snapshot in cryptobot path used undefined 'pricing' variable
- Net: -396 lines of duplicate pricing logic
2026-03-12 23:12:59 +03:00
firewookie fd3466b75c fix PR problems 2026-03-10 14:29:29 +05:00
Fringg bc9003c336 chore: ruff format 2026-03-10 03:36:32 +03:00
Fringg fcdeff1ee5 fix: migrate pricing to days-based proration, fix promo revenue leaks, fix admin panel bugs
- Migrate all addon pricing (devices, traffic, countries) from months-based to days-based proration
- Remove get_remaining_months() utility, use days_left / 30 consistently
- Fix promo/campaign balance bonuses counted as revenue in reports (add REAL_PAYMENT_METHODS filter)
- Fix partner stats, campaign stats, miniapp stats, referral fraud detection promo deposit leaks
- Fix admin balance history showing deductions with + sign (use -abs for expense types)
- Fix promo offer deactivation returning 400 for non-promocode offers
- Fix daily tariff renewal requesting 30-day renewal instead of 1-day purchase
2026-03-10 03:31:07 +03:00
Fringg 4a5cacda38 fix: resolve concurrent AsyncSession bug and sanitize error responses
- Fix critical concurrency issue in propagate_tariff_squads: preload
  users/tariffs before asyncio.gather, use single API client, no DB
  operations inside gather, single commit after all API calls
- Replace all str(e) leaks in admin_users.py with sanitized messages
- Fix double callback.answer by using callback.message.answer for
  failure alerts
- Move PropagateSquadsResult to module level, use field(default_factory)
- Compute traffic_strategy once before gather instead of N times
- Add warning logging on tariff refresh failures
- Reset synced counters on commit failure for accurate reporting
2026-03-09 02:26:38 +03:00
Fringg 79161eaae4 refactor: move squad propagation to service layer with parallel Remnawave sync
- Move _propagate_squads_to_subscriptions from handler to
  SubscriptionService.propagate_tariff_squads()
- Use asyncio.gather with semaphore (concurrency=5) for parallel
  Remnawave API calls instead of sequential O(N)
- Track failed subscription IDs for better observability
- Fix get_all_server_squads limit=50 default in admin handlers
  (now limit=10000 to prevent silent truncation)
- Add docstring to force_panel_delete parameter
- Return PropagateSquadsResult dataclass with total/synced/failed_ids
2026-03-09 01:58:23 +03:00
Fringg 9ba61a0879 feat: add telegram gift notification with inline activation button
- New gift_activation handler for gift_activate:{id} callback buttons
- Send Telegram notification to gift recipients with activate button
- Add skip_notification param to activate_purchase to prevent duplicates
- Fix telegram username regex minimum length (4→5 chars) in landing routes
- Add BOT_TOKEN guard in telegram gift notification sender
- Pre-resolve notification params before commit to avoid DetachedInstanceError
2026-03-07 20:39:04 +03:00
Fringg c10d6780ba feat: add external squad support for tariffs
- 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
2026-03-07 05:44:19 +03:00
Fringg d86c29a5d3 fix: preserve connected_squads during subscription replacement cleanup
Root cause: validate_and_clean_subscription() wiped connected_squads=[]
before create_remnawave_user() could send them to the Remnawave panel.
This caused replaced subscriptions to lose squad (server) assignments.

Also: add update_server_counters=True to all guest purchase flows,
commit tariff_id before create_remnawave_user to ensure fresh ORM state.
2026-03-06 22:28:39 +03:00
Fringg dce9eaa597 fix: reset traffic purchases on expired subscription renewal + pricing fixes
- Reset TrafficPurchase records and purchased_traffic_gb when renewing
  expired subscriptions (was incorrectly preserving stale purchases,
  inflating traffic limit e.g. 100GB+20GB=120GB instead of fresh 100GB)
- Fix in extend_subscription() CRUD, cabinet /renew, bot handlers,
  simple_subscription handlers
- Add RemnaWave sync to cabinet /renew endpoint after subscription changes
- Fix device_price_kopeks=0 falsy-zero bug (11+ instances, or → is not None)
- Fix double-increment of purchased_traffic_gb in cabinet traffic purchase
- Fix orphaned TrafficPurchase records in 5 locations (replace_subscription,
  extend_subscription fixed_with_topup, classic mode, switch_tariff,
  purchase.py is_traffic_fixed)
- Fix admin_users.py UnboundLocalError from TrafficPurchase inline import
  shadowing module-level import
- Standardize pricing order: base + devices → promo_group → promo_offer
  across all 5+ pricing paths (cabinet, miniapp, autopay, auto-purchase,
  monitoring)
- Fix exception handlers in calculate_renewal_price (raise instead of
  returning fallback 0)
- Fix monitoring_service double-discount (promo_offer applied twice)
- Fix auto-purchase _get_tariff_price_for_period return type to tuple
  (base_price, discount_percent) so callers add devices before discount
- Pass traffic_limit_gb/device_limit to extend_subscription in
  simple_subscription.py instead of manual overwrites
2026-03-04 04:46:29 +03:00
Fringg 59f0e42be7 fix: prevent squad drop on admin subscription type change, require subscription for wheel spins
- Fix active_internal_squads sent unconditionally as [] clearing Remnawave squads
- Fix dead code in _change_subscription_type (was_trial saved before mutation)
- Block wheel spins for users without active subscription (API + bot handler)
- Add has_subscription field to wheel config response
- Refund Stars to balance if spin payment arrives without subscription
- Fix SQL injection in promocode lookup (f-string → parameterized query)
- Remove redundant get_or_create_wheel_config call in stars handler
2026-02-27 00:53:46 +03:00
Fringg d4c4a8a211 fix: add missing broadcast_history columns and harden subscription logic
- Add migration 0006 for blocked_count, channel, email_subject,
  email_html_content columns missing from broadcast_history table
- Fix infinite trial reactivation loop in monitoring service
- Prevent webhook from overwriting freshly extended end_date
- Use tariff-specific pricing for auto-renewal instead of global config
2026-02-23 19:07:59 +03:00
Fringg eb18994b7d fix: complete datetime.utcnow() → datetime.now(UTC) migration
- Migrate 660+ datetime.utcnow() across 153 files to datetime.now(UTC)
- Migrate 30+ datetime.now() without UTC to datetime.now(UTC)
- Convert all 170 DateTime columns to DateTime(timezone=True)
- Add migrate_datetime_to_timestamptz() in universal_migration with SET LOCAL timezone='UTC' safety
- Remove 70+ .replace(tzinfo=None) workarounds
- Fix utcfromtimestamp → fromtimestamp(..., tz=UTC)
- Fix fromtimestamp() without tz= (system_logs, backup_service, referral_diagnostics)
- Fix fromisoformat/isoparse to ensure aware output (platega, yookassa, wata, miniapp, nalogo)
- Fix strptime() to add .replace(tzinfo=UTC) (backup_service, referral_diagnostics)
- Fix datetime.combine() to include tzinfo=UTC (remnawave_sync, traffic_monitoring)
- Fix datetime.max/datetime.min sentinels with .replace(tzinfo=UTC)
- Rename panel_datetime_to_naive_utc → panel_datetime_to_utc
- Remove DTZ003 from ruff ignore list
2026-02-17 04:45:40 +03:00
Fringg 1f0fef114b refactor: complete structlog migration with contextvars, kwargs, and logging hardening
- 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
2026-02-16 09:18:12 +03:00
Fringg ee2e79db31 refactor: remove modem functionality from classic subscriptions
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
2026-02-11 21:14:08 +03:00
Fringg 165965d8ea fix: add email/UUID fallback for OAuth user panel sync
OAuth users registering via cabinet have no telegram_id, causing
panel sync failures. All RemnaWave panel lookups now use a 3-level
chain: UUID → telegram_id → email. Also pass email and user_id to
format_remnawave_username to generate unique panel usernames.
2026-02-08 19:55:34 +03:00
Egor 2992dfbada Update subscription_service.py 2026-01-31 16:58:01 +03:00
Egor 82d24eea5b Update subscription_service.py 2026-01-26 20:26:23 +03:00
Egor 45edbd53d0 Add files via upload 2026-01-25 10:35:18 +03:00
c0mrade 9a2aea038a chore: add uv package manager and ruff linter configuration
- 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
2026-01-24 17:45:27 +03:00
Egor b576cb4486 Add files via upload 2026-01-23 11:22:31 +03:00
Egor 5563314718 Add files via upload 2026-01-21 15:41:33 +03:00
Egor c21804f8db Add files via upload 2026-01-19 23:45:53 +03:00
Egor fd947bc562 Update subscription_service.py 2026-01-16 08:36:37 +03:00
PEDZEO 0e24a5505c feat(subscription): добавлены новые функции для управления тарифами и трафиком
- Обновлены схемы и маршруты для поддержки покупки тарифов и управления трафиком.
- Реализована синхронизация тарифов и серверов из RemnaWave при запуске.
- Добавлены новые параметры в тарифы: server_traffic_limits и allow_traffic_topup.
- Обновлены настройки и логика для проверки доступности докупки трафика в зависимости от тарифа.
- Внедрены новые эндпоинты для работы с колесом удачи и обработка платежей через Stars.

Обновлён .env.example с новыми параметрами для режима продаж подписок.
2026-01-12 07:41:10 +03:00
gy9vin d524088bb8 Обязательная подписка на канал Доработка 2026-01-02 16:14:40 +03:00
gy9vin e15728e369 Fix простой покупки 2026-01-01 22:47:40 +03:00
Egor aa3c9231b0 Update subscription_service.py 2025-12-30 18:07:39 +04:00
Egor 40d6514dee Update subscription_service.py 2025-12-25 10:59:35 +03:00
Egor b0bf5131d4 Update subscription_service.py 2025-12-25 09:09:12 +03:00
Egor ee6f4b35b0 Update subscription_service.py 2025-12-22 15:29:02 +03:00
Egor f8ef2b9f5a Revert "Add limited Happ cryptolink support" 2025-12-15 11:12:14 +03:00