7974 Commits

Author SHA1 Message Date
Fringg 91de6d03fc fix: update cabinet_last_login on every request (throttled, 5 min) 2026-04-29 06:46:02 +03:00
Fringg 1fc04d842f fix: subscription-request-history — correct API client usage, add ownership check 2026-04-29 06:18:32 +03:00
Fringg e22beb7229 feat: subscription request history API + RemnaWave panel method
- Add get_subscription_request_history to RemnaWave API client
  (GET /api/users/{uuid}/subscription-request-history with pagination)
- Add GET /admin/users/{user_id}/subscription-request-history endpoint
  with subscription_id param for multi-tariff support
2026-04-29 06:12:38 +03:00
Fringg 74999fe99d fix: create locales directory with correct permissions in Dockerfile 2026-04-29 05:55:21 +03:00
Fringg 134e7fb0e1 fix: false subscription expiry notifications — 4 bugs fixed
1. _check_expired_subscription_followups: added Subscription.status=EXPIRED
   filter (was matching ALL statuses including ACTIVE), User.status=ACTIVE
   filter, and 30-day lookback window to stop scanning ancient subscriptions

2. _get_expiring_paid_subscriptions: added User.status=ACTIVE filter to
   prevent sending "expiring" notifications to blocked/deleted users

3. Multi-tariff: before sending expired/followup notifications, check if
   user has another ACTIVE subscription with end_date > now — skip if they
   still have service through another tariff

4. Multi-tariff: same check for _check_expired_subscriptions — don't send
   "subscription expired" if user has another active sub
2026-04-29 05:47:56 +03:00
Fringg c743fc81a5 fix: replace all late callback.answer() with edit_text for error feedback
- Fix 7 intermediate error paths (balance deduction failures) that used
  callback.answer() after the early answer was already consumed — user
  got no error feedback at all
- Fix 2 unfixed handlers: confirm_tariff_purchase, confirm_daily_tariff_purchase
  — same early-answer pattern applied
- All 7 purchase/extend/switch handlers now consistently use early
  callback.answer() + edit_text for errors
2026-04-29 05:37:30 +03:00
Fringg 579e4f2a69 fix: callback.answer() before heavy operations to prevent query timeout
Telegram invalidates callback queries after 30 seconds. When the bot
performed panel sync, DB transactions, and admin notifications before
answering, callback.answer() threw TelegramBadRequest: query is too old.

Moved callback.answer() to immediately after guard checks (balance,
tariff availability) in 5 handlers:
- confirm_tariff_extend
- confirm_custom_tariff_purchase
- confirm_tariff_switch
- confirm_daily_tariff_switch
- confirm_instant_switch

Error feedback now uses callback.message.edit_text() instead of the
expired callback.answer().
2026-04-27 16:56:39 +03:00
Fringg b9b695799c refactor: remove unused EXTERNAL_ADMIN_TOKEN functionality
- Delete app/services/external_admin_service.py entirely
- Remove EXTERNAL_ADMIN_TOKEN and EXTERNAL_ADMIN_TOKEN_BOT_ID from config
- Remove build_external_admin_token, get_external_admin_token, get_external_admin_bot_id methods
- Remove unused hashlib/hmac imports from config.py
- Remove from system_settings_service: READ_ONLY_KEYS, PLAIN_TEXT_KEYS,
  category title, category description, prefix mapping, documentation metadata
- Remove from bot_configuration.py category group
- Remove from main.py startup sequence (ensure_external_admin_token call)
- Remove from .env.example
- Remove from docs/project_structure_reference.md
2026-04-26 19:54:33 +03:00
Fringg 5cf19c76e6 fix: backup import crash + upload handler hardening
- Fix PaypearPayment → PayPearPayment (capital P) — import crash
- Fix AurapayPayment → AuraPayPayment (capital P) — import crash
- Update upload instruction message to mention .tar.gz format
- Add null guard on document.file_name before extension check
2026-04-26 19:39:09 +03:00
Fringg eafb243882 fix: backup completeness — add 15 missing tables, accept .tar.gz uploads
Tables added to backup AND clear lists:
- Payment providers: riopay, severpay, paypear, rollypay, overpay, aurapay, saved_payment_methods
- Content: email_templates, info_pages, news_articles, news_categories, news_tags
- Landing: landing_pages, guest_purchases
- Analytics: yandex_client_id_map

