Skip to content

npp-backend

npp-backend — центральный сервис платформы на NestJS + GraphQL + Prisma. Именно он владеет доменной моделью, авторизацией, ingest-потоками, отчётами и аналитическими агрегатами.

За что отвечает

  • пользователи, роли, сессии и refresh flow;
  • GraphQL API для npp-web;
  • ingest нормализованных данных от processing-worker;
  • каталог источников и история SourceRun;
  • хранение закупок, риск-сигналов, профилей компаний и отчётов;
  • аналитические summary и snapshot-отчёты;
  • внутренние HTTP endpoints для scraper-service.

Главные доменные области

В сервисе сходятся несколько контуров:

  • Auth
  • Sources / SourceRuns
  • Procurement
  • Registry
  • SupplierRisk
  • SupplierCompany
  • Reports
  • Analytics
  • Dashboard
  • Users

Это важно, потому что backend не просто “отдаёт таблицы”, а объединяет несколько источников данных в общую бизнес-модель.

Что приходит в backend

Сервис не ходит за внешними данными сам. Он принимает:

  • GraphQL ingest от processing-worker;
  • SourceRun статусы от scraper-service по internal HTTP;
  • runtime-конфиг и административные команды через backend/admin контур.

То есть backend не должен знать HTML конкретной площадки, но должен понимать смысл нормализованного события.

Основные точки входа

GraphQL

  • http://localhost:3000/graphql

Frontend читает backend именно через GraphQL-контракт из contracts/graphql/schema.graphql.

Health

  • http://localhost:3000/api/health
  • http://localhost:3000/api/health/ready

Internal scraper endpoints

Примеры:

  • /api/internal/scraper/config
  • /api/internal/scraper/source-runs
  • /api/internal/scraper/quarantine-events

Именно через них scraper-service синхронизирует runtime и статус запусков.

Где смотреть код

Ключевые каталоги:

ПутьЧто внутри
src/authlogin, refresh, logout, guards, сессии
src/sourcesкаталог источников, source runs, internal scraper API
src/procurementingest и выдача закупок
src/registryingest записей РНП
src/supplier-riskingest и хранение Fedresurs-сигналов
src/supplier-companyingest и хранение FNS-профилей
src/analyticsагрегаты аналитических экранов
src/reportssnapshot-отчёты и отчётные сценарии
src/dashboardsummary-данные для главного экрана
prismaschema, migrations, seed

Минимальные env-переменные

  • DATABASE_URL
  • REDIS_URL
  • RABBITMQ_URL
  • JWT_ACCESS_SECRET
  • INGEST_API_TOKEN

Практически удобнее брать готовый шаблон из infra/.env.example.

Локальный запуск

bash
cd ../infra
cp .env.example .env
docker compose --env-file .env -f docker-compose.yml -f docker-compose.apps.yml up -d postgres redis rabbitmq minio minio-init

cd ../npp-backend
npm install
npm run db:setup
npm run start:dev

Prisma и база

Полезные команды:

bash
npm run prisma:generate
npm run prisma:migrate:deploy
npm run prisma:db:seed

db:setup обычно объединяет ключевые шаги для локального старта.

Auth flow

Базовый сценарий такой:

  1. login возвращает accessToken и refreshToken.
  2. refreshSession перевыпускает пару токенов.
  3. logout завершает server-side сессию.
  4. access token валидируется не только криптографически, но и через связанную UserSession.

Это важно: backend хранит сессионную правду, а не просто “доверяет JWT”.

Упрощённо это выглядит так:

ts
async login(email: string, password: string, request?: RequestLike) {
  const user = await prisma.user.findFirst({ where: { email: email.toLowerCase(), deletedAt: null } });
  if (!user || !user.isActive || !(await compare(password, user.passwordHash))) {
    throw new UnauthorizedException("Invalid email or password");
  }

  return issueTokens({ id: user.id, email: user.email, fullName: user.fullName, role: user.role }, request);
}

async verifyAccessToken(token: string) {
  const payload = await jwtService.verifyAsync<TokenPayload>(token, { secret: jwtSecret });

  if (payload.sid) {
    const session = await prisma.userSession.findFirst({
      where: { id: payload.sid, userId: payload.sub, revokedAt: null, expiresAt: { gt: new Date() } }
    });

    if (!session) {
      throw new UnauthorizedException("Session is invalid or expired");
    }
  }
}

Это ключевое отличие от полностью stateless JWT-схемы: logout и revoke реально работают на стороне сервера.

Ingest-потоки

processing-worker вызывает ingest по GraphQL с заголовком x-ingest-token.

В зависимости от типа события backend может создавать:

  • Procurement
  • RegistryRecord
  • SupplierRiskSignal
  • SupplierCompanyProfile
  • AuctionItem

Кроме этого backend фиксирует:

  • RawEvent
  • Artifact
  • NormalizedItem

Это даёт трассируемость от доменной записи до исходного события и артефакта.

Принцип идемпотентной загрузки

Backend не должен слепо создавать новую сущность на каждый ingest. Одно и то же сообщение может прийти повторно из-за retry, перезапуска worker или повторной публикации.

Поэтому загрузка строится через:

  • contentHash
  • idempotencyKey
  • уникальные ключи доменных таблиц
  • журнал NormalizedItem

Упрощённый фрагмент:

ts
const contentHash = createHash("sha256")
  .update(JSON.stringify({
    externalId: input.externalId,
    source: input.source,
    payloadVersion: input.payloadVersion,
    title: input.title,
    rawPayload: input.rawPayload ?? null
  }))
  .digest("hex");

const idempotencyKey = createHash("sha256")
  .update(`${input.source}:${input.externalId}:${input.payloadVersion}:${contentHash}`)
  .digest("hex");

Именно это позволяет backend безопасно жить в асинхронной event-driven архитектуре.

Аналитика и отчёты

Backend — это место, где реально считается аналитика. Именно здесь живут:

  • агрегаты дашборда;
  • health источников;
  • аналитика по поставщикам;
  • атомный контур;
  • snapshot-отчёты.

Если меняется смысл метрики, менять нужно backend, а не frontend.

Как backend связан с эксплуатацией

В NPPWEB backend отвечает не только за “данные для UI”, но и за эксплуатационную наблюдаемость:

  • хранит SourceRun;
  • принимает quarantine-события;
  • синхронизирует runtime-конфигурацию парсеров;
  • отдаёт operational health и parser admin данные;
  • хранит snapshot-отчёты.

То есть backend одновременно является:

  • владельцем доменной модели;
  • владельцем API;
  • и центральной точкой operational truth.

Что особенно полезно читать в коде

Если нужно быстро понять backend, обычно лучше идти в таком порядке:

  1. src/app.module.ts
  2. src/auth
  3. src/procurement
  4. src/sources
  5. src/analytics
  6. src/reports
  7. prisma/schema.prisma

Так легче увидеть сначала каркас и точки входа, а уже потом специальные бизнес-сценарии.

Когда идти именно в этот репозиторий

Вы почти наверняка пришли по адресу, если:

  • нужен новый GraphQL query или mutation;
  • надо изменить ingest-логику;
  • нужно добавить доменную таблицу или связь;
  • требуется новая аналитическая метрика;
  • пустой экран объясняется именно backend-агрегацией;
  • нужно изменить auth, роли или session flow.

Как проверять качество

bash
npm run check
npm run test
npm run build

Для smoke-проверки после изменений:

  1. поднять Postgres и Redis;
  2. запустить backend;
  3. проверить health и ready;
  4. открыть GraphQL и убедиться, что резолверы поднимаются без runtime-ошибок.

Типичные ошибки

  • менять GraphQL-тип без обновления contracts;
  • считать backend владельцем HTML/JSON внешней площадки;
  • забывать, что часть аналитики зависит не только от Procurement, но и от supplier-domain сущностей;
  • чинить frontend, когда проблема в агрегации backend.

Техническая и аналитическая документация платформы NPPWEB.