1. Что делаем и чего не трогаем
Сейчас создана только новая рабочая ветка и этот план. Прикладной код не удалён, деплой не запускался, базы не менялись. Рабочая копия: /opt/github/hmkteam/verse-handoff. Основная копия /opt/github/hmkteam/verse не переключалась.
Базовый вариант: сначала сохранить работающие HTTP-контракты. Удаление самих функций продукта, способов входа, партнёрских API или страниц требует отдельного решения. Наличие ручки в коде не доказывает, что ею пользуются.
Анализ выполнен по исходникам main на указанном коммите. Фронтенд стейджа ранее деплоился из ветки agreement: перед совместным релизом нужно проверить его отличия. Частоту вызовов по логам и поведение всех ручек на живом сервере в рамках этого анализа не проверяли.
2. Как перенос работает сейчас
Пользователь проходит авторизацию.
POST /verse/sync
Если профиль уже есть, используем его.
POST /api/migrate/user
API-ключ + authUserId.
Ответ очищается по фиксированному списку разрешённых полей.
Импортированный пользователь сохраняется в verse_user.
Если источник ответил без пользователя, создаётся новый.
Ошибка источника не подменяется пустым профилем.
Импорт разрешён при appName !== 'Verse_Prod'. На исходном проде это условие сохраняем. Сейчас адрес берётся из BITQUEST_VERSE_API_URL, путь из VERSE_MIGRATION_SOURCE_API_PATH. Название Bitquest в конфиге и функциях историческое и само по себе не определяет источник.
Для стейджа ранее был настроен источник https://api-verse.hamsterkombatgame.io и путь /api/migrate/user. В коде значение по умолчанию для пути всё ещё /api/verse/user: в новой установке это нужно исправить, не полагаясь на старые ENV.
/sync остались обращения к interlude_user_mini и season2_tokens. Первая ветка снимает поля рассчитанного золота при выполнении условия пересчёта; вторая устанавливает tokenSeason2Claim = 0 и пересчитывает потенциальное золото. Перед удалением коллекций заменяем эти ветки нормализацией импортированного профиля и проверяем сохранность значений.Целевой путь: вход → локальный профиль либо один импорт → проверка отдельного входного DTO → явное преобразование в компактную модель → сохранение → ответ. Никакого повторного обращения к старым сезонным таблицам. Параллельные первые запросы не должны перезаписывать уже созданный профиль или повторять награды.
Два разных изменения: убрать поля из хранения на стейдже можно преобразованием ответа на стороне получателя. Чтобы они вообще не пересекали границу передачи, нужно отдельно сузить экспорт на источнике. Это изменение прода не входит в текущие действия и требует согласования совместимости.
Основания: backend/src/hid/http.ts:123, backend/src/hid/logic.ts:176, backend/src/migration/http.ts:15, backend/src/Config.ts:98, backend/src/hid/interlude/migrate.ts:75.3. Что делать с полями
| Группа и поля | Сейчас | План |
|---|---|---|
id, createdAt, updatedAt, lastSyncAt | Входят в экспорт. Ключ профиля используется при авторизации и sync. | Сохранить. Отдельно определить исходные и локальные даты. Проверить соответствие ID для Telegram и объединённых аккаунтов; не переносить старые сессии. |
totalDiamonds, balanceDiamonds, earnPassivePerHour | Входят в экспорт; часть показателей отображается в старых сезонных экранах. | Не удалять автоматически. Проверить нужность самих экранов. Профиль Season 1 подключает InterludeResult, поэтому это не просто забытые типы. |
tokenBalance | tokens_passive, tokens_keys, tokens_friends, tokens_achivements, tokens_tasks, total, claimed, unclaimed, nextUnlocked | Сохранить расчёты и нужные экрану значения. claimed / unclaimed / nextUnlocked участвуют в золоте; детализацию удалять только вместе с её UI-потребителями. |
totalHamsterGold1..3, | Балансы по играм и сезонам. Возвращаются в HTTP-профиле и партнёрском API. | Сохранить. Не суммировать и не обнулять. Нули и отсутствие значения тестировать отдельно. |
potentialToken, potentialHamsterGold, | Потенциальные суммы конвертации; часть вычисляется при sync. | Сохранить смысл. Выбрать один источник расчёта и проверить результат до и после очистки. |
rewardState, rewardGoldState, rewardGoldStateSeason2 | Флаги по boost, gamedev, fightclub, king, paws. | Сохранить. Не терять историю получения наград и не создавать возможность повторного получения. |
rewardGoldFrontState | Входит в схемы и экспорт; в выполненном поиске бизнес-потребитель не найден. | Кандидат. Перед удалением проверить внешних клиентов: поле доступно через HTTP. |
tokenSeason2, tokenSeason2Claim | token1..4, token5?, tokenTotal и флаг: отсутствует / 0 / 1. Используются сезонной логикой. | Сохранить право на конвертацию. Убрать зависимость от таблицы season2_tokens, но не обнулить суммы и состояние уже полученной награды. |
withdraw.info | Уже исключён из текущего экспорта. Внутренняя модель всё ещё содержит UID, адрес, memo, on-chain данные, подпись и хеш транзакции. | Убрать из целевой модели и из общих типов, если других оставленных потребителей нет. Не включать в fixtures или логи. |
withdraw.round, round2, unavailable, transactionRechecked, transactionRecheckedSync | Сейчас передаются. В просмотренной основной логике sync / конвертации не используются для расчёта. | Предложение: исключить из новой модели. Сузить экспорт на проде только отдельным согласованным изменением; проверить клиентские контракты. |
withdraw.state, selected | 25 + SaveInGame участвуют в расчёте золота. Фронт показывает название биржи для состояний 18,19,20,22; 22 означает Bybit. | Нельзя просто удалить. Предлагается нейтральное право на конвертацию и нейтральный UI без биржи. Эквивалентность расчёта подтвердить тестами. Это предложение, а не уже согласованный контракт. |
tokenRechekedAt, tokenRechekedAt2, tokenRechekedAt3 | Все передаются. Третий ограничивает старый пересчёт в sync; первые два в просмотренной основной логике не читаются. | Убирать вместе со старым пересчётом. Не оставлять путь, который без маркера повторно изменит баланс. |
migrateBqAt | Не экспортируется: получатель ставит локально. | Заменить локальной меткой импорта с нейтральным названием. Не считать её исходной датой регистрации. |
Формула, которую нельзя потерять: если unclaimed === 0, берём claimed только для state = 25 и selected = SaveInGame; иначе берём unclaimed. Прибавляем nextUnlocked и умножаем на 15. Для второго сезона используется tokenTotal × 15.
4. HTTP-контур и новая база
| Контур | Найдено в коде | Решение для первого этапа |
|---|---|---|
| Пользовательский Verse | 9 маршрутов: конфиг, sync, токен вендора, награды, две конвертации, партнёрские балансы. | Сохранить. Отдельные DTO для импорта, хранения и HTTP-ответов: сейчас в нескольких местах возвращается широкий VerseUser. |
| Авторизация | 14 маршрутов, включая Telegram, TON, device ID, Google, объединение аккаунтов и административный вход. | Сохранить необходимые способы входа. Удаление TON-типов вывода не означает, что можно удалить TonAuth. Проверить и ограничить служебные способы входа. |
| Партнёры и лидерборды | 2 vendor + 31 leaderboard/PVE. | Сохранить до решения по партнёрам. Исторические рейтинги не мигрируются этой ручкой; схемы нужны для новых событий. |
| Админка | 8 маршрутов, в том числе чтение raw, изменение профиля, удаление, speed-test и просмотр источника. | Состав пересмотреть отдельно. Диагностические и опасные методы не передавать автоматически только потому, что они HTTP. |
| Telegram | Webhook и зарегистрированные обработчики платежа / возврата; бот также читает cbt_user. | Не удалять бота и платежные типы без решения о поддержке этих сценариев. Ручка создания invoice в hid/http закомментирована: её не включали в 69 маршрутов. |
| Экспорт миграции | 1 API: /api/migrate/user. | На исходном проде оставить. На получателе необходимость собственного экспорта согласовать отдельно; импортный клиент ему не равнозначен. |
| Эксплуатация | /, /health, /metrics; отдельно Swagger и static-plugin. | Оставить необходимые проверки здоровья и метрики. Swagger / playground ограничить или убрать из передаваемого внешнего контура. |
Что не входит в текущий перенос
Ручка читает только профиль verse_user. Она не переносит auth-коллекции, историю инвойсов, рейтинги, newsletter, подписчиков и кэши. Это не обещание, что все эти сущности можно удалить из кода: часть заполняется заново, часть требует настройки или отдельного решения.
Удаление старых таблиц: interlude_user_mini и season2_tokens становятся кандидатами после замены sync. users1_cache / users2_cache / users3_cache помечены TODO, но всё ещё упоминаются в leaderboard-модулях. По комментарию удалять нельзя.
Инфраструктура: App.create требует Kafka и ClickHouse; HTTP-лидерборды и аналитика получают эти сервисы. На первом этапе сохраняем их подключение. Отказ от них является отдельным изменением архитектуры, а не удалением «лишних» файлов. Новый бот, ключи партнёров, внешние магазины и расписания требуют явной настройки на пустой базе.
Полный список 69 явных маршрутов с местом в коде
69 маршрутов
Количество получено из TypeScript AST в подключённых к App.ts модулях. Закомментированные ручки не учтены. Маршруты, автоматически добавляемые Swagger, static и CORS, в число 69 не входят.
Основания: backend/src/App.ts:30, backend/src/Db.ts:20, backend/src/auth/AuthDbSchema.ts:19, backend/src/tg/TelegramBot.ts:393, backend/src/auth/authUtils.ts:140.5. Что чистим в репозитории
| Область | Действие | Условие |
|---|---|---|
| Старые сезонные миграции | Убрать raw-типы, нормализаторы, interlude/migrate и регистрацию исходных таблиц после перевода sync. | Проверить все импорты: migrateGold вызывается из refreshGold; этот операторский скрипт тоже выводится из поставки. |
| Операторские скрипты | Не включать в продукт getStarsTxs*, tgLogin, рассылки, txRefundRecovery, txUpRecovery, refreshGold и отладочные файлы. | Это отдельные точки запуска, не runtime HTTP. Нужные текущему владельцу инструменты сохранить отдельно; не стирать историю отчётов. |
| Вне статического графа API | Кандидаты: hid/applyCommand.ts, hid/interlude/types.ts, tg/tonWallet.ts, src/test.ts, src/ttt.ts. | Проверить сборку, frontend, тесты и скрипты. Файл вне графа не равен доказанно неиспользуемому файлу. |
| Списки и старые настройки | Убрать персональные whitelist, старых администраторов, закомментированные ключи, старые домены и фиксированные настройки прежнего владельца из поставки. | Не публиковать сами значения. Новые настройки задавать явно; при обнаружении действующих ключей заменить их и проверить историю Git. |
| Широкие типы и зависимости | После удаления потребителей сузить protocol, Db, HidDb, конфиги и package.json. | Не удалять пакет по названию. Например, TON используется действующей авторизацией; TDLib-клиент может уйти вместе с операторскими скриптами. |
| Логи | Убрать дампы профиля до/после, raw сезонных записей и тело ответа источника из сообщений ошибок; оставить коды и безопасные метаданные. | Не потерять диагностику auth/sync. Секреты и initData не логировать. |
| Фронтенд и документация | Править сезонные экраны, переводы, store и HTTP-клиент вместе с моделью. Переписать README под запуск на чистой базе. | Не оставлять скрытые обращения к удалённым полям. Отдельно согласовать состав игр и старых сезонных экранов. |
Статический ориентир: из backend/src/index.ts прослежены 77 локальных TypeScript-файлов, включая импорты типов. Это не полный анализ неиспользуемых символов: отдельные CLI, Nuxt auto-import, сборочные команды и внешних HTTP-клиентов нужно проверять дополнительно.
6. Последовательность реализации
- Изолировать стейдж
Запретить для handoff-ветки prod-цели во всех workflow: API, frontend, CDN, consumer, ClickHouse. Проверять branch + environment до получения prod-доступов. Зафиксировать stage bucket/scope/release, prod-настройки не менять. Сейчас этого запрета ещё нет.
- Зафиксировать контракт передачи
Утвердить поля из раздела 3, HTTP-ручки и UI. По умолчанию сохранить действующее поведение. Выделить Migration DTO и тесты, запрещающие лишние поля на каждом уровне вложенности.
- Переписать импорт и sync
Отделить адаптер ответа прода от внутренней модели, убрать обращения к старым таблицам. Настроить таймаут, обработку ошибки, проверку ID, безопасное создание при конкурирующих sync и локальную отметку импорта. Различать «пользователь не найден» и «источник недоступен».
- Сузить модель и обновить потребителей
Убрать ненужную историю вывода после замены расчётов и UI. Сохранить балансы, начисления и флаги получения. Обновить ответы клиенту и партнёрам, не расширяя экспорт продового источника.
- Удалить недостижимый код и старые данные из поставки
Убрать исходные сезонные модели, ненужные CLI, отладочные данные, whitelist, старые настройки. Затем удалить ненужные зависимости, типы, коллекции и команды сборки. Обновить тесты и документацию.
- Проверить на отдельной чистой stage-базе
Не очищать даже общий стейдж автоматически. Использовать выделенные тестовые scopes, новый бот или согласованные credentials. Прогнать контракты, миграцию, UI и повторные запросы; затем согласованный stage-only релиз.
- Подготовить пакет новой команде
Проверенный снимок без секретов, дампов и старой Git-истории, ENV-пример без значений, перечень сервисов и HTTP-контрактов, инструкция запуска и результаты тестов.
Что нужно решить до удаления функций
- Оставляем ли старые сезонные экраны и биржевую историю, либо заменяем их нейтральным игровым состоянием? Сейчас предложен второй вариант, но он не утверждён.
- Какие способы входа, партнёры, лидерборды и Telegram-платежи входят в передаваемый продукт?
- Можно ли отдельно менять экспорт на проде, если поля должны отсутствовать не только в хранении, но и в самом ответе источника?
7. Как поймём, что очистка завершена
- Миграция: существующий пользователь, отсутствующий профиль, неправильный ключ, таймаут, 5xx, неполный/лишний JSON, несовпадающий ID. При ошибке не создаётся пустой пользователь.
- Идемпотентность: второй sync использует локальный профиль; одновременные первые sync не затирают изменения; повторная конвертация и выдача награды не создают дополнительный баланс.
- Суммы: одинаковый результат до/после на синтетических профилях: SaveInGame, завершённый вывод, unclaimed, vesting, ноль, отсутствующее поле, оба сезона, уже полученные награды.
- Чистая база: приложение стартует без старых сезонных таблиц; новые нужные коллекции создаются. Проверены авторизация, sync, профиль, партнёрские запросы, leaderboard и webhook для утверждённого состава.
- Граница данных: исключённые поля отсутствуют в ответах, локальных документах и логах; список export-полей не зависит от расширения VerseUser.
- Качество: Biome, typecheck затронутых workspaces, unit/e2e и frontend build; визуальная проверка изменённых страниц.
- Изоляция: workflow из handoff-ветки отказывается деплоить на prod; исходный прод по-прежнему не импортирует пользователей. Финальная поставка проверена на секреты и старые данные.