Сервис единого окна для селлеров Wildberries. Бэкенд — модульный монолит
на NestJS (TypeScript) с PostgreSQL + Redis; фоновые задачи —
BullMQ + @nestjs/schedule. Задачи проекта ведутся в Jira (OP).
Документация и конвенции AI-агентов — backend/CLAUDE.md. Документация
заказчика — docs-site/ (публикуется на docs.optimawb.ru).
cp .env.example .env # настроить переменные окружения
npm install
npm run start:dev # → http://localhost:3000, Swagger UI на /api/docsPostgreSQL и Redis удобно поднять через корневой docker-compose.yml
(сервисы postgres, redis). При npm run start:dev приложение
поднимется и без них — соединения создаются лениво при первом запросе.
| Команда | Что делает |
|---|---|
npm run start:dev |
dev-режим с hot-reload |
npm run build |
сборка в dist/ |
npm run lint |
ESLint с автофиксом |
npm test |
unit-тесты Jest |
npm run test:cov |
покрытие тестами |
npm run docs |
Compodoc → documentation/ |
npm run docs:coverage |
проверка покрытия TSDoc (порог 80%) |
npm run docs:openapi |
выгрузка контракта в openapi.json |
npm run migration:run |
применить миграции к БД из DATABASE_URL |
npm run migration:revert |
откатить последнюю применённую миграцию |
npm run migration:generate -- src/core/database/migrations/<Name> |
сгенерировать миграцию по diff entity ↔ БД |
npm run migration:show |
список миграций со статусом применения |
npm run seed:run |
прогон сидов (src/core/database/seeds/*.seed.ts) |
src/
├── main.ts # точка входа (создаёт Nest-app, поднимает Swagger)
├── swagger.ts # конфигурация контракта API
├── app.module.ts # корневой модуль (собирает core + фичи)
├── app.controller.ts # образец конвенции документирования (TSDoc + Swagger)
├── app.service.ts
├── health/ # liveness-проба (/health); готовность зависимостей — OP-25
│
├── core/ # горизонтальные сервисы, используемые всеми фичами
│ ├── config/ # @nestjs/config + zod-валидация env, AppConfigService
│ ├── crypto/ # CryptoModule: EncryptionService (AES-256-GCM, OP-32)
│ ├── database/ # DatabaseModule (TypeORM), BaseEntity, миграции
│ └── redis/ # RedisModule, токен REDIS_CLIENT (ioredis, lazyConnect)
│
└── modules/ # фичи-модули 5.1–5.15 из ТЗ
└── README.md # конвенция структуры внутри фичиcore/ — только инфраструктурные сервисы (@Global()). Бизнес-логика сюда не кладётся.modules/<feature>/ — каждая фича — отдельный каталог, см. src/modules/README.md.process.env запрещено: используй
AppConfigService. Доступ к БД — через TypeORM (@InjectRepository(Entity)
после регистрации в TypeOrmModule.forFeature([...]); @InjectDataSource()
для сырых запросов и транзакций — см. ADR-0001 и docs/database.md).
К Redis — через @Inject(REDIS_CLIENT).backend/CLAUDE.md.
Архитектурные решения — ADR в docs/adr/.Контракт описан в src/core/config/env.schema.ts (zod). Все переменные
обязательны кроме помеченных default; приложение не запустится с неполным
env. Пример — .env.example.
| Переменная | Назначение | По умолчанию |
|---|---|---|
NODE_ENV |
окружение (development/test/production) |
development |
LOG_LEVEL |
минимальный уровень логов (error/warn/log/debug/verbose) |
по NODE_ENV (prod→log, test→warn, иначе debug) |
PORT |
порт HTTP-сервера | 3000 |
DATABASE_URL |
строка подключения PostgreSQL | — |
REDIS_URL |
строка подключения Redis | — |
JWT_ACCESS_SECRET |
секрет access-токена (F2) | — |
JWT_REFRESH_SECRET |
секрет refresh-токена (F2) | — |
ENCRYPTION_KEYS |
key-ring kid:hex64[,…] для шифрования секретов at-rest (F2, OP-32; см. ADR-0005) |
— |
ENCRYPTION_ACTIVE_KID |
активный kid из ENCRYPTION_KEYS (для записи новых шифротекстов) |
— |
Логи — структурный JSON, по одной строке на событие, в stdout (его собирает
рантайм Coolify). Конвенции — ADR-0011; код — src/core/observability.
Уровни. Поддерживаются error/warn/log/debug/verbose. Минимальный
уровень — LOG_LEVEL либо дефолт по окружению (production→log,
test→warn, иначе debug). Менее важные сообщения отсекаются.
Correlation-id. У каждой «единицы работы» — единый идентификатор, который
автоматически попадает в каждую запись лога (поле correlationId):
traceIdMiddleware берёт X-Request-Id (если пришёл от
прокси/клиента) либо генерирует UUID, возвращает его тем же заголовком и
выполняет обработку внутри контекста корреляции (AsyncLocalStorage).correlation.run('job-<очередь>-<id>', () => …). Шаблон — в
ExampleJobsProcessor; новые воркеры повторяют его.Логировать через стандартный new Logger(MyService.name) — глобальный логгер
заменён на StructuredLoggerService в main.ts, поэтому весь вывод (включая
логи фреймворка) уже структурный и с correlationId.
Маскирование секретов. Объекты в meta-полях логов проходят через
redactSecrets — значения чувствительных ключей (password, token,
secret, authorization, cookie, apiKey, … — регистр и -/_
игнорируются) заменяются на [REDACTED]. Не кладите секреты в текст
сообщения — маскируются только структурированные поля.
// Пример: meta-объект — секреты замаскируются автоматически.
this.logger.log('создан токен', { userId, token }); // token → [REDACTED]Шифрование секретов at-rest — AES-256-GCM с key-ring (см. ADR-0005). Ротация без data-migration: новый ключ становится активным для записи, старый остаётся в ring для чтения легаси-данных, по мере перезаписи поля шифруются новым ключом.
Пошаговый чек-лист:
openssl rand -hex 32 # 64 hex-символа = 32 байта = ключ AES-256ENCRYPTION_KEYS через запятую (старый ключ оставить!):Example :ENCRYPTION_KEYS=k1:0123…,k2:<новый-hex64>k1.kid:Example :ENCRYPTION_ACTIVE_KID=k2k2. Старые продолжают
читаться через k1 (kid указан в самой строке шифротекста).k2. Для массовой перешифровки
(если объём оправдывает) — отдельная утилита/Story.k1-шифротекстов в БД не осталось (проверить SELECT … WHERE <поле> LIKE 'v1.k1.%'), убрать k1 из ENCRYPTION_KEYS и передеплоить.Если ENCRYPTION_ACTIVE_KID указывает на отсутствующий в ENCRYPTION_KEYS
ключ — приложение не поднимется (EncryptionService падает на старте,
см. core/crypto/encryption.service.ts).
Husky + lint-staged прогоняют по staged-файлам backend/src/**/*.ts
eslint --fix и tsc -p tsconfig.build.json --noEmit. Активируется
автоматически при npm install (скрипт prepare).