Commit Graph

7507 Commits

Author SHA1 Message Date
Fringg 4990ddf9e4 fix: add diagnostic payload logging in create_user error path
Consistent with update_user — log full payload before re-raising
non-A039 errors to aid debugging.
2026-03-21 04:08:18 +03:00
Fringg de00612965 fix: retry Remnawave API calls without externalSquadUuid on A039 FK violation
When a tariff has a stale external_squad_uuid that no longer exists in
the Remnawave panel, PATCH/POST /api/users fails with A039 (P2003 FK
constraint violation). This caused subscriptions to not sync with the
panel even though balance was already charged.

Now both update_user() and create_user() catch A039 errors and
automatically retry without externalSquadUuid, logging a warning about
the stale UUID. The subscription sync succeeds without the external
squad assignment rather than failing entirely.
2026-03-21 03:58:21 +03:00
Egor 5f71eaa926 Merge pull request #2778 from smediainfo/fix/dashboard-guest-revenue
fix: include SUBSCRIPTION_PAYMENT in dashboard revenue
2026-03-21 03:15:20 +03:00
Fringg 54155b5649 style: ruff format cloudpayments.py 2026-03-21 03:14:15 +03:00
Egor 8dc778654d Merge pull request #2785 from BEDOLAGA-DEV/revert-2783-revert-2779-fix/dashboard-revenue-complete
Revert 2783 revert 2779 fix/dashboard revenue complete
2026-03-21 03:11:03 +03:00
Egor 5175cccab6 Revert "Revert "fix: include landing page revenue in dashboard statistics"" 2026-03-21 03:07:19 +03:00
Egor 39ae095d9b Merge pull request #2786 from BEDOLAGA-DEV/main
w
2026-03-21 03:04:27 +03:00
Egor db3ac254ba Revert "Revert "fix: include landing page revenue in dashboard statistics"" 2026-03-21 03:03:06 +03:00
Egor bff9ebf078 Merge pull request #2783 from BEDOLAGA-DEV/revert-2779-fix/dashboard-revenue-complete
Revert "fix: include landing page revenue in dashboard statistics"
2026-03-21 03:02:57 +03:00
Egor 42ddadec5b Revert "fix: include landing page revenue in dashboard statistics" 2026-03-21 03:02:36 +03:00
Egor c6c1599e14 Merge pull request #2779 from smediainfo/fix/dashboard-revenue-complete
fix: include landing page revenue in dashboard statistics
2026-03-21 03:02:09 +03:00
Egor 77b2d645c5 Merge pull request #2782 from SayonaraQ/pr/remnawave-node-webhook-toggle-dev
add toggle for Remnawave node connection webhook alerts
2026-03-21 03:00:13 +03:00
Fringg 3875335cd7 fix: narrow exception handling and fix session leak in gift.py
- Replace broad `except Exception` with `except TelegramAPIError` in
  balance.py and wheel.py Stars invoice creation (prevents masking
  programming errors)
- Fix session leak in gift.py telegram_stars path: wrap PaymentService
  usage in try/finally to ensure bot.session.close() is called
2026-03-21 02:55:48 +03:00
Fringg 0a53b85b8a refactor: centralize Bot instantiation via create_bot() factory
Replace all ~45 direct Bot() calls across the codebase with a centralized
create_bot() factory function that automatically configures SOCKS5 proxy
session when PROXY_URL is set. This ensures proxy support applies uniformly
to all Telegram API traffic.

Key changes:
- Add app/bot_factory.py with create_bot() factory
- Replace direct Bot() instantiation in 33 files
- Fix session leaks in cloudpayments.py and auth.py (async with)
- Replace 2 direct httpx calls to api.telegram.org with
  bot.create_invoice_link() (balance.py, wheel.py)
- Remove now-unused imports (Bot, DefaultBotProperties, ParseMode, httpx)
2026-03-21 02:49:37 +03:00
Fringg 82b6a8bf70 feat: add SOCKS5 proxy support for Telegram API traffic
Route bot traffic through SOCKS5 proxy when PROXY_URL env var is set.
Validates scheme to reject HTTP proxies (would expose bot token).
Credentials are masked in logs.
2026-03-21 02:32:35 +03:00
Fringg 2a72deadd6 fix: resolve EmailService stale SMTP config causing NoneType crash on from_email
EmailService singleton cached SMTP settings in __init__ at import time.
is_configured() read live from settings, but self.from_email stayed None
when SMTP was unconfigured at startup → AttributeError on .split('@').

