gendesign/tradein-mvp/backend/data/sql/191_account_quota_unlimited_flag.sql
bot-backend 3e4c93c91e
All checks were successful
CI Trade-In / changes (pull_request) Successful in 9s
CI / changes (pull_request) Successful in 10s
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
CI Trade-In / backend-tests (pull_request) Successful in 56s
fix(tradein/auth): restore praktika access + data-driven unlimited quota grants
praktika (ГК «Практика») role expired -> pilot: доступ восстановлен по решению
владельца продукта. Добавлен expiredtest QA-аккаунт, чтобы test_rbac.py сохранил
regression-покрытие role=expired (praktika больше не годится как fixture).

account_quota.is_unlimited() больше не хардкодит username == 'kopylov': грант
безлимитных оценок теперь читается из account_quota_overrides.unlimited (migration
191) — единый источник правды для всех non-admin unlimited-аккаунтов. kopylov
перенесён в данные той же миграцией (поведение не меняется, деплой применяет SQL
раньше кода); praktika получил такой же грант.

unlimited выбран как явная boolean-колонка, а не sentinel monthly_limit (-1/0),
т.к. 0 неоднозначен ("ноль оценок" vs "без лимита").
2026-07-27 01:49:01 +03:00

68 lines
4.7 KiB
PL/PgSQL
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

-- Migration 191: account_quota_overrides.unlimited — безлимит как данные, не хардкод
--
-- WHY:
-- app.services.account_quota.is_unlimited() до этой миграции проверял ровно два
-- условия: роль admin ИЛИ literal `username == 'kopylov'` — захардкоженное сравнение
-- строки прямо в коде. Восстановление пилота praktika (ГК «Практика», доступ вернул
-- владелец продукта 2026-07-27 — см. auth/roles.yaml) с безлимитным грантом сделало
-- бы это хардкодом ВТОРОГО имени: не масштабируется (каждый следующий безлимитный
-- клиент требовал бы code-change + review + deploy вместо data-change) и плохо само
-- по себе как паттерн (магическая строка вместо конфигурируемых данных).
--
-- unlimited — отдельная boolean-колонка, а не sentinel-значение monthly_limit
-- (-1 / 0): `0` неоднозначен («ноль оценок в месяц» vs «без лимита»), explicit
-- boolean честнее и не требует специального парсинга в user_limit()/is_unlimited().
--
-- kopylov ПЕРЕНЕСЁН в данные этой же миграцией (хардкод в коде убран, не оставлен
-- параллельно) — единый источник правды для non-admin unlimited-аккаунтов вместо
-- двух параллельных механизмов (код-константа + таблица). Порядок деплоя
-- (SQL-миграция применяется РАНЬШЕ, чем стартует новый код — см.
-- .claude/rules/sql.md "Migration order") гарантирует, что строка kopylov уже в
-- таблице к моменту, когда новый is_unlimited() (без хардкода) начинает работать —
-- поведение kopylov не меняется ни на секунду простоя.
--
-- WHAT:
-- 1. account_quota_overrides.unlimited boolean NOT NULL DEFAULT false.
-- 2. Seed: kopylov (перенос хардкода) + praktika (новый грант, пилот восстановлен
-- 2026-07-27) — оба unlimited=true. monthly_limit=999999 — placeholder: get_status()
-- безусловно читает monthly_limit через user_limit() даже для unlimited-аккаунтов
-- (чтобы вернуть какое-то "limit" поле в /quota), а само enforcement для unlimited
-- обходит этот лимит (is_unlimited() гейтит раньше в check_and_raise()/increment()).
-- Значение просто не должно выглядеть абсурдным, если когда-либо surfaced напрямую.
--
-- IDEMPOTENCY:
-- ALTER TABLE ... ADD COLUMN IF NOT EXISTS (новая колонка) + INSERT ... ON CONFLICT
-- DO UPDATE (повторный прогон сходится к тому же состоянию, не дублирует строки).
--
-- Dependencies: 185_account_quota_overrides.sql (создаёт account_quota_overrides).
BEGIN;
ALTER TABLE account_quota_overrides
ADD COLUMN IF NOT EXISTS unlimited boolean NOT NULL DEFAULT false;
COMMENT ON COLUMN account_quota_overrides.unlimited IS
'Безлимитный грант (нет месячного лимита оценок) — читается '
'app.services.account_quota.is_unlimited(). Заменяет прежний хардкод username в коде.';
INSERT INTO account_quota_overrides (username, monthly_limit, unlimited, note)
VALUES ('kopylov', 999999, true,
'Личный аккаунт — безлимит перенесён из хардкода is_unlimited() в данные '
'(migration 191, 2026-07-27), поведение не изменилось')
ON CONFLICT (username) DO UPDATE SET
monthly_limit = EXCLUDED.monthly_limit,
unlimited = EXCLUDED.unlimited,
note = EXCLUDED.note,
updated_at = now();
INSERT INTO account_quota_overrides (username, monthly_limit, unlimited, note)
VALUES ('praktika', 999999, true,
'ГК «Практика» — пилот восстановлен 2026-07-27 (решение владельца продукта), '
'безлимитный грант')
ON CONFLICT (username) DO UPDATE SET
monthly_limit = EXCLUDED.monthly_limit,
unlimited = EXCLUDED.unlimited,
note = EXCLUDED.note,
updated_at = now();
COMMIT;