fix(tradein/quota): per-user quota override + no burn on empty result #2517

Merged
lekss361 merged 4 commits from fix/tradein-quota-per-user-and-no-burn into main 2026-07-13 20:18:43 +00:00

4 commits

Author SHA1 Message Date
bot-backend
950ac91f7e fix(tradein/quota): fix stale 'Migration 184' header comment in 185_account_quota_overrides.sql
All checks were successful
CI / changes (pull_request) Successful in 9s
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI Trade-In / changes (pull_request) Successful in 9s
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Successful in 4m55s
2026-07-13 23:07:43 +03:00
bot-backend
0875d5b9e6 fix(tradein/quota): renumber migration 184 -> 185 (prefix collision)
184_user_events.sql merged into main (#2515) after this branch's base,
claiming the 184 prefix first. Renumber account_quota_overrides to 185
(next free slot) and update the "migration 184" references in the SQL
header comment and account_quota.py docstrings so
test_migrations_manifest.py::test_new_files_do_not_reuse_prefix stays green.
2026-07-13 23:07:11 +03:00
bot-backend
52e5a32fd5 Merge remote-tracking branch 'forgejo/main' into fix/tradein-quota-per-user-and-no-burn 2026-07-13 23:05:14 +03:00
bot-backend
9c12407c09 fix(tradein/quota): per-user quota override + no burn on empty result
All checks were successful
CI Trade-In / backend-tests (pull_request) Successful in 4m54s
CI Trade-In / changes (pull_request) Successful in 8s
CI / changes (pull_request) Successful in 8s
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
Two bugs hit the first paying pilot (user2/Брусника):

1. Bonus attempts were granted via negative `used` hack
   (account_estimate_usage.used = -35), which made /quota return
   {limit:15, used:-35, remaining:50} -> frontend rendered the absurd
   "Осталось 50 из 15". Replaced with a proper personal monthly limit:
   account_quota_overrides table (migration 184) + account_quota.user_limit()
   used across get_status/check_and_raise/increment instead of the hardcoded
   global MONTHLY_LIMIT. get_status now clamps remaining = max(0, limit -
   max(0, used)) so remaining can never exceed limit even if a negative
   used leaks through again. Migration seeds user2 -> monthly_limit=50 and
   resets the old hack (used<0 -> 0), so user2 now sees "50 из 50".

2. POST /estimate unconditionally called account_quota.increment() after
   estimate_quality, even when the address failed to geocode and the
   handler returned the _empty_estimate fallback (median=0, n_analogs=0,
   insufficient_data=True, HTTP 200) -- burning a paid slot for a null
   result. Now increment is skipped when result.insufficient_data is True;
   the #747 TOCTOU-safe atomic increment/429 gate is unchanged for real
   results.
2026-07-13 22:53:14 +03:00