Replace cached attributes with @property accessors, snapshot from_email
once per send_email call with validation guard.
2026-03-21 02:21:25 +03:00
Fringg afefcc9c07 fix: resolve remaining TOCTOU issues in RioPay, SeverPay and restore paid_at
- RioPay: use create_transaction(commit=False) to keep FOR UPDATE lock,
  replace update_riopay_payment_status with inline assignment + flush,
  add emit_transaction_side_effects after commit
- SeverPay: add db.flush() before _finalize, remove self-assignment,
  add paid_at to both webhook and status-check paths
- Freekassa/KassaAI: add is_paid and paid_at to webhook and status-check
  inline sections (regression from CRUD→inline migration)
- MulenPay: add is_paid and paid_at to webhook inline section
2026-03-21 02:10:41 +03:00
Fringg 82c79c1306 fix: prevent double-payment TOCTOU race in all payment providers
Apply the same FOR UPDATE locking pattern across 8 providers:
- RioPay: added FOR UPDATE lock (had none at all)
- CryptoBot: moved lock before status check, removed redundant lock
- WATA: moved lock before is_paid commit, removed redundant lock
- Freekassa: moved lock before is_paid commit, removed redundant lock
- KassaAI: moved lock before is_paid commit, removed redundant lock
- MulenPay: moved lock before is_paid commit, removed redundant lock
- Pal24: moved lock before is_paid commit, removed redundant lock
- SeverPay: moved lock before is_paid check, removed redundant lock

Pattern applied to all: acquire FOR UPDATE with populate_existing=True
immediately after finding the payment, replace intermediate commits with
inline assignments + flush(), re-check is_paid from locked row.
2026-03-21 01:52:56 +03:00
Fringg 0e1296e0ea fix: prevent double balance credit on concurrent Platega webhooks
- acquire FOR UPDATE lock immediately after payment lookup, before is_paid check
- use populate_existing=True to prevent SQLAlchemy identity map stale reads
- replace intermediate update_platega_payment(commit) with inline assignments + flush
- re-check locked.is_paid after lock in get_platega_payment_status
- guard _finalize_platega_payment: only called when lock held and is_paid=False
- suppress "message is not modified" TelegramBadRequest in message_patch
2026-03-21 01:46:44 +03:00
Fringg 9dd6b54c6e fix: prevent bootstrap from reactivating revoked superadmin roles
- bootstrap _assign_if_missing no longer reactivates revoked UserRole rows
- revoke_role uses SELECT FOR UPDATE + pg_advisory_xact_lock to prevent TOCTOU race on last-superadmin check
- block self-revocation of superadmin role
- block is_active/level changes on system roles
- block expires_at on superadmin role assignments
- single SUPERADMIN_LEVEL constant in crud/rbac.py, imported everywhere
- get_superadmin_count excludes expired assignments
- removed dead UserRoleCRUD.revoke_role method
- warn when revoking RBAC role from a legacy ADMIN_IDS user
- added migration 0043: indexes on user_roles.role_id, access_policies.role_id, lower(users.email)
2026-03-21 01:09:13 +03:00
SayonaraQ 8a5710aff3 style: align webhook service with ruff format 2026-03-21 00:59:18 +03:00
SayonaraQ ce36fba54f style: format remnawave webhook service 2026-03-21 00:54:54 +03:00
SayonaraQ f601bedb48 add toggle for Remnawave node connection webhook alerts 2026-03-21 00:30:49 +03:00
sMedia.tech 801921ff74 fix: increase landing purchase rate limit from 5 to 30 req/min
Users hitting 429 Too Many Requests when trying to purchase on landing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 23:48:02 +03:00
Fringg 67da390371 feat: show both bot and cabinet referral links everywhere
Previously the bot showed only one referral link (cabinet when CABINET_URL
is set, bot otherwise). Users who received the cabinet link were confused —
they opened a web registration form instead of being directed to the bot.