Also:
- Telegram backup upload handler now accepts .tar.gz format (was .json/.json.gz only)
- All 92 ORM models + 3 association tables now covered
2026-04-26 19:29:55 +03:00
Egor 56b0b1fb5f Merge pull request #2914 from BEDOLAGA-DEV/release-please--branches--main
chore(main): release 3.52.1
v3.52.1
2026-04-24 18:22:53 +03:00
github-actions[bot] 10519bf68e chore(main): release 3.52.1 2026-04-24 15:22:37 +00:00
Egor 7bff56070c Merge pull request #2913 from BEDOLAGA-DEV/dev
Dev
2026-04-24 18:22:09 +03:00
Fringg 5ed9a0d4fb fix: use fresh DB session for deactivate after long unpin loop 2026-04-24 18:13:48 +03:00
Fringg 63e1127353 fix: broadcast preview count — add .correlate(User) to EXISTS subqueries 2026-04-24 18:03:29 +03:00
Fringg ab4661b5c6 fix: unpin messages in Telegram BEFORE deactivating in DB
The "Unpin all" button called deactivate_active_pinned_message() first,
then looped over users to unpin. If Telegram API calls failed or timed
out, the message was already marked inactive in the DB with no way to
retry. Now: get active message → unpin from all chats → deactivate in DB.
2026-04-24 18:01:52 +03:00
Fringg 52bf2a9589 fix: ignore bot's own messages in unknown message handlers 2026-04-24 17:55:37 +03:00
Egor 14e24a546e Merge pull request #2911 from BEDOLAGA-DEV/release-please--branches--main
chore(main): release 3.52.0
v3.52.0
2026-04-24 17:12:13 +03:00
github-actions[bot] f60231e104 chore(main): release 3.52.0 2026-04-24 14:11:54 +00:00
Egor 920bab5b4f Merge pull request #2910 from BEDOLAGA-DEV/dev
Dev
2026-04-24 17:11:17 +03:00
Fringg 68d2350dfd fix: stop printing tracebacks for warning-level logs inside except blocks 2026-04-24 17:09:20 +03:00
Fringg 7d512d214a fix: integrate Yandex Metrika offline conv + S2S postback hooks
Restore integration hooks dropped in PR #2851 merge:
- PurchaseRequest accepts yandex_cid, referrer, subid from frontend
- Cache yandex_cid and subid in Redis at purchase creation (24h TTL)
- On fulfill_purchase: extract subid from cache, persist to DB
- Save Yandex CID from Redis to yandex_client_id_map
- Fire on_registration + S2S postback for new accounts
- Fire on_purchase + S2S postback for all paid purchases
- All hooks wrapped in try/except — failures never block delivery
2026-04-24 16:59:27 +03:00
Fringg 2cde38c63b fix: restore referrer field in admin landing purchases response 2026-04-24 16:53:47 +03:00
Fringg 24dc8d2a5e fix: restore HTTP Referer fallback for landing purchases 2026-04-24 16:47:22 +03:00
Fringg 1522d35f2d fix: gift purchases no longer inflate promo group level
Two bugs caused max promo group assignment on gift send/activate:

1. Buyer: GIFT_PAYMENT was counted in get_user_total_spent_kopeks
   alongside SUBSCRIPTION_PAYMENT. Now only SUBSCRIPTION_PAYMENT
   counts as personal spending for promo group auto-assignment.

2. Recipient: fulfill_purchase and activate_purchase created a
   SUBSCRIPTION_PAYMENT transaction for the recipient with the full
   gift price. Now skipped for gift recipients — they didn't pay.
2026-04-24 16:43:32 +03:00
Fringg 2b0d8a2a88 style: ruff format admin_bulk_actions.py 2026-04-24 16:34:43 +03:00
Fringg 9217f4116f fix: sanitize error messages in all bulk action catch-all handlers
Replace str(exc) with generic 'Action failed: internal error' in both
_execute_for_user and _execute_for_subscription catch-all blocks.
Prevents leaking internal paths, SQL details, or connection strings.
2026-04-24 16:24:45 +03:00
Fringg 2e45a93bd7 fix: bulk delete_user — pass real admin_id, sanitize error messages
- Thread admin_id through _execute_for_user to _do_delete_user for audit trail
- Replace raw exception str(e) with generic error message in client response
- Add subscriptions=[] to failure paths to prevent MissingGreenlet
2026-04-24 16:16:50 +03:00
Fringg d77fd81e16 feat: bulk actions — campaign/partner filters, delete_user action
- Add campaign_id and partner_id query params to GET /admin/users
- Filter users by advertising campaign via EXISTS subquery on registrations
- Filter users by partner via JOIN campaign registrations → campaigns
- Add DELETE_USER bulk action type with delete_from_panel param
- Handler calls UserService.delete_user_account for full bot+panel removal
- Permission check: users:delete required for delete_user action
- Add to _USER_LEVEL_ACTIONS (operates on user_ids, not subscription_ids)
2026-04-24 16:07:14 +03:00
Fringg bdb8cab1c9 feat: info page tab replacement — replaces_tab field + API
- Add replaces_tab column to InfoPage ('faq','rules','privacy','offer')
- Migration 0067: add nullable replaces_tab column
- CRUD: clear_replaces_tab ensures one page per tab, get_tab_replacements
  returns {tab: slug} mapping for active pages
