From ea453c3ff2b02c1552e9339c529f10ab5b320c5c Mon Sep 17 00:00:00 2001 From: bot-backend Date: Fri, 19 Jun 2026 12:53:37 +0300 Subject: [PATCH] =?UTF-8?q?fix(tradein):=20remove=20=C2=AB=D0=9F=D1=80?= =?UTF-8?q?=D0=B0=D0=BA=D1=82=D0=B8=D0=BA=D0=B0=C2=BB=20white-label=20bran?= =?UTF-8?q?d=20(DB=20row=20+=20account=20mapping=20+=20assets)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tradein-mvp/backend/app/core/auth.py | 22 +++++++------------ .../data/sql/121_remove_brand_praktika.sql | 9 ++++++++ tradein-mvp/backend/tests/test_me_brand.py | 19 ++++++++-------- .../frontend/public/brands/praktika.svg | 1 - .../src/components/trade-in/Topbar.tsx | 2 +- .../src/components/trade-in/trade-in.css | 6 ++--- tradein-mvp/frontend/src/lib/useBrand.ts | 3 +-- 7 files changed, 32 insertions(+), 30 deletions(-) create mode 100644 tradein-mvp/backend/data/sql/121_remove_brand_praktika.sql delete mode 100644 tradein-mvp/frontend/public/brands/praktika.svg diff --git a/tradein-mvp/backend/app/core/auth.py b/tradein-mvp/backend/app/core/auth.py index 0dbafb3a..06f896b6 100644 --- a/tradein-mvp/backend/app/core/auth.py +++ b/tradein-mvp/backend/app/core/auth.py @@ -50,20 +50,14 @@ class UserScope(TypedDict): # Account → brand mapping (#657 brand-by-account) # --------------------------------------------------------------------------- # -# Брендирование «Практика» (#657 brand-by-account) применяется ТОЛЬКО под -# аккаунтом `praktika`. Резолвится в /me как UserScope.brand, а при генерации -# trade-in PDF — по `created_by` оценки (анти-spoofing #7, бренд не хранится). -# Остальные юзеры (вкл. `kopylov`) → None (стандартный generic UI/PDF). Slug -# должен существовать в таблице `brands` (см. services/brand.py) — иначе -# useBrand fallback на generic. -# -# `kopylov` ранее мапился на бренд `praktika`, но это приводило к утечке лого -# «Практики» в отчёты, созданные под `kopylov`, не тем клиентам (инцидент: -# отчёт для Брусники с лого Практики) — маппинг удалён. NB: kopylov/praktika -# остаются в роли `pilot` в roles.yaml — это РОЛЬ, не бренд, не трогаем. -_USERNAME_BRAND: dict[str, str] = { - "praktika": "praktika", -} +# White-label бренд «Практика» снят целиком (бизнес-решение 2026-06-19): строка +# удалена из таблицы `brands` (миграция 121), SVG-логотип убран из фронта. +# Маппинг аккаунт→бренд теперь пустой — все юзеры резолвятся в None (generic +# UI/PDF). Механизм сохранён для будущих white-label клиентов: добавить slug +# сюда + строку в `brands` (см. services/brand.py). NB: аккаунты praktika/ +# kopylov остаются пилот-логинами (роль `pilot` в roles.yaml) — это РОЛЬ, не +# бренд, не трогаем. +_USERNAME_BRAND: dict[str, str] = {} def get_brand_for_user(username: str) -> str | None: diff --git a/tradein-mvp/backend/data/sql/121_remove_brand_praktika.sql b/tradein-mvp/backend/data/sql/121_remove_brand_praktika.sql new file mode 100644 index 00000000..4235cb1b --- /dev/null +++ b/tradein-mvp/backend/data/sql/121_remove_brand_praktika.sql @@ -0,0 +1,9 @@ +-- 121_remove_brand_praktika.sql +-- Полное удаление white-label бренда «Практика» (бизнес-решение 2026-06-19). +-- Бренд снят целиком: эта миграция убирает строку из brands; аккаунт-маппинг +-- praktika/kopylov удалён в app/core/auth.py; SVG-логотип удалён из фронта. +-- Идемпотентно: DELETE по PK slug. На fresh-build удаляет строку, засеянную 002 +-- и обновлённую 084/099 (эта миграция с бОльшим номером — выполняется последней). +BEGIN; +DELETE FROM brands WHERE slug = 'praktika'; +COMMIT; diff --git a/tradein-mvp/backend/tests/test_me_brand.py b/tradein-mvp/backend/tests/test_me_brand.py index f6866334..1c932bd9 100644 --- a/tradein-mvp/backend/tests/test_me_brand.py +++ b/tradein-mvp/backend/tests/test_me_brand.py @@ -1,7 +1,8 @@ """Tests for #657 brand-by-account — UserScope.brand resolution. -Pure-function tests against app.core.auth: brand slug привязывается к аккаунту -(ТОЛЬКО praktika → "praktika"), остальные юзеры — включая kopylov — → None. +Pure-function tests against app.core.auth. White-label бренд «Практика» снят +целиком (бизнес-решение 2026-06-19): маппинг аккаунт→бренд пустой, поэтому ВСЕ +юзеры (включая praktika/kopylov) резолвятся в None (generic UI/PDF). get_user_scope прокидывает brand в scope, который /me отдаёт фронту. Без БД и сети: читается реальный auth/roles.yaml (ancestor-walk внутри репо). @@ -28,10 +29,9 @@ from app.core.auth import get_brand_for_user, get_user_scope # noqa: E402 @pytest.mark.parametrize( ("username", "expected"), [ - ("praktika", "praktika"), - # kopylov больше НЕ брендируется как Практика (#657 brand leak fix): - # бренд резолвится по created_by при генерации PDF, поэтому утечка лого - # Практики в отчёты под kopylov исправляется тем, что kopylov → generic. + # «Практика» снята целиком (2026-06-19): praktika/kopylov больше не + # брендируются — маппинг пустой, все аккаунты → generic UI/PDF. + ("praktika", None), ("kopylov", None), ("admin", None), ("user1", None), @@ -43,15 +43,16 @@ def test_get_brand_for_user(username: str, expected: str | None) -> None: def test_user_scope_brand_none_for_kopylov() -> None: - # Регресс-гард на #657 brand leak: kopylov-оценки рендерят generic PDF. + # Регресс-гард: бренд снят, kopylov-оценки рендерят generic PDF. scope = get_user_scope("kopylov") assert scope["brand"] is None assert scope["username"] == "kopylov" -def test_user_scope_carries_brand_for_praktika() -> None: +def test_user_scope_brand_none_for_praktika() -> None: + # Бренд «Практика» удалён — praktika-аккаунт теперь generic. scope = get_user_scope("praktika") - assert scope["brand"] == "praktika" + assert scope["brand"] is None assert scope["username"] == "praktika" diff --git a/tradein-mvp/frontend/public/brands/praktika.svg b/tradein-mvp/frontend/public/brands/praktika.svg deleted file mode 100644 index a452e484..00000000 --- a/tradein-mvp/frontend/public/brands/praktika.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/tradein-mvp/frontend/src/components/trade-in/Topbar.tsx b/tradein-mvp/frontend/src/components/trade-in/Topbar.tsx index 486415c3..c0c444e6 100644 --- a/tradein-mvp/frontend/src/components/trade-in/Topbar.tsx +++ b/tradein-mvp/frontend/src/components/trade-in/Topbar.tsx @@ -118,7 +118,7 @@ export function Topbar({ active }: TopbarProps) { {brand ? ( /* logo_url из бренд-конфига → safeUrl (блок javascript:/data: схем, #766). Брендовый логотип — полноразмерный wordmark (содержит название), поэтому - отдельный brand-product текст НЕ рендерим, иначе дубль («ПРАКТИКА» ×2). + отдельный brand-product текст НЕ рендерим, иначе дубль названия бренда ×2. Невалидный/пустой URL → fallback TI-марка + текстовое название бренда. */ safeUrl(brand.logo_url) ? ( ` — демо/превью, только если у аккаунта нет бренда. - * (praktika/kopylov логинятся → свой бренд автоматически из me.brand, без ?brand=.) * Если ни один не задан — нет бренда (generic UI), хук возвращает {data: null}. * * Mirror паттерна useMe.ts: TanStack Query, staleTime/gcTime Infinity