chore(history): squash 59 commit(s) from 2026-09-23

- refactor(hud/weather/render): Scoreboard 931→240 + Rain 903→159 + UiMeshGeometry 956→99 (8.5.2)
- refactor(chams): Chams 985 → 129 + 4×≤195 (8.5.2)
- refactor(module): Module 1031 → 139 + 4×≤134 (8.5.2)
- refactor(tooltips): BetterTooltips 960 → 181 + 4×≤199 (8.5.2)
- refactor(clickgui): ClickGuiPickerState 970→166 + ModulesMenuScreen 966→145 (8.5.2)
- refactor(clickgui): CombatProtocolHeuristicsEditorState 932→79 + 4×≤192 (8.5.2)
- refactor(hud): CompactHudStatModel 894 → 125 + 4×≤175 (8.5.2)
- refactor(iris): ShaderPatchCompiler 843→66 + 4×≤174 (8.5.2)
- refactor(tab): CustomTabList 834→136 + 4×≤166 (8.5.2)
- refactor(relations): OnlineRelationPlayerPickerComponent 837→74 + 4×≤192 (8.5.2)
- refactor(hits): HitEffect 770→120 + 4×≤176 (8.5.2)
- refactor(predict): Predictions 815→150 + 4×≤195 (8.5.2)
- refactor(config): ConfigProfilesComponent 799→125 + 4×≤195 (8.5.2)
- refactor(sim): PlayerMovementSimulation 757→157 + 4×≤194 (8.5.2)
- refactor(predict): ProjectilePuncher 747→104 + 3×≤134 (8.5.2)
- refactor(crosshair): Crosshair 743→143 + 4×≤167 (8.5.2)
- refactor(mediaplayer): MediaPlayer 743→174 + 4×≤196 (8.5.2)
- refactor(hud): Itemizer 699→193 + Cooldowns 707→151 (8.5.2)
- refactor(hud): Potions 617→176 + 4×≤166 (8.5.2)
- refactor(hud): Admins 702→118 + 4×≤138 (8.5.2)
- refactor(hud): Keybinds 611→157 + 4×≤160 (8.5.2)
- refactor(hud): ModuleList 499→181 + 3×≤150 (8.5.2)
- refactor(hud): Radar 392→161 + 2×≤124 (8.5.2)
- refactor(hud): Fps 309→168 + 3×≤107 (8.5.2)
- refactor(hud): Memory 304→151 + 3×≤144 (8.5.2)
- refactor(hud): Tps 339→154 + 3×≤143 (8.5.2)
- refactor(hud): Ping 309→145 + 3×≤125 (8.5.2)
- refactor(hud): SpeedBps 334→156 + 3×≤142 (8.5.2)
- refactor(hud): GameTime 314→150 + 3×≤127 (8.5.2)
- refactor(hud): SystemTime 300→136 + 3×≤130 (8.5.2)
- refactor(hud): Coordinates 337→174 + 2×≤172 (8.5.2)
- refactor(hud): Armor 324→162 + 2×≤148 (8.5.2)
- refactor(hud): Inventory 476→153 + 3×≤181 (8.5.2)
- docs(todo): обновлён 8.5.2 — отмечены готовые гиганты (BetterButtons/InventorySwap/Svg/Темы/WorldParticles/Module/Chams и хвост)
- docs(api): папка combatant-client-26.2/docs + addons.md (API можно ломать при рефакторе)
- refactor(visuals): BlockHighlight 692→271 + 4×≤200 (8.5.2)
- refactor(mixins): GameRendererMixin 830→344 (хуки) + 4 хелпера (8.5.2)
- refactor(visuals): WorldParticles 821→260 + 6×≤200, режимы добиты (8.5.2)
- docs(todo): 8.5.2 — WorldParticles/GameRendererMixin/BlockHighlight отмечены ГОТОВО
- refactor(render): MeshBuilder 738→447 (фасад write-API) + 3 хелпера, FrameStats снесён (8.5.2)
- docs(todo): Фаза 10 — дизайн платформы (аккаунты/конфиги/аватарки), backlog RPC-чата
- docs(todo): Подсистема 3 — Аддоны 2.0 (скрипты/версии) + витрина аддонов + админка
- docs(todo): Подсистема 2 — RPC-чат/друзья/виджеты-телеметрия + рейт-лимиты, доп. правки Подсистемы 3
- chore: переименование combatant-client-26.2 -> mod, добавлены backend/ и frontend/
- docs(backend): implementation plan for accounts-service (Подсистема 1, часть 1)
- chore: пересоздать backend/frontend через cargo new и bun create vite + tailwind; убрать таблицу компонентов из TODO.md
- docs(backend): edition 2024 в плане вместо 2021
- chore: обновить версии до реально актуальных (проверено компиляцией)
- docs: правила платформы (лимит 250 строк, антипаттерны backend/frontend по итогам ресёрча) + frontend/ARCHITECTURE.md
- docs: добавить правило ≤4 файла на папку в правила платформы (backend/frontend)
- docs: commit messages in English from now on
- chore: gitignore .superpowers/ scratch directory
- docs: commit messages in English from now on (mod)
- feat(backend): scaffold accounts-service with health check
- chore(backend): remove target/ build artifacts from git, add gitignore
- chore: track docs/ in git (was accidentally gitignored, never committed)
- chore(backend): convert to Cargo workspace, plan full microservice layout
- feat(backend): accounts/avatars/device_links schema + migration
- feat(backend): argon2id password hashing
This commit is contained in:
loki5512344 2026-09-23 22:38:08 +02:00
parent ec10395511
commit 9d08fa910a
2239 changed files with 35265 additions and 28110 deletions