- Admin routes: auto-clear old assignment on create/update
- Public route: GET /info-pages/tab-replacements (no auth)
- Schemas: replaces_tab with regex validation in all request/response models
2026-04-24 14:21:32 +03:00
Fringg d394565fe9 feat: FAQ support in info pages — page_type field + migration
- Add page_type column to InfoPage model ('page' or 'faq')
- Migration 0066: ALTER TABLE ADD COLUMN with server_default='page'
- Update schemas with page_type field and regex validation
- Update CRUD: page_type in create, filter in list
- Update admin/public routes with page_type query filter
- Backward compatible: existing pages default to type 'page'
2026-04-24 14:01:18 +03:00
Fringg 122d12db20 fix: /reorder route unreachable — move before /{page_id} path param 2026-04-24 13:50:38 +03:00
Fringg 2071a680d3 fix: info pages review — deduplicate slug index, type reorder items
- Remove triple-redundant slug index: keep only unique=True on column
  (PostgreSQL creates unique index automatically), remove __table_args__
  index and explicit create_index in migration
- Type ReorderRequest.items with ReorderItem(id: int, sort_order: int)
  instead of raw dict — prevents unvalidated input causing 500
- Migration downgrade: just drop_table (unique constraint drops with it)
2026-04-24 08:21:41 +03:00
Fringg e4b4a54797 feat: information pages — CRUD model, admin API, public API
- InfoPage model: slug, title (JSONB locale dict), content (JSONB),
  is_active, sort_order, icon, created_at/updated_at
- CRUD: create, get by id/slug, list, update, delete, reorder
- Admin routes: /admin/info-pages with full CRUD, toggle-active, reorder
  (permissions: settings:read/settings:edit)
- Public routes: /info-pages list active, /info-pages/{slug} get by slug
- Migration 0065: create info_pages table with unique slug index
- Custom pages support: admins can create any info page with any slug
2026-04-24 08:09:54 +03:00
Fringg 59c54c9b39 fix: privacy policy and offer text display HTML links as plain text
parse_mode='HTML' was missing from message sends during /start
registration. HTML tags like <a href="..."> were shown as literal
text instead of rendered links.

Fixed in 4 places:
- Privacy policy edit_text (line 1151)
- Privacy policy fallback answer (line 1158)
- Welcome/offer text in complete_registration_from_callback (line 1729)
- Welcome/offer text in complete_registration (line 2084)
2026-04-24 07:43:54 +03:00
Fringg 0d2b1dfdc9 feat: support multiple tariff_ids in user list filter
tariff_id query param now accepts comma-separated IDs (e.g.
tariff_id=1,3,5). CRUD functions updated to use IN() operator
for multi-tariff server-side filtering. Pagination works correctly
with multiple tariffs selected.
2026-04-24 07:16:33 +03:00
Fringg 605f202191 feat: bulk delete_subscription action — removes from bot DB + RemnaWave
Deactivates user in RemnaWave panel first, then deletes subscription
with related SubscriptionServer and TrafficPurchase records.
Subscription-level action (works with subscription_ids targeting).
2026-04-24 06:50:44 +03:00
Fringg be787a85bf feat: bulk set_devices action + device info in subscription list
- Add SET_DEVICES bulk action: sets device_limit on subscriptions,
  syncs to RemnaWave panel (subscription-level action)
- Add device_limit to SubscriptionListItem and BulkSubscriptionInfo
  schemas for frontend display
- Populate device_limit in _build_user_list_item and
  _build_subscription_info helpers
2026-04-24 06:35:29 +03:00
Fringg ff41ea9abb fix: suppress 'User already enabled' traceback in bulk add_traffic 2026-04-24 06:11:02 +03:00
Fringg cfbcc3082f fix: always return subscriptions list in user list API
subscriptions were only populated when is_multi_tariff_enabled()
was true. Users with multiple subscriptions in regular tariff mode
had empty subscriptions[] — bulk actions couldn't show or select them.
Now subscriptions are always populated regardless of tariff mode.
2026-04-24 06:08:23 +03:00
Fringg 2ad893badf fix: MissingGreenlet in subscription-ids bulk actions
_execute_for_subscription accessed user.subscriptions after commit,
triggering async lazy load → MissingGreenlet. Actions committed
successfully but reported as failed with cryptic error message.

