Бэкенд «Оптима»

Сервис единого окна для селлеров Wildberries. Бэкенд — модульный монолит на NestJS (TypeScript) с PostgreSQL + Redis; фоновые задачи — BullMQ + @nestjs/schedule. Задачи проекта ведутся в Jira (OP).

Документация и конвенции AI-агентов — backend/CLAUDE.md. Документация заказчика — docs-site/ (публикуется на docs.optimawb.ru).

Запуск локально

Example :
cp .env.example .env       # настроить переменные окружения
npm install
npm run start:dev          # → http://localhost:3000, Swagger UI на /api/docs

PostgreSQL и 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)

Модульная структура

Example :
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.
  • Зависимости через DI. Прямое чтение process.env запрещено: используй AppConfigService. Доступ к БД — через TypeORM (@InjectRepository(Entity) после регистрации в TypeOrmModule.forFeature([...]); @InjectDataSource() для сырых запросов и транзакций — см. ADR-0001 и docs/database.md). К Redis — через @Inject(REDIS_CLIENT).
  • Документация с кодом (русский TSDoc + Swagger) — см. 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 (для записи новых шифротекстов) —

Логирование (OP-44 · F6)

Логи — структурный JSON, по одной строке на событие, в stdout (его собирает рантайм Coolify). Конвенции — ADR-0011; код — src/core/observability.

Уровни. Поддерживаются error/warn/log/debug/verbose. Минимальный уровень — LOG_LEVEL либо дефолт по окружению (production→log, test→warn, иначе debug). Менее важные сообщения отсекаются.

Correlation-id. У каждой «единицы работы» — единый идентификатор, который автоматически попадает в каждую запись лога (поле correlationId):

  • HTTP-запрос — traceIdMiddleware берёт X-Request-Id (если пришёл от прокси/клиента) либо генерирует UUID, возвращает его тем же заголовком и выполняет обработку внутри контекста корреляции (AsyncLocalStorage).
  • Фоновая джоба / cron — воркер сам открывает контекст correlation.run('job-<очередь>-<id>', () => …). Шаблон — в ExampleJobsProcessor; новые воркеры повторяют его.

Логировать через стандартный new Logger(MyService.name) — глобальный логгер заменён на StructuredLoggerService в main.ts, поэтому весь вывод (включая логи фреймворка) уже структурный и с correlationId.

Маскирование секретов. Объекты в meta-полях логов проходят через redactSecrets — значения чувствительных ключей (password, token, secret, authorization, cookie, apiKey, … — регистр и -/_ игнорируются) заменяются на [REDACTED]. Не кладите секреты в текст сообщения — маскируются только структурированные поля.

Example :
// Пример: meta-объект — секреты замаскируются автоматически.
this.logger.log('создан токен', { userId, token }); // token → [REDACTED]

Ротация ключа шифрования

Шифрование секретов at-rest — AES-256-GCM с key-ring (см. ADR-0005). Ротация без data-migration: новый ключ становится активным для записи, старый остаётся в ring для чтения легаси-данных, по мере перезаписи поля шифруются новым ключом.

Пошаговый чек-лист:

  1. Сгенерировать новый ключ:Example :
    openssl rand -hex 32   # 64 hex-символа = 32 байта = ключ AES-256
  2. Добавить в ENCRYPTION_KEYS через запятую (старый ключ оставить!):Example :
    ENCRYPTION_KEYS=k1:0123…,k2:<новый-hex64>
    Передеплоить. Приложение поднимется — оба ключа в ring, активный пока k1.
  3. Переключить активный kid:Example :
    ENCRYPTION_ACTIVE_KID=k2
    Передеплоить. Новые записи теперь шифруются k2. Старые продолжают читаться через k1 (kid указан в самой строке шифротекста).
  4. Перешифровать легаси-записи при обновлении соответствующих полей — их шифротекст автоматически переедет на k2. Для массовой перешифровки (если объём оправдывает) — отдельная утилита/Story.
  5. Когда k1-шифротекстов в БД не осталось (проверить SELECT … WHERE <поле> LIKE 'v1.k1.%'), убрать k1 из ENCRYPTION_KEYS и передеплоить.

Если ENCRYPTION_ACTIVE_KID указывает на отсутствующий в ENCRYPTION_KEYS ключ — приложение не поднимется (EncryptionService падает на старте, см. core/crypto/encryption.service.ts).

Pre-commit гейт

Husky + lint-staged прогоняют по staged-файлам backend/src/**/*.ts eslint --fix и tsc -p tsconfig.build.json --noEmit. Активируется автоматически при npm install (скрипт prepare).

results matching ""

    No results matching ""