Now the bot, cabinet API, and miniapp API all return both links:
- Bot link (t.me deep link) — always shown
- Cabinet link (web registration) — shown when CABINET_URL is configured

Changes:
- Add get_bot_referral_link() and get_cabinet_referral_link() to config
- Refactor config methods to eliminate code duplication
- Update bot referral handler to display both links
- Fix switch_inline_query 256-char limit with auto-truncation
- Add html_escape() to all user-controlled strings in HTML messages
- Add translations for 5 new keys in all 5 locale files (ru/en/ua/zh/fa)
- Simplify cabinet route to use new methods instead of inline URL construction
- Add bot_referral_link to MiniApp API schema and response
2026-03-20 23:12:22 +03:00
sMedia.tech d400cd7b49 feat: broadcast caption validation + landing daily created stats
- Validate message length for media broadcasts (1024 char Telegram limit)
- Add created count per day to landing stats API (separate from successful)
- Fix total_purchases to show total_created instead of total_successful

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 17:14:27 +03:00
sMedia.tech bf0ba22790 style: ruff format email_templates.py
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 16:30:47 +03:00
sMedia.tech f82a713110 feat: expose cabinet_email/password vars in subscription delivered template admin UI
Add cabinet_email and cabinet_password to context_vars and sample_contexts
for guest_subscription_delivered template type so they appear in the admin
email template editor.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 16:28:18 +03:00
sMedia.tech fedcf2569a feat: include cabinet credentials in subscription delivered email
Add login/password block to the "subscription ready" email template
so users receive their cabinet credentials in the first email.
The credentials block is only shown when cabinet_password is present
(new accounts). All 5 locales updated (ru, en, zh, ua, fa).

The separate credentials email (GUEST_CABINET_CREDENTIALS) is still
sent as before — this provides redundancy in case one email doesn't
arrive (e.g. due to SMTP quota limits).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 16:22:22 +03:00
sMedia.tech 1882909b3e fix: derive income_today from revenue_chart to ensure consistency
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 12:25:39 +03:00
sMedia.tech 226d3f2766 fix: default payment_method to BALANCE for bot subscription payments
Prevents double-counting in revenue: bot users create DEPOSIT (real
money) + SUBSCRIPTION_PAYMENT (balance debit). Without explicit
payment_method, subscription_payment had NULL which was patched to
kassa_ai, causing both to count as real revenue.

Now create_transaction defaults to BALANCE for SUBSCRIPTION_PAYMENT
when no payment_method is specified.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 12:19:55 +03:00
sMedia.tech 6982d27378 fix: include SUBSCRIPTION_PAYMENT in recent payments today/week totals
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 12:19:55 +03:00
sMedia.tech 27ef75214e fix: include SUBSCRIPTION_PAYMENT in sales summary and deposits stats
Same fix as transaction.py — sales dashboard summary and deposits
breakdown were only counting DEPOSIT transactions, missing all
landing page purchases (SUBSCRIPTION_PAYMENT).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 12:19:55 +03:00
sMedia.tech 13dba5a303 fix: include SUBSCRIPTION_PAYMENT in dashboard revenue calculations
Guest/landing purchases create SUBSCRIPTION_PAYMENT transactions (not
DEPOSIT), so they were excluded from income_today, total_income,
revenue_by_period, and payment_methods breakdown.

Also use func.abs() for SUBSCRIPTION_PAYMENT amounts since they are
stored as negative values.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 12:19:55 +03:00
sMedia.tech d7f91c8358 fix: include SUBSCRIPTION_PAYMENT in dashboard revenue calculations
Guest/landing purchases create SUBSCRIPTION_PAYMENT transactions (not
DEPOSIT), so they were excluded from income_today, total_income,
revenue_by_period, and payment_methods breakdown.

