- Introduced `@Pagination()` decorator to mark list endpoints for pagination.
- Added `TransformInterceptor` to wrap responses in a standardized format `{ data, meta }`.
- Created pagination-related utility functions and constants for managing pagination logic.
- Defined `PaginationQueryDto` for handling pagination query parameters.
- Established `PaginationMetaDto` for OpenAPI documentation of pagination metadata.
- Updated existing controller and service structures to support pagination in responses.
- Added unit tests for pagination utilities and interceptor to ensure correct functionality.
59 lines
1.6 KiB
TypeScript
59 lines
1.6 KiB
TypeScript
import {
|
|
CallHandler,
|
|
ExecutionContext,
|
|
Injectable,
|
|
NestInterceptor,
|
|
StreamableFile,
|
|
} from '@nestjs/common';
|
|
import { Reflector } from '@nestjs/core';
|
|
import { Observable } from 'rxjs';
|
|
import { map } from 'rxjs/operators';
|
|
import { PAGINATION_RESPONSE, RAW_RESPONSE } from './constants';
|
|
import {
|
|
createPaginationResponse,
|
|
resolvePaginationQuery,
|
|
} from './pagination-meta.helper';
|
|
|
|
/**
|
|
* Applies pagination enveloping when a handler is marked with `@Pagination()`.
|
|
*
|
|
* Non-paginated handlers pass through unchanged. `@RawResponse()`, empty
|
|
* bodies, and `StreamableFile` downloads are never wrapped.
|
|
*/
|
|
@Injectable()
|
|
export class TransformInterceptor implements NestInterceptor {
|
|
constructor(private readonly reflector: Reflector) {}
|
|
|
|
intercept(context: ExecutionContext, next: CallHandler): Observable<unknown> {
|
|
const isRaw = this.reflector.getAllAndOverride<boolean>(RAW_RESPONSE, [
|
|
context.getHandler(),
|
|
context.getClass(),
|
|
]);
|
|
if (isRaw) {
|
|
return next.handle();
|
|
}
|
|
|
|
const isPagination = this.reflector.getAllAndOverride<boolean>(
|
|
PAGINATION_RESPONSE,
|
|
[context.getHandler(), context.getClass()],
|
|
);
|
|
if (!isPagination) {
|
|
return next.handle();
|
|
}
|
|
|
|
const request = context.switchToHttp().getRequest<{
|
|
query: Record<string, unknown>;
|
|
}>();
|
|
const { page, limit } = resolvePaginationQuery(request.query);
|
|
|
|
return next.handle().pipe(
|
|
map((payload: unknown): unknown => {
|
|
if (payload == null || payload instanceof StreamableFile) {
|
|
return payload;
|
|
}
|
|
return createPaginationResponse(payload, page, limit);
|
|
}),
|
|
);
|
|
}
|
|
}
|