# WealthLedger backend NestJS API WealthLedger. Backend владеет auth, SQLite-данными и server-side интеграциями. Потребители используют публичный HTTP API и OpenAPI artifact; их репозитории не нужны для разработки, сборки и проверок backend. ## Быстрый старт Требуется Node 24 (`.nvmrc`). Создайте `.env` из примера, замените placeholder секреты и установите зависимости строго из lockfile: ```bash cp .env.example .env nvm use --silent npm ci npx prisma migrate deploy npm run start:dev ``` `JWT_SECRET` должен быть секретом не короче 32 символов, а `TOKEN_ENCRYPTION_KEY` — base64-кодированным 32-байтным ключом. Не добавляйте T-Bank token в `.env`: он вводится пользователем и хранится в зашифрованном виде. ## Контейнерный image `Dockerfile` имеет targets `development` (Nest watch, generated Prisma client и Linux native modules) и `production` (compiled Node process от UID 1000, Prisma CLI/migrations и T-Bank proto assets). Node соответствует `.nvmrc`. Build context ограничен `.dockerignore`; `.env`, local DB и host node_modules не попадают в image. ```bash docker build --target development -t wealthledger-backend:development . docker build --target production -t wealthledger-backend:production . nvm use --silent npm run test:container -- wealthledger-backend:production ``` Gitea `image.yml` запускается на защищённом release tag `vX.Y.Z`, проверяет package/lock version и canonical gates. Runner `ubuntu-latest` должен иметь Docker/buildx. Образы хранятся в Nexus: `nexus.personal.ksv741.ru/docker-hosted/wealthledger/backend`. Пользователь `wealthledger-ci` задаётся Actions secrets `NEXUS_USERNAME`/`NEXUS_PASSWORD`; эта же учётная запись используется сервером для загрузки образов. Docker endpoint и token realm должны использовать HTTPS; в `docker-hosted` включается запрет redeploy. Прежние Gitea secrets сохраняются на время перехода и не передаются в Nexus. Публикуются semver tags, без `latest`; существующие release tags не перезаписываются. Production выбирает опубликованный version tag. Compose topology принадлежит workspace, эксплуатационная последовательность — [документация](https://git.personal.ksv741.ru/WealthLedger/docs), страница `architecture/deployment.md`. Проверка публикации требует настроенного registry/runner; локальный build не доказывает package permissions. ## Проверка Единый completion gate не изменяет versioned-файлы: ```bash npm run verify ``` Он запускает zero-warning lint, application/test typechecks, Jest с покрытием и build. `npm run lint:fix` — отдельная записывающая команда. `npm run test:cov` также доступен отдельно; общего числового threshold нет. `npm run test:e2e` отдельно проверяет функциональные HTTP suites на новых базах и соответствие генерируемого OpenAPI настроенному приложению. Для полной backend-проверки выполните обе команды. Тяжёлый synthetic benchmark с 300 000 операций и 1 000 000 свечей запускается отдельно командой `npm run test:performance`: его wall-clock budget зависит от выделенного runner CPU и не является функциональным merge gate. Nest 12 поставляется как ESM. Скрипты Jest включают `--experimental-vm-modules`, включая watch/coverage/debug; SQLite runner включает тот же флаг. TypeScript 6 использует явные `rootDir` и типы Node/Jest в `tsconfig.json`. Переход на TypeScript 7 требует поддержки со стороны `ts-jest` и `typescript-eslint`. ### CI и обновления зависимостей Единый `.gitea/workflows/verify.yml` запускает `commits → security → verify` последовательно на PR; на push в `develop`/`main` — `security → verify`. Рабочие ветки проверяются после открытия PR, без дублирующего push-запуска. Изменение заголовка PR тоже повторяет проверки Conventional Commits. Новый запуск отменяет устаревший для того же PR/ветки и типа события (требуется Gitea 1.26+). Ежедневно в 05:47 UTC выполняется только `security`. Job `verify` запускает `npm ci`, генерацию Prisma client, `npm run verify` и функциональный `npm run test:e2e`. Используется только checkout backend и его зависимости; OpenAPI генерируется во время проверки, SHA записывается в лог. Успешный unit gate не заменяет SQLite/e2e; ошибка любой suite завершает job с FAIL. Job `security` сканирует всю Git-историю Gitleaks 8.30.1 с полностью скрытыми значениями, блокирует любой runtime advisory через `npm audit --omit=dev --audit-level=low` и high/critical findings полного дерева через `npm audit --include=dev --audit-level=high`. Moderate advisory `sprintf-js` остаётся только в Jest coverage tooling; исправленной версии пока нет, а предложенный npm downgrade `ts-jest` несовместим с текущим test stack. `.gitleaksignore` перечисляет только проверенные исторические совпадения: детерминированные fixtures, placeholder и badge из Nest starter. Новые коммиты этими исключениями не покрываются. Имена jobs `commits`, `security`, `verify` сохранены, но Gitea включает имя workflow в статус. При внедрении администратор должен заменить required checks для защищённых веток на `Backend verify / commits (pull_request)`, `Backend verify / security (pull_request)` и `Backend verify / verify (pull_request)`, удалив старые имена и push-статусы, если они требовались. Все три PR-проверки должны оставаться обязательными. Публикация образов по тегам остаётся в `image.yml`. Последовательность jobs ограничивает один запуск; для ограничения нагрузки всего сервера настройте `runner.capacity: 1` у общего runner и учтите остальные зарегистрированные runners. Конфигурация runner управляется вне этого репозитория. Renovate группирует minor/patch updates, а major требует согласования в Dependency Dashboard. Автоматический merge не включён. Прямые зависимости фиксируются точными версиями: `.npmrc` задаёт `save-exact=true` для npm install, Renovate использует `rangeStrategy: pin`. Обновляйте `package.json` вместе с `package-lock.json`. Overrides в `package.json` закрывают advisories транзитивных `multer`, `deepmerge-ts` и `mysql2`; удаляйте их, когда родительские пакеты примут исправленные версии. Совместимость Prisma override проверяется генерацией и SQLite migrations. Required checks настраиваются отдельно в Gitea: наличие workflow само по себе не блокирует merge. Общий `tsconfig.json` включает `strict: true` и `noFallthroughCasesInSwitch` для приложения, тестов и сборок. ESLint запрещает non-null assertions (`value!`) и definite-assignment assertions (`field!: Type`). Поля DTO, заполняемые вне конструктора, описываются через `declare`: входные значения заполняет и проверяет Nest, ответные DTO описывают форму возвращаемых объектов. `declare` не создаёт значение и не заменяет runtime-валидацию. В обычном коде нужны инициализация или сужение типа после проверки значения. ## ESLint `npm run lint` проверяет весь authored TypeScript и JavaScript, включая scripts и configs. `npm run lint:fix` (также `npm run format`) применяет безопасные автоисправления. Форматирование принадлежит ESLint Stylistic: два пробела, одинарные кавычки и точки с запятой; Prettier не используется. Профиль: ESLint recommended, typescript-eslint recommendedTypeChecked, Node, NestJS typed, Jest, import-x с TypeScript resolver, SonarJS, RegExp, Security, Perfectionist recommended-natural и Stylistic customize. NestJS проверяется в production TS, Jest — в specs/e2e; generated Prisma, dist и coverage исключены. DI imports сохраняют runtime metadata. Исключения конфигурации: private backend не публикуется в npm; TS imports разрешает import-x вместо Node resolver; IsIn проверяет domain unions. Автосортировка объектов, классов, модулей и декораторов отключена, поскольку порядок может влиять на runtime. Остальные точечные исключения объясняют false positives непосредственно в коде. `eslint-suppressions.json` — штатный ESLint baseline только для существующих SonarJS cognitive-complexity, no-nested-conditional и no-nested-template-literals. Это отложенный refactor, а не исправленные нарушения. Baseline ограничен файлами и количеством сообщений; новые файлы и превышение количества блокируют lint. Он не защищает от усложнения уже учтённой функции при прежнем количестве сообщений — это остаётся предметом review. При сокращении долга обновляйте baseline командой `npx eslint . --prune-suppressions`; не пересоздавайте его для обхода новых ошибок. Security, TypeScript и Jest в baseline не включены. Рост baseline — изменение quality gate: он требует отдельного обоснования и review затронутого поведения. При рефакторинге сохраняйте или сокращайте baseline; прежнее число сообщений не доказывает прежнюю сложность функции. ## SonarQube Проект `wealthledger-backend` на `https://sonarqube.personal.ksv741.ru` использует `sonar-project.properties`. Анализируются исходники `src`; unit specs и HTTP specs учитываются как тесты. Внешние T-Bank proto contracts исключены, generated Prisma client находится за пределами `sonar.sources`. `npm run verify` запускает unit tests с покрытием один раз. Jest штатно создаёт `coverage/lcov.info`; scanner импортирует этот отчёт. Покрытие HTTP/e2e tests в него не входит, сами HTTP suites остаются обязательным отдельным шагом CI. Создайте в SonarQube проект с ключом `wealthledger-backend`, а в Gitea → backend → Settings → Actions → Secrets добавьте `SONAR_TOKEN` с правом анализа этого проекта. Токен не сохраняется в файлах. Community Edition 9.9.8 получает анализ только при push в `develop`, после успешных `verify` и SQLite/e2e checks. CI ожидает Quality Gate; отрицательный результат или ошибка scanner завершает job с ошибкой. PR и другие ветки проходят штатные проверки без отправки анализа. Переименование основной ветки SonarQube не требуется; `sonar.branch.name` не задаётся. Runner должен поддерживать Docker actions и иметь доступ к GitHub, Docker Hub и HTTPS-адресу SonarQube с действующим сертификатом для этого имени. Официальный Docker action закреплён на commit версии 2.3.0, как в frontend. `tsconfig.sonar.json` содержит совместимые со старым анализатором настройки модулей и decorators с target ES2022, без новых compiler options приложения. Штатная компиляция приложения остаётся на ES2023. После обновления сервера пересмотрите scanner и необходимость отдельного tsconfig. Локально после `nvm use --silent`, `npm ci` и генерации Prisma client выполните `npm run verify`, затем `sonar-scanner` с совместимым SonarScanner CLI и `SONAR_TOKEN` в окружении. Первый запуск CI после merge должен подтвердить загрузку отчёта и покрытия: локальные проверки не доказывают доступность сервера. ## Документация - [AGENTS: правила и источники](AGENTS.md) - [Backend architecture](.ai/architecture.md) - [HTTP API](.ai/conventions/http-api.md), [data](.ai/conventions/data.md) и [testing](.ai/conventions/testing.md) - [Security/auth](.ai/conventions/security-auth.md) и [integrations](.ai/conventions/integrations.md) - [Генерация OpenAPI](#генерация-openapi) Генерация без сервера сверяется с OpenAPI настроенного приложения: ```bash npm run test:openapi-contract ``` ## Генерация OpenAPI ```bash nvm use --silent npm run openapi:generate ``` Команда собирает backend и записывает `openapi.json` в корень репозитория атомарно: через временный файл и rename, без частичной записи при сбое. `npm run openapi:check` только сравнивает runtime schema с файлом и завершается с ошибкой при расхождении, не перезаписывая его. Сервер и HTTP-порт не запускаются; Nest preview читает metadata без создания провайдеров и подключения к БД/T-Bank. Настроенный `.env` и реальные секреты не нужны. `openapi.json` исключён из Git и каждый раз создаётся из DTO/Swagger metadata; передай полученный artifact потребителю отдельно. `npm run test:openapi-contract` собирает backend и сравнивает результат генератора с runtime schema настроенного приложения, используя временный каталог и SQLite. `npm run test:e2e` также сначала собирает backend. Изменение публичного API проверяй relevant HTTP tests: без versioned snapshot сравнение не отслеживает breaking changes относительно предыдущего выпуска. Разработка идёт напрямую по задаче пользователя; OpenSpec/SDD — по явному запросу. ## Реальная SQLite и сохранность данных После `nvm use --silent` запускайте `npm run test:sqlite` при изменении auth, карточных операций или DB boundary, `npm run test:e2e` — для всех HTTP suites. Обе команды используют один runner: отдельный каталог `wealthledger-e2e-*` в системном temp на каждую suite, один `DATABASE_URL` для migrations и Jest. Внешний `DATABASE_URL` заменяется временным; `.env` и пользовательские данные не изменяются. Прямой Jest без runner отклоняется до обращения к базе. При ошибке migration/Jest или SIGINT/SIGTERM runner останавливает дочерние процессы, удаляет свой каталог и возвращает ненулевой exit code. Нужен доступ к локальным HTTP-портам; sandbox может его запрещать. `prisma.config.ts` удаляет унаследованный `RUST_LOG` только в процессе Prisma CLI: на Prisma 7.9.1 уровни `warn`/`error` вызывают пустой `Schema engine error` при fresh migration, без переменной миграция проходит. Постоянный `RUST_LOG=info` и обновление зависимостей не нужны. Logging приложения настраивается отдельно. История migrations применяется штатным `migrate deploy`. Регрессия самого запуска: `node test/sqlite-regression.cjs`. Она выполняет три последовательных и два параллельных полных e2e, проверяет уникальность DB, сохранность вымышленного внешнего файла, отказ migration и SIGINT/SIGTERM cleanup. Логи остаются в каталоге из вывода. Для одной suite: `node test/sqlite.cjs app.e2e-spec.ts`. | Риск | Проверка реальной DB | | --- | --- | | Чужой пользователь | `bank-cards.e2e-spec.ts`: owner каждой карты, категории, покупки и стороны перевода | | Повтор и конкурентный update | Те же tests: fingerprint, tombstone, версии, один refund в пределах покупки, archive/write | | Конкурентный refresh | `app.e2e-spec.ts`: оба запроса читают прежний hash до CAS; один 200, один 401, cookie победителя работает после replay | | Rollback и деньги | Карточные tests: откат source lock, атомарный перевод, точные суммы выше 2^53 и границы signed64 | | Migration, restart, backup/restore | `test:durability`: прежнее приложение, новая schema, история, откат приложения и восстановление backup | Prisma в этих suites не заменяется mocks. В refresh тесте барьер задерживает настоящую подпись JWT, чтобы оба запроса дошли до реальной конкурентной записи. Unit tests с mocks отдельно проверяют orchestration и не доказывают DB concurrency. При изменении schema/migrations или сохранения данных дополнительно выполните `npm run test:durability -- /absolute/path/to/baseline`. Он не входит в lint/verify. Baseline — отдельная сборка до проверяемых миграций с неизменённым префиксом migration history. Для текущей карточной схемы подходит revision `8ec172f48eefa2237dc0c16919fbb2e726b39a65` (до добавления карт): ```bash # Из backend; экспорт не содержит .env или локальную базу. baseline_dir=$(mktemp -d /tmp/wealthledger-baseline-XXXXXX) git archive 8ec172f48eefa2237dc0c16919fbb2e726b39a65 | tar -x -C "$baseline_dir" ( cd "$baseline_dir" nvm use --silent npm ci npm run build ) nvm use --silent npm run build npm run test:durability -- "$baseline_dir" ``` Не подменяйте baseline текущей сборкой: runner отвергает тот же путь, отсутствующую сборку и неподходящую migration history. Для следующей schema выберите совместимую предыдущую revision и проверьте её API-сценарии runner. Durability оставляет временные DB, логи и `result.json` по пути из вывода для диагностики; он не откатывает schema пользовательской базы. ## Релизы Репозиторий выпускается независимо. `develop` — интеграция, `main` — принятые релизы. Обычная работа: issue-linked ветка от актуального `develop`, PR в `develop`; общие правила — в Engineering Workflow по ссылке из AGENTS. Обычные PR допускают squash; release и обратная синхронизация сохраняют историю. Commits и PR titles — Conventional Commits со ссылкой на Issue (кроме release), включая `spec:`. Версии вручную не повышай. 1. Получи `main`, `develop` и теги: `git fetch origin --tags`. Убедись, что `develop` содержит последний релиз. Создай `release/` от проверенного `develop`; `nvm use --silent`, `npm ci`. Hotfix начинается от `main` в `release/hotfix-` и проходит тот же процесс. 2. На чистой ветке выполни `npm run release:preview`, просмотри версию/changelog, затем `npm run release:prepare`. Команда создаёт `chore(release): X.Y.Z`, обновляет manifest/lockfile/CHANGELOG; тег и push не выполняет. Не обходи guards ветки, чистого дерева, отсутствующих тегов или уже подготовленной версии. 3. Проверь diff и штатные gates этого репозитория. Отправь только release-ветку, открой PR в `main` с заголовком `chore(release): X.Y.Z`. После зелёных `verify`, `security`, `commits` выполни fast-forward-only merge. Если `main` изменился, согласуй историю и повтори проверки кандидата. 4. Получи удалённый `main`, сравни его HEAD с проверенным release SHA и версией manifest/lockfile. Создай аннотированный `vX.Y.Z` именно на этом SHA: `git tag -a vX.Y.Z -m "Release X.Y.Z"`, затем отправь только `git push origin refs/tags/vX.Y.Z`. Не запускай release-it повторно на `main`. 5. Верни release-коммит через PR `main → develop` без squash/rebase; проверь, что `develop` содержит тег. Опубликованные теги не перемещай. При сбое после merge создай отсутствующий тег без нового bump; до merge исправь и проверь существующий кандидат без повторного повышения версии. `feat:` повышает minor, `fix:`/`perf:` и прочие разрешённые типы — patch; `!`/`BREAKING CHANGE:` — major, включая версии до 1.0. `chore(release):` исключён из рекомендации и changelog. Конфигурация — `.release-it.json`. В Gitea для `main`/`develop` должны быть запрещены прямой/force push и обход администратором, обязательны `verify`, `security`, `commits`; теги `v*` защищены. Workflow сам не включает эти настройки. Отдельное review approval для одного владельца не требуется. Npm publishing, deploy и Gitea Releases не включены.