Also use func.abs() for SUBSCRIPTION_PAYMENT amounts since they are
stored as negative values.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 10:10:56 +03:00
Egor ee9f0b7382 Merge pull request #2777 from BEDOLAGA-DEV/release-please--branches--main
chore(main): release 3.36.1
v3.36.1
2026-03-20 08:48:30 +03:00
github-actions[bot] e16eba10d9 chore(main): release 3.36.1 2026-03-20 05:48:16 +00:00
Egor 1771cc4d13 Merge pull request #2776 from BEDOLAGA-DEV/dev
Dev
2026-03-20 08:47:53 +03:00
Egor 3bda0a2001 Merge pull request #2775 from BEDOLAGA-DEV/main
w
2026-03-20 08:47:02 +03:00
Fringg 877b1cde11 fix: handle duplicate admin roles in RBAC bootstrap
Use scalars().first() instead of scalar_one_or_none() to tolerate
duplicate rows in admin_roles table by (is_system, level).
2026-03-20 08:45:23 +03:00
Fringg 5faf7015ac fix: make migration 0042 idempotent for retry_count column 2026-03-20 08:44:19 +03:00
Egor 55f386d7e8 Merge pull request #2774 from BEDOLAGA-DEV/release-please--branches--main
chore(main): release 3.36.0
v3.36.0
2026-03-20 07:23:48 +03:00
github-actions[bot] e6a310dc32 chore(main): release 3.36.0 2026-03-20 04:21:42 +00:00
Egor ccd8f86e96 Merge pull request #2773 from BEDOLAGA-DEV/dev
Dev
2026-03-20 07:21:11 +03:00
Egor b6d4373933 Merge pull request #2771 from smediainfo/fix/kassa-ai-guest-metadata
fix: use base model name for KassaAI guest metadata patch
2026-03-20 07:20:25 +03:00
Egor 266620904d Merge pull request #2772 from BEDOLAGA-DEV/main
w
2026-03-20 07:18:45 +03:00
Fringg 479af5741a style: ruff format 2026-03-20 07:15:15 +03:00
Fringg 79c110ff41 fix: address review findings for multi-provider recovery
- Use atomic UPDATE SET retry_count = retry_count + 1 instead of
  SELECT+modify+commit to avoid identity map pollution
- Filter retry_count < max_retries in SQL WHERE clause to avoid
  wasting LIMIT slots on exhausted purchases
- Extract _fail_exhausted_purchases_batch() — separate pass for
  exhausted purchases, alert sent outside session context
- HTML-escape all user-controlled values in admin alert messages
- Mark purchases FAILED on amount mismatch (prevents repeated
  error logs every scheduler cycle) with admin alert
- Accept plain dict in _send_stuck_purchase_alert instead of ORM
  object (avoids expired-attribute access after commit)
2026-03-20 07:02:47 +03:00
Fringg 3d78974af7 feat: add multi-provider recovery, retry_count, amount verification, and indexes
- Add retry_count column to guest_purchases with Alembic migration
- Add expression indexes on metadata_json->>'purchase_token' for all 12
  payment provider tables (partial indexes filtered by is_paid/status)
- Implement _find_succeeded_provider_payment() covering all providers:
  YooKassa, Heleket, MulenPay, Pal24, Wata, Platega, CloudPayments,
  Freekassa, KassaAi, RioPay, SeverPay, and CryptoBot (payload field)
- Add amount verification in _check_and_recover_pending_purchase():
  compares provider payment amount with GuestPurchase.amount_kopeks,
  skips for CryptoBot (USD conversion imprecision)
- Increment retry_count on each retry attempt in retry_stuck_paid_purchases
  and retry_stuck_pending_activation
- Mark purchases as FAILED after 20 retries with admin Telegram alert
  via AdminNotificationService (ERRORS category)
2026-03-20 06:53:49 +03:00
Fringg 57c5c679ee fix: address review findings for guest purchase recovery
- Add FOR UPDATE to recovery path in try_fulfill_guest_purchase to prevent
  TOCTOU race that could overwrite DELIVERED back to PAID
- Isolate monitoring phases with independent try/except so Phase 1 failure
  does not block Phase 2/3
- Optimize recover_stuck_pending_purchases to select only token and
  payment_method columns instead of full ORM objects
- Remove dead elif branch in stars_payments.py (try_fulfill_guest_purchase
  no longer returns False)
- Add Phase 3 comment for consistency
2026-03-20 06:45:21 +03:00