341
TODO.md
View file

@ -1,12 +1,21 @@
# LoVisual
Проект состоит из одного компонента — мода.
Проект: клиентский мод (`mod/`, бывш. `combatant-client-26.2/`) + платформа
(`backend/`, `frontend/` — сайт+бэкенд), см. Фазу 10/Подсистемы 1-3 ниже.
| Компонент | Стек | TODO |
|-----------|------|------|
| `combatant-client-26.2/` | Java 25, Fabric (MC 26.2) | [`combatant-client-26.2/TODO.md`](combatant-client-26.2/TODO.md) |
> Старый лаунчер (Rust + Slint) и старый backend (Rust + axum, другой дизайн)
> были удалены раньше (решение 2026-09-05) — `backend/` и `frontend/` сейчас
> строятся заново по дизайну из Фазы 10 / Подсистем 1-3, это не восстановление
> старого кода.
> Лаунчер (Rust + Slint) и backend (Rust + axum) удалены из проекта — развитие только мода.
> **Коммиты — на английском** (решение 2026-09-23, действует с этого момента
> вперёд; более старые коммиты на русском не переписываем).
> **TEMP-NOTE (удалить этот блок, когда сделано):** после окончания текущего
> рефакторинга владелец squash'ит историю git с 100+ коммитов до ~20, затем
> пушит и открывает репозиторий из приватного в публичный (сейчас приват —
> ранний код был спижен). Это план владельца, не техническая задача агента —
> просто держать коммиты чистыми/по делу до этого момента.
---
@ -20,6 +29,58 @@
---
## Правила платформы (`backend/`, `frontend/`) — исследовано 2026-09-23
> KISS/DRY/SOLID и "без пустышек, тесты обязательны" из правил мода действуют
> и здесь без изменений. Ниже — то, что отличается или уточняется под
> Rust/Axum и React/Vite, по итогам ресёрча актуальных практик и антипаттернов.
**Лимит на файл: ≤250 строк (не 200, как у мода)** — сознательно шире:
у мода лимит калибровался под Java (многословный синтаксис, много
boilerplate на класс), а в Rust/TypeScript тот же объём логики обычно
компактнее на строку, но модуль (структуры + impl + тесты в одном файле,
как в `backend/PLAN.md`) естественно тянет чуть больше. Фасады-делегаты —
то же исключение, что и у мода.
**Макс. 4 файла на папку** — то же правило, что у мода, без изменений:
превышение — сигнал выносить смысловую подпапку. Считаются только файлы,
подпапки в лимит не входят (проверено на живом коде мода: `addon/` — 0
файлов + 4 подпапки).
**Backend (Rust/Axum) — паттерны:**
- Модуль на домен (`auth/`, `accounts/`, `device/`, `avatars/`), а не по
техническому слою (`handlers/`, `models/`, `services/` вперемешку) —
так одну фичу не размазывает по всему дереву папок
- Repository-паттерн: SQL живёт только в `*/repo.rs`, хендлеры его не видят
напрямую (уже так в `backend/PLAN.md`)
- Собственный `enum AppError` вместо `String`/`anyhow` как типа ошибки на
границе HTTP — `String` теряет контекст и заставляет аллоцировать на
каждой ошибке
- Axum-экстракторы (`Json<T>`, `State<T>`) — валидация на границе запроса
компилятором, а не руками в теле хендлера
**Антипаттерны — сознательно избегаем:**
- `.unwrap()`/`.expect()` на данных из запроса — только `?` с конвертацией
через `From<...> for AppError` (уже в `Global Constraints` плана)
- Чрезмерный `.clone()` вместо ссылок — первый признак борьбы с borrow
checker'ом не по делу
- Блокирующий I/O внутри `async fn` (напр. синхронный файловый API) —
душит рантайм tokio целиком, а не только один запрос
- "Бог-структуры" (`AppState` со всем на свете) — состояние делится по
доменам (`AuthState`, `DeviceState`, `AvatarState` — уже так в плане)
**Frontend (React/Vite) — структура фичами, не по типу файла:**
Один из самых частых промахов — раскладывать по `/components`, `/hooks`,
`/services` вперемешку: фича размазывается по всему дереву, и удалить её
целиком нельзя, не зацепив другие фичи. Вместо этого — `src/features/<имя>/`
хранит свои компоненты+хуки+API-вызовы+типы вместе; общее (`src/shared/`)
только для того, что реально используют 2+ фичи. Детали — в
`frontend/ARCHITECTURE.md`.
Источники: [Rustify — Axum Guide 2026](https://rustify.rs/articles/rust-backend-development-axum-2026), [Rust FAQ — Common Anti-Patterns](https://www.rustfaq.org/en/common-rust-anti-patterns-and-how-to-avoid-them/), [Rust Design Patterns — Anti-patterns](https://rust-unofficial.github.io/patterns/anti_patterns/), [Robin Wieruch — React Folder Structure 2026](https://www.robinwieruch.de/react-folder-structure/).
---
## Статус
- [x] Фаза 1: Ребрендинг `combatant` → `lovisual`
- [x] Фаза 2: Выкинуть Runtime/JarReplacement + Panic
@ -27,15 +88,18 @@
- [x] Фаза 3.5: Удаление читерских модулей (легит-направление)
- [x] Фаза 4: Security (BackdoorProtection, StaffTracker)
- [x] Фаза 5: Нормализация модулей (ленивый Minecraft, GameClock)
- [x] Фаза 6: Тесты (EventBus, конфиг, утилиты) — см. `combatant-client-26.2/TODO.md`
- [x] Фаза 6: Тесты (EventBus, конфиг, утилиты) — см. `mod/TODO.md`
- [x] Фаза 7: Новые модули по мотивам SoupVisuals — ЗАВЕРШЕНА 2026-09-16 (Cosmetics
Overhaul 2026-09-11; Gps/HitBubbles/AmbientParticles/EndCrystal/Balancer/Trinket/
HandsShader — 2026-09-15…16, см. ниже)
- [ ] Фаза 8.5: Аудит структуры ≤200/≤4 — 8.5.1 (папки >4) ЗАВЕРШЕНА 2026-09-10
(0 папок >4, коммиты 8846d53…303b17e); далее 8.5.2 (гиганты >600),
план в `combatant-client-26.2/TODO.md`
план в `mod/TODO.md`
- [ ] Фаза 9: Оптимизация FPS — 9.1 (мёртвые грузы) ЗАВЕРШЕНА 2026-09-11 (137121e…4d60d3d);
9.2 Optimize гейтится Фазой 8.5, 9.3 A/B-мерка needs запуск на Windows
- [ ] Фаза 10: Платформа (сайт + бэкенд) — Подсистемы 1/2/3 спроектированы 2026-09-23
(см. ниже). Реализация: план backend Подсистемы 1 в `backend/PLAN.md`
(написан 2026-09-23), frontend/Подсистема 2/3 — планы позже.
---
@ -121,3 +185,266 @@
- [x] **Фикс ChinaHat** — текущая геометрия кривая (смахивает на самбреро/зонт);
переделать модель в нормальную конусную шляпу по референсу Soup
(`visuals/ChinaHat.java` + `ChinaHatModel.java`).
---
## Фаза 10: Платформа — сайт + бэкенд (дизайн от 2026-09-23)
> Прим.: лаунчер и старый backend (Rust+axum) были удалены ранее (см. шапку файла) —
> это НЕ восстановление старого кода, а новый дизайн с нуля под текущие требования.
> Статус: дизайн готов, реализация backend начинается — план в `backend/PLAN.md`.
> Код — в выделенных `backend/` и `frontend/` (созданы 2026-09-23), отдельно от `mod/`.
### Границы (декомпозиция)
- **Подсистема 1 (этот дизайн)**: аккаунты, облачные конфиги, аватарки, публичная
витрина конфигов.
- **Подсистема 2 (backlog, отдельный дизайн)**: RPC-чат (переименован из рабочего
названия "RVS" — отдельная чат-группа для владельцев клиента, не привязана к
конкретному minecraft-серверу), друзья/статус онлайн, виджеты-телеметрия друга
(HP/координаты/сервер/в бою или нет поверх HUD). Требует realtime-слой
(WebSocket/presence) — не влезает в REST-модель Подсистемы 1.
### Архитектура
Микросервисы с самого старта (осознанный выбор владельца проекта, не
default-рекомендация): `accounts-service`, `configs-service`, `chat-service`
(зарезервирован под Фазу 11), каждый — отдельный Rust/Axum процесс, общение
между сервисами по gRPC (tonic). Перед ними — отдельный **API Gateway**
(тоже Axum): единственная публичная точка (`api.<domain>`), терминирует TLS,
проверяет JWT один раз, роутит по gRPC во внутренние сервисы; сайт и мод не
знают о микросервисах и стучатся только в gateway. Для будущего chat-service
gateway дополнительно проксирует WebSocket (`wss://api.<domain>/chat/ws`).
Один физический Postgres-инстанс/порт, у каждого сервиса — своя отдельная БД
внутри этого инстанса (`accounts_db`, `configs_db`, `chat_db`) — логическая
изоляция без отдельных серверов на старте. Аватарки и файлы конфигов — в
S3-совместимом хранилище (MinIO), не в Postgres.
Фронтенд сайта: React + Vite + Tailwind CSS. Дизайн/палитра — плагин
`design-assets` (jezweb/claude-skills маркетплейс, skills `color-palette`,
`favicon-gen`, `icon-set-generator`, `image-processing`, `ai-image-generator`)
+ встроенный skill `frontend-design`.
### Модель данных
**accounts_db**
- `accounts`: id, email (unique), password_hash (argon2id), display_nick
(произвольный, НЕ привязан к minecraft-нику), created_at
- `avatars`: account_id, s3_key, uploaded_at
- `device_links`: id, account_id, device_token_hash, linked_at, last_seen —
привязанные устройства (моды), видны/отзываемы в личном кабинете
**configs_db**
- `config_slots`: id, account_id, slot_index (1..4 — фиксированный лимит на
аккаунт), name, data (jsonb), updated_at
- `share_codes`: config_slot_id, code (unique, криптографически случайный,
постоянный, владелец может регенерировать — старый код инвалидируется)
- `showcase_listings`: config_slot_id, title, description, published_at —
публичная витрина (фича из brainstorm-опроса)
Свой конфиг грузится по имени/индексу напрямую. Чужой — только по коду через
`%config load <код>` в игре (см. API ниже); одноразовость кода осознанно
отклонена — код обязан быть постоянным (ссылка-приглашение), пока владелец
сам не перевыпустит.
### Auth — два независимых пути
**Сайт**: обычный email + пароль.
- `POST /auth/register` {email, password, nick}
- `POST /auth/login` → access JWT (~15 мин) + refresh-токен (httpOnly cookie)
- `POST /auth/refresh`, `POST /auth/logout`
**Мод → аккаунт**: OAuth 2.0 Device Authorization Grant (как Twitch/PlayStation),
пароль в мод никогда не вводится.
1. Мод: `POST /device/code` → `{device_code, user_code, expires_in}`
(`user_code` короткий, напр. `ABCD-1234`)
2. Мод показывает `user_code` в настройках, игрок вводит его на
`site.<domain>/link` (уже залогинен на сайте) → `POST /device/confirm`
3. Мод параллельно поллит `POST /device/token` {device_code} каждые 2-3с
4. После подтверждения — долгоживущий `device_token` (хранится у мода
локально), используется как Bearer-заголовок ко всем запросам gateway
5. Личный кабинет → список привязанных устройств с `last_seen` и кнопкой
revoke (единичный отзыв, не ломает остальные устройства)
### API конфигов и аватарок
- `GET /configs` → 4 слота аккаунта (name, updated_at, share_code)
- `PUT /configs/{slot}` {name, data} → сохранить, last-write-wins
- `GET /configs/{slot}` → своё содержимое
- `POST /configs/{slot}/regenerate-code`
- `GET /configs/shared/{code}` → чужой конфиг по коду (код = секрет, отдельной
авторизации получателя не требует) — этот путь дёргает `%config load <код>`
- `POST|DELETE /configs/{slot}/publish` → витрина
- `GET /showcase?sort=new|popular&page=`, `GET /showcase/{listing_id}` —
публично, без авторизации; "скопировать к себе" → копия в свободный слот
- `POST /avatars` (multipart, ≤5MB, png/jpg/webp, валидация по magic bytes,
не по расширению) → ресайз до 256×256, S3 + CDN-URL в `accounts.avatar_url`
### Безопасность
- Rate-limit на `/auth/login` и `/device/*` (брутфорс / спам кодов)
- `password_hash` — argon2id
- `device_token` хранится хэшированным в БД, как пароль
- Share-код — криптографически случайный, не инкрементный ID (не перебираем)
## Подсистема 2: RPC-чат + друзья + виджеты-телеметрия (дизайн от 2026-09-23)
> Статус: спроектировано, реализация не начата. Требует realtime-слой
> (WebSocket + presence), поэтому — отдельный `chat-service` (см. Фазу 10,
> раздел "Архитектура"), а не REST как у Подсистем 1/3.
>
> Важно не путать с уже существующей в моде системой "друзей"
> (`NameProtect` и т.д., `features/module/modules/misc/protect/NameProtect.java`)
> — это **локальный** список ников на клиенте для защиты от пинга/подсветки,
> не связан с аккаунтами. Ниже — отдельная, новая система друзей на аккаунтах.
### Структура чата
Один глобальный канал ("все онлайн-владельцы клиента") + личные сообщения
1-на-1 только между взаимными друзьями. Без тематических каналов/групп —
не усложняем на старте.
- Только live-доставка, **без хранения истории на сервере** (как IRC) —
ушёл из чата/оффлайн — старое не восстановить. Упрощает `chat_db` до
почти пустой схемы (не нужна retention-политика, GDPR-очистка и т.п.)
- `chat_db` хранит только: список текущих online-подключений (для presence)
и friend-графа (см. ниже) — сами сообщения в БД не пишутся, гоняются
напрямую через WebSocket-хаб в памяти chat-service
### Друзья (новая, account-based система)
- Добавление — запрос/принятие по нику аккаунта (не по minecraft-нику):
поиск на сайте или в клиенте → `POST /friends/request` {target_nick} →
получатель видит входящий запрос (на сайте и/или в клиенте) →
`POST /friends/accept|decline`
- `chat_db.friendships`: account_id_a, account_id_b, status (pending/accepted),
created_at (симметричная связь после accepted)
- Личные сообщения в чате доступны только между accepted-друзьями
### Виджеты-телеметрия друга (HUD)
- Данные: HP, координаты, minecraft-сервер (адрес/имя), в бою (да/нет) —
каждое поле передаётся отдельно, не пакетом "всё или ничего"
- **Настраиваемый шаринг по полям** (выбор пользователя, приватность):
`chat_db.telemetry_sharing_prefs`: account_id, share_hp (bool),
share_coords (bool), share_server (bool), share_combat_state (bool) —
дефолт консервативный (напр. только HP+combat включены, координаты/сервер
выключены by default, т.к. чувствительны)
- Транспорт: тот же WebSocket-канал chat-service, отдельный тип сообщения
`telemetry_update` (broadcast только подписанным взаимным друзьям, с
фильтром по `sharing_prefs` получателя-источника на сервере — клиент
никогда не получает поле, которое источник не разрешил шарить)
- Отображение — новые draggable HUD-элементы по образцу существующих
(`registerDraggableHudElement`), один виджет на друга или агрегированный
список — решить на этапе UI-дизайна перед реализацией
### Presence (онлайн-статус)
- Мод коннектится к `wss://api.<domain>/chat/ws` с `device_token` при старте
клиента (не требует отдельного логина — токен уже есть после device-link
из Подсистемы 1)
- `online` = есть открытый WebSocket; `last_seen` обновляется на disconnect
- Список друзей на сайте/в клиенте показывает online/offline живым статусом
через тот же сокет (site тоже может подключиться, если залогинен — видеть
друзей онлайн из браузера)
### Профили/статусы (лёгкая версия, backlog внутри backlog)
Идея из brainstorm-опроса ("уровень/бейджи/тайтлы", "давно с нами", "шарил N
конфигов") — не проектируется в деталях сейчас, т.к. это чисто косметическая
надстройка без технических рисков — легко добавить в `accounts_db` (поля
`created_at` уже есть, `badges` — просто jsonb-массив) на любом этапе позже,
не блокирует остальную Подсистему 2.
### Рейт-лимиты Подсистемы 2 (дополнение к общей таблице в Фазе 10)
| Действие | Лимит |
|---|---|
| Отправка сообщения (глобальный канал) | 1 / сек / аккаунт, burst 5 |
| Отправка личного сообщения | 2 / сек / аккаунт |
| `POST /friends/request` | 20 / час / аккаунт (защита от спама заявками) |
| `telemetry_update` (от клиента к серверу) | не чаще 1 раз / 2 сек / аккаунт — сервер троттлит и досылает клиентам с той же частотой, не завязан на тик-рейт игры |
---
## Подсистема 3: Аддоны 2.0 + витрина аддонов (дизайн от 2026-09-23)
> Статус: спроектировано, реализация не начата. Расширяет существующий Java
> addon-API (`docs/api/addons.md`, `addon/core/AddonManager.java`) — тот API
> и дальше можно ломать (решение владельца 2026-09-05, см. верх файла), новый
> скриптовый слой — надстройка сбоку, не замена.
### 1. Скриптовые аддоны (новый тип, для витрины)
- JavaScript через GraalVM, отдельный от Java-`AddonManager` загрузчик рядом
с `addon/core`
- Точка входа — `manifest.json` (не `fabric.mod.json`: скрипт-аддону не нужен
Fabric-loader): `id, name, version (semver), author, tags, entry.js`
- Ограниченный whitelisted API — обёртки над тем же, что доступно
Java-аддонам сейчас: `registerModule` (JS-класс с полями-настройками),
`registerCommand`, `addRenderCallback` (только 2D HUD-отрисовка). Никакого
прямого доступа к рефлексии/файловой системе/сети — весь I/O только через
явно предоставленные хелперы контекста
- Java-аддоны (текущие Fabric-моды) остаются для продвинутых случаев
(мировой рендер, миксины), устанавливаются вручную; в витрине идут отдельным
помеченным разделом от скриптовых
### 2. Больше точек расширения в существующем Java API
Добавить в `LoVisualAddonContext`:
- Доступ к событиям (input, world tick, packet)
- Кастомные конфиг-виджеты в ClickGui
- Регистрация собственных рендер-пайплайнов (не только callback)
- API для чтения/записи через облачную конфиг-систему Подсистемы 1
### 3. Метаданные / версии / автообновления
- `manifest.json` получает `version` (semver) — и у скриптовых, и у Java-аддонов
(в `custom.lovisual:addon` добавляется поле)
- `AddonManager` при старте сверяет установленную версию с версией на сайте
(если аддон пришёл оттуда — манифест хранит `source_url`/`addon_id`) →
бейдж "обновление доступно" в `LoVisualAddonManagerScreen`; апдейт — по
клику пользователя, никогда не втихую
### 4. Витрина аддонов на сайте
- Публикация обязательно требует исходники (архив или ссылка на репозиторий)
— открытость как единственный механизм доверия. Читерские аддоны **не
блокируются** (решение владельца) — пользователь сам выбирает, что качать
- Теги: обязательный `type: cheat | legit`; необязательные категории
(мультивыбор, для фильтров, не для модерации): `visuals | hud | new-module
| utility | qol | other`
- Данные: `addons_db` (отдельная БД в том же Postgres-инстансе, как
`accounts_db`/`configs_db`) — таблицы `addons` (id, owner_account_id,
latest_version, tags[], type, source_url, downloads_count) и
`addon_versions` (addon_id, version, changelog, file_s3_key, published_at)
- Скачивание — прямая ссылка на файл/архив в S3; установка не автоматическая
и не тихая — юзер жмёт "добавить" в клиенте, клиент качает и кладёт в
addons-папку, но не запускает без явного включения в `LoVisualAddonManagerScreen`
### 5. Админка на сайте (`/admin`)
- Новая роль `accounts.role: admin` (по умолчанию `user`); доступ к
admin-эндпоинтам проверяется на gateway, не на фронте — обычные
пользователи даже не видят `/admin` в UI
- Модерация аддонов: снять с публикации / пометить проблемным (не за
cheat/legit — только за реальные нарушения: сигналы о malware от юзеров,
нерабочие/поддельные ссылки на исходники, спам) / **удалить** аддон целиком
(со всеми версиями, если, например, DMCA или явный malware)
- `accounts.can_publish_addons: bool` (default `true`) — отдельный флаг,
отзываемый админом независимо от общего бана аккаунта: юзер продолжает
пользоваться сайтом/конфигами/чатом, но не может публиковать новые аддоны
(существующие остаются видны, если явно не удалены отдельно)
- Модерация витрины конфигов (Подсистема 1): аналогично, скрыть листинг
- Управление аккаунтами: бан/разбан (полный), отзыв `can_publish_addons`,
просмотр жалоб
- Статистика: кол-во аккаунтов/аддонов/конфигов, загрузок за период; график
регистраций по дням/неделям/месяцам; активные аккаунты DAU/MAU (по
`device_links.last_seen` + активности на сайте)
### 6. Рейт-лимиты (сквозной механизм, все Подсистемы)
Реализуются на **API Gateway** (единая точка — не дублируются в каждом
сервисе), token bucket на Redis (или in-memory при однонодовом деплое на
старте), ключ — `account_id` для авторизованных запросов, `IP` для
неавторизованных (регистрация, логин).
| Эндпоинт(ы) | Лимит | Почему |
|---|---|---|
| `POST /auth/login` | 5 / мин / IP | брутфорс пароля |
| `POST /auth/register` | 3 / час / IP | спам-аккаунты |
| `POST /device/code`, `/device/token` | 10 / мин / IP | спам device-кодов |
| `PUT /configs/{slot}` | 20 / мин / аккаунт | защита БД от спама сохранений (мод может дёргать часто) |
| `GET /configs/shared/{code}` | 30 / мин / IP | перебор share-кодов |
| `POST /avatars` | 5 / час / аккаунт | не файлопомойка |
| `POST /addons` (публикация версии) | 10 / день / аккаунт | не спамить витрину |
| `GET /showcase`, `GET /addons` (листинги) | 60 / мин / IP | защита от скрейпинга/DoS чтения |
| Chat WebSocket: отправка сообщения | 1 / сек / аккаунт (burst 5) | защита от флуда в RPC-чате |
Превышение → `429 Too Many Requests` с `Retry-After`; для WebSocket —
локальный drop сообщения с уведомлением клиенту, без разрыва соединения.