fix(tradein): удалить white-label бренд «Практика» целиком (БД + маппинг + ассеты) #1779
7 changed files with 32 additions and 30 deletions
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -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"
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1498 194" fill="none" role="img" aria-label="Практика"><path d="M1497.3 80.77h-79.53l-32.75 112.282h23.38l88.9-112.282z" fill="#FF5BBA"/><path d="M1279.34 139.779c-22.35 0-59.83-5.658-59.83-43.524 0-34.767 39.58-42.067 72.78-42.067h40.58V51.01c0-17.81-14.88-21.467-37.23-21.467-15.95 0-51.49 3.906-61.53 5.037V5.704C1242.28 4.479 1274.05 0 1299.36 0c25.31 0 42.35 4.805 54.45 14.306 12.09 9.487 17.97 22.243 17.97 38.998v83.731h-15.72l-18.18-23.312-1.58 3.627c-2.7 6.2-8.71 11.517-17.87 15.81-9.4 4.402-22.55 6.634-39.09 6.634v-.015zm12.13-63.147c-8.17 0-33.04 0-33.04 18.243 0 8.37 5 18.352 28.81 18.352 12.4 0 22.76-2.666 30.75-7.936 7.92-5.208 14.88-17.5 14.88-26.319v-2.356h-41.4v.016zM113.367 136.911V32.101H38.905v104.81H0V3.642h152.272v133.269h-38.905zM1170.91 137.035l-41.52-52.452h-36.94v52.452h-38.92V3.642h38.92v52.452h36.94l41.52-52.452h41.98L1160.1 70.34l52.79 66.696h-41.98zM178.73 193.052V3.642h15.717l18.182 23.312 1.581-3.627c2.697-6.2 8.711-11.516 17.871-15.81 8.727-4.092 25.327-6.634 43.322-6.634 45.369 0 69.363 23.855 69.363 69.006 0 69.89-52.204 69.89-69.363 69.89-17.995 0-34.595-2.542-43.322-6.634-4.185-1.953-7.75-4.154-10.602-6.526l-3.859-3.208v69.641h-38.905.015zm85.839-163.649c-21.467 0-40.687 9.424-46.732 22.94l-.186.419v.45c-.14 2.96-.248 14.12-.233 17.111 0 3.085.093 14.54.217 17.515l.031.636.341.542c9.486 14.756 25.591 23.235 44.19 23.235 9.285 0 18.693-2.139 27.203-6.185 14.477-7.486 17.484-23.575 17.453-35.758.031-12.168-2.961-28.257-17.376-35.712-7.161-3.41-15.748-5.193-24.893-5.193h-.015zM417.833 139.779c-22.351 0-59.83-5.658-59.83-43.524 0-34.767 39.587-42.067 72.788-42.067h40.579V51.01c0-17.81-14.88-21.467-37.231-21.467-15.95 0-51.491 3.906-61.535 5.037V5.704C380.772 4.479 412.547 0 437.859 0c25.311 0 42.346 4.805 54.436 14.306 12.089 9.487 17.964 22.243 17.964 38.998v83.731h-15.717l-18.181-23.312-1.581 3.627c-2.697 6.2-8.711 11.517-17.872 15.81-9.393 4.402-22.552 6.634-39.091 6.634l.016-.015zm12.136-63.147c-8.168 0-33.046 0-33.046 18.243 0 8.37 5.007 18.352 28.815 18.352 12.4 0 22.754-2.666 30.752-7.936 7.92-5.208 14.88-17.5 14.88-26.319v-2.356h-41.401v.016zM654.114 137.035L612.59 84.583h-36.952v52.452h-38.905V3.642h38.905v52.452h36.952l41.524-52.452h41.974L643.295 70.34l52.793 66.696h-41.974zM760.041 136.911V32.101h-57.334l8.199-28.459h144.072V32.1h-56.032v104.811h-38.905zM984.992 3.751l-71.408 90.504V3.642h-38.781v133.269h42.904l70.602-89.45v89.45h38.781V3.642l-42.098.109z" fill="#2C3133"/></svg>
|
||||
|
Before Width: | Height: | Size: 2.5 KiB |
|
|
@ -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) ? (
|
||||
<img
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
/* #657 white-label: Manrope — OPEN geometric grotesk, визуальная замена
|
||||
платного HalvarMittel из брендбука «Практики». Подключается всегда, но
|
||||
применяется только при активном бренде (см. --font-sans override ниже). */
|
||||
/* #657 white-label: Manrope — OPEN geometric grotesk для брендового режима.
|
||||
Подключается всегда, но применяется только при активном бренде
|
||||
(см. --font-sans override ниже). */
|
||||
@import url('https://fonts.googleapis.com/css2?family=Manrope:wght@400;500;600;700&display=swap');
|
||||
|
||||
:root {
|
||||
|
|
|
|||
|
|
@ -1,12 +1,11 @@
|
|||
"use client";
|
||||
|
||||
/**
|
||||
* useBrand — white-label resolver для #657 («Практика» и пр.).
|
||||
* useBrand — white-label resolver (#657).
|
||||
*
|
||||
* Активный бренд резолвится из ДВУХ источников (account-бренд приоритетен, #765):
|
||||
* 1. `me.brand` из /me — привязка бренда к аккаунту (#657 brand-by-account) — WINS;
|
||||
* 2. query-параметр `?brand=<slug>` — демо/превью, только если у аккаунта нет бренда.
|
||||
* (praktika/kopylov логинятся → свой бренд автоматически из me.brand, без ?brand=.)
|
||||
* Если ни один не задан — нет бренда (generic UI), хук возвращает {data: null}.
|
||||
*
|
||||
* Mirror паттерна useMe.ts: TanStack Query, staleTime/gcTime Infinity
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue