npp-backend
npp-backend — центральный сервис платформы на NestJS + GraphQL + Prisma. Именно он владеет доменной моделью, авторизацией, ingest-потоками, отчётами и аналитическими агрегатами.
За что отвечает
- пользователи, роли, сессии и refresh flow;
- GraphQL API для
npp-web; - ingest нормализованных данных от
processing-worker; - каталог источников и история
SourceRun; - хранение закупок, риск-сигналов, профилей компаний и отчётов;
- аналитические summary и snapshot-отчёты;
- внутренние HTTP endpoints для
scraper-service.
Главные доменные области
В сервисе сходятся несколько контуров:
AuthSources / SourceRunsProcurementRegistrySupplierRiskSupplierCompanyReportsAnalyticsDashboardUsers
Это важно, потому что 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/healthhttp://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/auth | login, refresh, logout, guards, сессии |
src/sources | каталог источников, source runs, internal scraper API |
src/procurement | ingest и выдача закупок |
src/registry | ingest записей РНП |
src/supplier-risk | ingest и хранение Fedresurs-сигналов |
src/supplier-company | ingest и хранение FNS-профилей |
src/analytics | агрегаты аналитических экранов |
src/reports | snapshot-отчёты и отчётные сценарии |
src/dashboard | summary-данные для главного экрана |
prisma | schema, migrations, seed |
Минимальные env-переменные
DATABASE_URLREDIS_URLRABBITMQ_URLJWT_ACCESS_SECRETINGEST_API_TOKEN
Практически удобнее брать готовый шаблон из infra/.env.example.
Локальный запуск
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:devPrisma и база
Полезные команды:
npm run prisma:generate
npm run prisma:migrate:deploy
npm run prisma:db:seeddb:setup обычно объединяет ключевые шаги для локального старта.
Auth flow
Базовый сценарий такой:
loginвозвращаетaccessTokenиrefreshToken.refreshSessionперевыпускает пару токенов.logoutзавершает server-side сессию.- access token валидируется не только криптографически, но и через связанную
UserSession.
Это важно: backend хранит сессионную правду, а не просто “доверяет JWT”.
Упрощённо это выглядит так:
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 может создавать:
ProcurementRegistryRecordSupplierRiskSignalSupplierCompanyProfileAuctionItem
Кроме этого backend фиксирует:
RawEventArtifactNormalizedItem
Это даёт трассируемость от доменной записи до исходного события и артефакта.
Принцип идемпотентной загрузки
Backend не должен слепо создавать новую сущность на каждый ingest. Одно и то же сообщение может прийти повторно из-за retry, перезапуска worker или повторной публикации.
Поэтому загрузка строится через:
contentHashidempotencyKey- уникальные ключи доменных таблиц
- журнал
NormalizedItem
Упрощённый фрагмент:
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, обычно лучше идти в таком порядке:
src/app.module.tssrc/authsrc/procurementsrc/sourcessrc/analyticssrc/reportsprisma/schema.prisma
Так легче увидеть сначала каркас и точки входа, а уже потом специальные бизнес-сценарии.
Когда идти именно в этот репозиторий
Вы почти наверняка пришли по адресу, если:
- нужен новый GraphQL query или mutation;
- надо изменить ingest-логику;
- нужно добавить доменную таблицу или связь;
- требуется новая аналитическая метрика;
- пустой экран объясняется именно backend-агрегацией;
- нужно изменить auth, роли или session flow.
Как проверять качество
npm run check
npm run test
npm run buildДля smoke-проверки после изменений:
- поднять Postgres и Redis;
- запустить backend;
- проверить
healthиready; - открыть GraphQL и убедиться, что резолверы поднимаются без runtime-ошибок.
Типичные ошибки
- менять GraphQL-тип без обновления
contracts; - считать backend владельцем HTML/JSON внешней площадки;
- забывать, что часть аналитики зависит не только от
Procurement, но и от supplier-domain сущностей; - чинить frontend, когда проблема в агрегации backend.