Fix: use _build_subscription_info([sub]) with the already-loaded
targeted subscription instead of trying to lazy-load the full
user.subscriptions list.
2026-04-24 05:55:06 +03:00
Fringg e78177b2fc feat: multi-tariff bulk actions — subscription-level targeting
- Add subscription_ids to BulkExecuteRequest (mutually exclusive with
  user_ids via model_validator). Admins can now target specific
  subscriptions instead of auto-resolving the first active one.
- Add _execute_for_subscription dispatcher that loads subscription by
  ID, gets user, and passes sub_override to action handlers
- Add sub_override parameter to all 5 subscription-level handlers
  (extend, cancel, activate, change_tariff, add_traffic) — bypasses
  _resolve_subscription when a specific subscription is targeted
- Add SubscriptionListItem to UserListItem response — in multi-tariff
  mode, each user row includes all their subscriptions with tariff
  name, status, days remaining, traffic info
- User-level actions (add_balance, assign_promo_group, grant) reject
  subscription_ids with 400
- Add subscription_id field to BulkUserResult and SSE progress events
- Add _stream_bulk_execute_subscriptions SSE generator
- Backward compatible: existing user_ids requests work unchanged
2026-04-24 05:41:04 +03:00
Fringg daa472570c fix: add subscription/tariff/promo_group filters to admin user list API
The bulk actions page filters (subscription_status, tariff_id,
promo_group_id) were sent by the frontend but ignored by the
backend — the list_users endpoint had no such parameters.

- Add subscription_status, tariff_id, promo_group_id query params
  to GET /cabinet/admin/users
- Add subscription-level filtering via subquery in get_users_list
  and get_users_count CRUD functions
- Add tariff_id, tariff_name, traffic_used_gb, traffic_limit_gb,
  device_limit, days_remaining to UserListItem response schema
- Populate tariff info from subscription.tariff relationship in
  _build_user_list_item
2026-04-24 05:24:53 +03:00
Fringg db7b6734fd fix: bulk change_tariff not clearing squads when new tariff has none
When switching to a tariff with empty/null allowed_squads, the old
tariff's squads were preserved on the subscription. Now always sets
connected_squads from the new tariff (or empty list).
2026-04-24 05:10:47 +03:00
Fringg c0e0756b9a feat: bulk actions — SSE streaming progress, grant subscription, multi-tariff info
- Add SSE streaming mode (?stream=true): per-user progress events with
  real-time success/error counts, final summary event
- Add GRANT_SUBSCRIPTION action: creates new subscription with tariff,
  skips users who already have that tariff (multi-tariff aware),
  handles IntegrityError with graceful rollback
- Add BulkSubscriptionInfo: returns all user's subscriptions in each
  result for multi-tariff visibility
- Refactor: extract _validate_and_prepare and _execute_for_user helpers
  shared by streaming and non-streaming paths
2026-04-24 04:32:16 +03:00
Fringg 5b45d4354f fix: bulk actions review — rollback on error, multi-tariff constraint checks
- Add db.rollback() in per-user exception handler to prevent session
  poisoning (one failed commit would abort all subsequent users)
- Add multi-tariff duplicate subscription check in activate_subscription
  (prevents uq_subscriptions_user_tariff_active violation)
- Add multi-tariff duplicate subscription check in change_tariff
  (prevents switching to a tariff the user already holds)
2026-04-24 04:24:20 +03:00
Fringg fb2773fee4 feat: admin bulk actions API — mass operations on users
Add POST /cabinet/admin/bulk/execute endpoint for applying operations
to multiple users at once (up to 500 per request):

- extend_subscription / add_days: extend subscription by N days
- cancel_subscription: deactivate and expire subscriptions
- activate_subscription: reactivate expired subscriptions
- change_tariff: switch tariff without changing remaining days
- add_traffic: add extra GB to subscription traffic
- add_balance: credit balance with transaction record
- assign_promo_group: set or remove promo group for users

Features: dry_run preview mode, partial success handling (per-user
try/except), param validation before loop, auto-panel sync after
subscription mutations, multi-tariff mode support
2026-04-24 04:15:07 +03:00
Fringg ae7feeb726 fix: server squad sync fails on fresh DB without default promo group
On first startup with empty database, ensure_servers_synced() fetched
squads from RemnaWave but create_server_squad() failed with
ValueError('Server squad must be linked to at least one promo group')
because no default promo group existed yet.

Now _get_default_promo_group_id() auto-creates a default promo group
via _get_or_create_default_promo_group() when none exists, matching
the pattern used throughout the codebase for user registration.
2026-04-24 03:32:29 +03:00
Egor bcf5519880 Merge pull request #2904 from BEDOLAGA-DEV/dev
docs: add Overpay to README with partner block
2026-04-23 05:29:22 +03:00
Fringg 70568f82c5 docs: add Overpay to README with partner block 2026-04-23 05:25:49 +03:00