first (part 3: src directory)
Deploy Iddaai Backend / build-and-deploy (push) Successful in 33s

This commit is contained in:
2026-04-16 15:12:27 +03:00
parent 2f0b85a0c7
commit 182f4aae16
125 changed files with 22552 additions and 0 deletions
+147
View File
@@ -0,0 +1,147 @@
import {
Injectable,
NestInterceptor,
ExecutionContext,
CallHandler,
} from '@nestjs/common';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { ApiResponse, createSuccessResponse } from '../types/api-response.type';
/**
* Response interceptor that wraps all successful responses
* in the standard ApiResponse format
*/
import { I18nService, I18nContext } from 'nestjs-i18n';
@Injectable()
export class ResponseInterceptor<T> implements NestInterceptor<
T,
ApiResponse<T>
> {
constructor(private readonly i18n: I18nService) {}
intercept(
context: ExecutionContext,
next: CallHandler,
): Observable<ApiResponse<T>> {
return next.handle().pipe(
map((data: unknown) => {
const request = context.switchToHttp().getRequest();
// Determine language
const i18nContext = I18nContext.current();
let lang = i18nContext?.lang;
if (!lang) {
const acceptLanguage = request.headers['accept-language'];
const xLang = request.headers['x-lang'];
if (xLang) {
lang = Array.isArray(xLang) ? xLang[0] : xLang;
} else if (acceptLanguage) {
lang = acceptLanguage.split(',')[0].split(';')[0].split('-')[0];
}
}
lang = lang || 'en';
// If data is already an ApiResponse, we should still translate its 'data' property
// But first let's just do it directly on 'data' below before returning
if (this.isApiResponse(data)) {
if (data !== null) {
try {
this.translateReasons(data, lang);
} catch {
// Ignore if object is not extensible
}
}
return data as ApiResponse<T>;
}
// Recursively translate reasons arrays in the response body
if (data !== null) {
try {
this.translateReasons(data, lang);
} catch {
// Ignore if object is not extensible
}
}
const message = this.i18n.translate('common.success', {
lang,
});
// Wrap in success response
return createSuccessResponse(data as T, message);
}),
);
}
private translateReasons(data: any, lang: string) {
if (!data || typeof data !== 'object') {
return;
}
if (Array.isArray(data)) {
data.forEach((item) => this.translateReasons(item, lang));
return;
}
Object.keys(data).forEach((key) => {
const val = data[key];
if (
(key === 'reasons' ||
key === 'decision_reasons' ||
key === 'reasoning_factors') &&
Array.isArray(val)
) {
data[key] = val.map((r: any) => {
if (typeof r !== 'string') return r;
const translationKey = `predictions.reasons.${r}`;
const translated = this.i18n.translate(translationKey, {
lang,
});
return translated === translationKey ? r : translated;
});
} else if (key === 'reason' && typeof val === 'string') {
const translationKey = `predictions.reasons.${val}`;
const translated = this.i18n.translate(translationKey, {
lang,
});
data[key] = translated === translationKey ? val : translated;
} else if (key === 'flags' && Array.isArray(val)) {
data[key] = val.map((r: any) => {
if (typeof r !== 'string') return r;
const translationKey = `predictions.flags.${r}`;
const translated = this.i18n.translate(translationKey, {
lang,
});
return translated === translationKey ? r : translated;
});
} else if (key === 'warnings' && Array.isArray(val)) {
data[key] = val.map((r: any) => {
if (typeof r !== 'string') return r;
const translationKey = `predictions.warnings.${r}`;
const translated = this.i18n.translate(translationKey, {
lang,
});
return translated === translationKey ? r : translated;
});
} else if (typeof val === 'object' && val !== null) {
this.translateReasons(val, lang);
}
});
}
private isApiResponse(data: unknown): boolean {
return (
data !== null &&
typeof data === 'object' &&
'success' in data &&
'status' in data &&
'message' in data &&
'data' in data
);
}
}