ksv741 ec95d2756d
Backend verify / commits (pull_request) Successful in 32s
Backend verify / security (pull_request) Successful in 13s
Backend verify / verify (pull_request) Successful in 4m59s
fix: align password policy to 8-72 printable ASCII on register (WealthLedger/backend#60)
Register accepts only printable ASCII (letters, digits, symbols, space),
8-72 characters, so characters equal bytes and the bcrypt 72-byte limit
holds without silent truncation. Login only caps the password at 72 bytes
so accounts created under the previous policy can still sign in.

CredentialsDto is split into RegisterDto and LoginDto; OpenAPI publishes
the register pattern and length bounds.
2026-10-16 09:41:58 +03:00
2026-08-13 19:31:43 +03:00
2026-08-14 17:12:15 +03:00
2026-09-13 15:05:38 +03:00
2026-10-12 23:19:12 +03:00

WealthLedger backend

NestJS API WealthLedger. Backend владеет auth, SQLite-данными и server-side интеграциями. Потребители используют публичный HTTP API и OpenAPI artifact; их репозитории не нужны для разработки, сборки и проверок backend.

Быстрый старт

Требуется Node 24 (.nvmrc). Создайте .env из примера, замените placeholder секреты и установите зависимости строго из lockfile:

cp .env.example .env
nvm use --silent
npm ci
npx prisma migrate deploy
npx prisma generate
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.

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, эксплуатационная последовательность — документация, страница architecture/deployment.md. Проверка публикации требует настроенного registry/runner; локальный build не доказывает package permissions.

Проверка

Единый completion gate не изменяет versioned-файлы:

npm run verify

Он запускает генерацию Prisma client, 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; security и verify на metadata-only событии не запускаются. Новый запуск отменяет устаревший для того же PR/ветки и типа события (требуется Gitea 1.26+). Ежедневно в 05:47 UTC выполняется только security. Job verify запускает npm ci и npm run verify (генерация Prisma client внутри gate, один Nest build), затем функциональный node test/sqlite.cjs --all на уже собранных артефактах. Используется только 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 должен подтвердить загрузку отчёта и покрытия: локальные проверки не доказывают доступность сервера.

Документация

Генерация без сервера сверяется с OpenAPI настроенного приложения:

npm run test:openapi-contract

Генерация OpenAPI

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 suites, проверяет уникальность DB, сохранность вымышленного внешнего файла, отказ migration и SIGINT/SIGTERM cleanup. Полный e2e набор остаётся в npm run test:e2e. Логи остаются в каталоге из вывода. Для одной suite: node test/sqlite.cjs app.e2e-spec.ts. Crash-регрессия в tbank-operations-sync.e2e-spec.ts загружает классы из dist/, поэтому перед адресным запуском этой suite выполните npm run build, иначе child/dist может расходиться с parent/src.

Риск Проверка реальной 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 (до добавления карт):

# Из 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/<name> от проверенного develop; nvm use --silent, npm ci. Hotfix начинается от main в release/hotfix-<name> и проходит тот же процесс.
  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 <release-sha> -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 не включены.

S
Description
No description provided
Readme
2.2 MiB
Languages
TypeScript 95.7%
JavaScript 4.1%
Dockerfile 0.2%