Update passport.client.ts

This commit is contained in:
Artem Gorlenko 2026-10-22 14:37:09 +05:00
parent d1097a5089
commit 701dfbe2f5

View File

@ -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<T> = { 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<T> = { 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<T>;
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');
}