From 701dfbe2f518c957c6b83aaef154aff7b9bdbaab Mon Sep 17 00:00:00 2001 From: Artem Gorlenko Date: Thu, 22 Oct 2026 14:37:09 +0500 Subject: [PATCH] Update passport.client.ts --- .../src/modules/passport/passport.client.ts | 33 +++++++++++++++---- 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/apps/api/src/modules/passport/passport.client.ts b/apps/api/src/modules/passport/passport.client.ts index 46f4bdc..e2c63f8 100644 --- a/apps/api/src/modules/passport/passport.client.ts +++ b/apps/api/src/modules/passport/passport.client.ts @@ -1,9 +1,21 @@ -import { BadGatewayException, BadRequestException, ConflictException, Injectable, Logger, UnauthorizedException } from '@nestjs/common'; +import { BadGatewayException, BadRequestException, ConflictException, HttpException, HttpStatus, Injectable, Logger, UnauthorizedException } from '@nestjs/common'; import { createHash } from 'crypto'; import { config } from '../../config'; // Ответ /api/v1/*: { result } или { errors: [{ code, input_field, description }] }. -type V1Response = { result?: T; errors?: { code: number; input_field?: string | null; description?: string | null }[] }; +// description — фраза паспорта на нескольких языках: { ru, en } (App\\Language\\Phrase), иногда строка. +type V1Description = string | { ru?: string; en?: string } | null; +type V1Response = { result?: T; errors?: { code: number; input_field?: string | null; description?: V1Description }[] }; + +function describe(d: V1Description | undefined): string | null { + if (!d) return null; + if (typeof d === 'string') return d.trim() || null; + return d.ru?.trim() || d.en?.trim() || null; +} + +// Коды 1002–1007 — не ошибка человека, а неверная настройка связи с паспортом: +// нет метода, неверная подпись, нет платформы (config/api_v1_errors.php паспорта). +const SETUP_CODES = new Set([1002, 1003, 1004, 1005, 1006, 1007]); export type PassportUserFull = { uuid: string; @@ -39,13 +51,15 @@ export type PassportPerson = { isEnabled: boolean; }; -export class PassportApiError extends Error { +// Ошибка паспорта, которую код не превратил во что-то понятнее (humanize): отдаём 502 с текстом паспорта, +// а не 500 «Internal server error». +export class PassportApiError extends HttpException { constructor( readonly code: number, readonly field: string | null, message: string, ) { - super(message); + super(message, HttpStatus.BAD_GATEWAY); } } @@ -118,8 +132,13 @@ export class PassportClient { const r = data as V1Response; if (r?.errors?.length) { const e = r.errors[0]; - this.logger.warn(`Паспорт ${method}: ошибка ${e.code} ${e.input_field ?? ''} ${e.description ?? ''}`); - throw new PassportApiError(e.code, e.input_field ?? null, e.description || `Ошибка паспорта ${e.code}`); + const text = describe(e.description) || `Ошибка паспорта ${e.code}`; + if (SETUP_CODES.has(e.code)) { + this.logger.error(`Паспорт ${method}: ${text} (код ${e.code}) — проверьте PASSPORT_URL, PLATFORM_CODE, PASSPORT_PLATFORM_TOKEN и что метод выложен`); + throw new PassportApiError(e.code, null, `Паспорт Aspans не настроен для «Обходов»: ${text} (код ${e.code})`); + } + this.logger.warn(`Паспорт ${method}: ошибка ${e.code}${e.input_field ? ` [${e.input_field}]` : ''} ${text}`); + throw new PassportApiError(e.code, e.input_field ?? null, text); } if (!r || !('result' in r)) throw new BadGatewayException('Паспорт Aspans ответил без результата'); return r.result as T; @@ -224,7 +243,7 @@ export class PassportClient { } private humanize(e: unknown) { - if (!(e instanceof PassportApiError)) return e; + if (!(e instanceof PassportApiError) || SETUP_CODES.has(e.code)) return e; if (e.field === 'email' && /разным/.test(e.message)) { return new ConflictException('Этот email и этот телефон принадлежат разным людям в Aspans ID'); }