Compare commits
No commits in common. "701dfbe2f518c957c6b83aaef154aff7b9bdbaab" and "cd69aec83e0a6b633eb28eeac5daf088a06abf34" have entirely different histories.
701dfbe2f5
...
cd69aec83e
@ -1,21 +1,9 @@
|
|||||||
import { BadGatewayException, BadRequestException, ConflictException, HttpException, HttpStatus, Injectable, Logger, UnauthorizedException } from '@nestjs/common';
|
import { BadGatewayException, BadRequestException, ConflictException, Injectable, Logger, UnauthorizedException } from '@nestjs/common';
|
||||||
import { createHash } from 'crypto';
|
import { createHash } from 'crypto';
|
||||||
import { config } from '../../config';
|
import { config } from '../../config';
|
||||||
|
|
||||||
// Ответ /api/v1/*: { result } или { errors: [{ code, input_field, description }] }.
|
// Ответ /api/v1/*: { result } или { errors: [{ code, input_field, description }] }.
|
||||||
// description — фраза паспорта на нескольких языках: { ru, en } (App\\Language\\Phrase), иногда строка.
|
type V1Response<T> = { result?: T; errors?: { code: number; input_field?: string | null; description?: string | null }[] };
|
||||||
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 = {
|
export type PassportUserFull = {
|
||||||
uuid: string;
|
uuid: string;
|
||||||
@ -51,15 +39,13 @@ export type PassportPerson = {
|
|||||||
isEnabled: boolean;
|
isEnabled: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Ошибка паспорта, которую код не превратил во что-то понятнее (humanize): отдаём 502 с текстом паспорта,
|
export class PassportApiError extends Error {
|
||||||
// а не 500 «Internal server error».
|
|
||||||
export class PassportApiError extends HttpException {
|
|
||||||
constructor(
|
constructor(
|
||||||
readonly code: number,
|
readonly code: number,
|
||||||
readonly field: string | null,
|
readonly field: string | null,
|
||||||
message: string,
|
message: string,
|
||||||
) {
|
) {
|
||||||
super(message, HttpStatus.BAD_GATEWAY);
|
super(message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -132,13 +118,8 @@ export class PassportClient {
|
|||||||
const r = data as V1Response<T>;
|
const r = data as V1Response<T>;
|
||||||
if (r?.errors?.length) {
|
if (r?.errors?.length) {
|
||||||
const e = r.errors[0];
|
const e = r.errors[0];
|
||||||
const text = describe(e.description) || `Ошибка паспорта ${e.code}`;
|
this.logger.warn(`Паспорт ${method}: ошибка ${e.code} ${e.input_field ?? ''} ${e.description ?? ''}`);
|
||||||
if (SETUP_CODES.has(e.code)) {
|
throw new PassportApiError(e.code, e.input_field ?? null, e.description || `Ошибка паспорта ${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 ответил без результата');
|
if (!r || !('result' in r)) throw new BadGatewayException('Паспорт Aspans ответил без результата');
|
||||||
return r.result as T;
|
return r.result as T;
|
||||||
@ -243,7 +224,7 @@ export class PassportClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private humanize(e: unknown) {
|
private humanize(e: unknown) {
|
||||||
if (!(e instanceof PassportApiError) || SETUP_CODES.has(e.code)) return e;
|
if (!(e instanceof PassportApiError)) return e;
|
||||||
if (e.field === 'email' && /разным/.test(e.message)) {
|
if (e.field === 'email' && /разным/.test(e.message)) {
|
||||||
return new ConflictException('Этот email и этот телефон принадлежат разным людям в Aspans ID');
|
return new ConflictException('Этот email и этот телефон принадлежат разным людям в Aspans ID');
|
||||||
}
|
}
|
||||||
|
|||||||
@ -252,8 +252,7 @@ export class RoundsService {
|
|||||||
@Cron(CronExpression.EVERY_MINUTE)
|
@Cron(CronExpression.EVERY_MINUTE)
|
||||||
async closeExpired() {
|
async closeExpired() {
|
||||||
if (config.disableCron) return;
|
if (config.disableCron) return;
|
||||||
// Для UPDATE драйвер TypeORM (postgres) возвращает не строки, а пару [строки RETURNING, число изменённых].
|
const closed: { id: string; site_id: string; status: RoundStatus; organization_id: string; done: number; total: number }[] =
|
||||||
const [closed]: [{ id: string; site_id: string; status: RoundStatus; organization_id: string; done: number; total: number }[], number] =
|
|
||||||
await this.db.query(
|
await this.db.query(
|
||||||
`UPDATE rounds r SET status = CASE
|
`UPDATE rounds r SET status = CASE
|
||||||
WHEN r.checkpoints_done = 0 THEN 'missed'
|
WHEN r.checkpoints_done = 0 THEN 'missed'
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user