Refactor Notification Handling and Update API Response Structure
- Improved notification handling in the NotificationsController to ensure consistent response formats across different endpoints. - Updated response types to utilize ApiErrorResponse for error scenarios, enhancing clarity in API responses. - Refactored service methods to streamline notification processing and improve maintainability. - Cleaned up unused imports and optimized code structure for better readability.
This commit is contained in:
parent
10c8461661
commit
a1be0ea527
75
apps/bff/src/core/http/transform.interceptor.ts
Normal file
75
apps/bff/src/core/http/transform.interceptor.ts
Normal file
@ -0,0 +1,75 @@
|
||||
import {
|
||||
Injectable,
|
||||
type NestInterceptor,
|
||||
type ExecutionContext,
|
||||
type CallHandler,
|
||||
} from "@nestjs/common";
|
||||
import { SetMetadata } from "@nestjs/common";
|
||||
import { Reflector } from "@nestjs/core";
|
||||
import { Observable, map } from "rxjs";
|
||||
import type { ApiSuccessResponse } from "@customer-portal/domain/common";
|
||||
|
||||
export const SKIP_SUCCESS_ENVELOPE_KEY = "bff:skip-success-envelope";
|
||||
|
||||
/**
|
||||
* Opt-out decorator for endpoints that must not be wrapped in `{ success: true, data }`,
|
||||
* e.g. SSE streams or file downloads.
|
||||
*/
|
||||
export const SkipSuccessEnvelope = () => SetMetadata(SKIP_SUCCESS_ENVELOPE_KEY, true);
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return Boolean(value) && typeof value === "object";
|
||||
}
|
||||
|
||||
function isLikelyStream(value: unknown): boolean {
|
||||
// Avoid wrapping Node streams (file downloads / SSE internals).
|
||||
return isRecord(value) && typeof (value as { pipe?: unknown }).pipe === "function";
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class TransformInterceptor<T> implements NestInterceptor<T, ApiSuccessResponse<T>> {
|
||||
constructor(private readonly reflector: Reflector) {}
|
||||
|
||||
intercept(context: ExecutionContext, next: CallHandler<T>): Observable<ApiSuccessResponse<T>> {
|
||||
if (context.getType() !== "http") {
|
||||
// Only wrap HTTP responses.
|
||||
return next.handle() as unknown as Observable<ApiSuccessResponse<T>>;
|
||||
}
|
||||
|
||||
const skip =
|
||||
this.reflector.getAllAndOverride<boolean>(SKIP_SUCCESS_ENVELOPE_KEY, [
|
||||
context.getHandler(),
|
||||
context.getClass(),
|
||||
]) ?? false;
|
||||
|
||||
if (skip) {
|
||||
return next.handle() as unknown as Observable<ApiSuccessResponse<T>>;
|
||||
}
|
||||
|
||||
const req = context.switchToHttp().getRequest<{ originalUrl?: string; url?: string }>();
|
||||
const url = req?.originalUrl ?? req?.url ?? "";
|
||||
|
||||
// Only enforce success envelopes on the public API surface under `/api`.
|
||||
// Keep non-API endpoints (e.g. `/health`) untouched for operational tooling.
|
||||
if (!url.startsWith("/api")) {
|
||||
return next.handle() as unknown as Observable<ApiSuccessResponse<T>>;
|
||||
}
|
||||
|
||||
return next.handle().pipe(
|
||||
map(data => {
|
||||
// Keep already-wrapped responses as-is (ack/message/data variants).
|
||||
if (isRecord(data) && "success" in data) {
|
||||
return data as unknown as ApiSuccessResponse<T>;
|
||||
}
|
||||
|
||||
// Avoid wrapping streams/buffers that are handled specially by Nest/Express.
|
||||
if (isLikelyStream(data)) {
|
||||
return data as unknown as ApiSuccessResponse<T>;
|
||||
}
|
||||
|
||||
const normalized = (data === undefined ? null : data) as T;
|
||||
return { success: true as const, data: normalized };
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user