From d598e221ed6ee7eed90e2f5c337147f559cb661f Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Thu, 7 Aug 2025 00:50:04 +0200 Subject: [PATCH 1/5] feat: modified search score cards endpoint --- .gitignore | 3 + src/api/api.module.ts | 2 + src/api/scorecard/scorecard.controller.ts | 79 ++++++++++--------- src/api/scorecard/scorecard.service.ts | 69 ++++++++++++++++ src/dto/scorecard.dto.ts | 42 +++++++++- .../PaginationHeaderInterceptor.ts | 21 +++++ 6 files changed, 178 insertions(+), 38 deletions(-) create mode 100644 src/api/scorecard/scorecard.service.ts create mode 100644 src/interceptors/PaginationHeaderInterceptor.ts diff --git a/.gitignore b/.gitignore index 4b56acf..e60d4b7 100644 --- a/.gitignore +++ b/.gitignore @@ -42,6 +42,9 @@ lerna-debug.log* .env.production.local .env.local +# Prisma score cards +/prisma/Scorecards + # temp directory .temp .tmp diff --git a/src/api/api.module.ts b/src/api/api.module.ts index af307c6..108c9a5 100644 --- a/src/api/api.module.ts +++ b/src/api/api.module.ts @@ -16,6 +16,7 @@ import { ChallengeApiService } from 'src/shared/modules/global/challenge.service import { WebhookController } from './webhook/webhook.controller'; import { WebhookService } from './webhook/webhook.service'; import { GiteaWebhookAuthGuard } from '../shared/guards/gitea-webhook-auth.guard'; +import { ScoreCardService } from './scorecard/scorecard.service'; @Module({ imports: [HttpModule, GlobalProvidersModule], @@ -37,6 +38,7 @@ import { GiteaWebhookAuthGuard } from '../shared/guards/gitea-webhook-auth.guard ChallengeApiService, WebhookService, GiteaWebhookAuthGuard, + ScoreCardService, ], }) export class ApiModule {} diff --git a/src/api/scorecard/scorecard.controller.ts b/src/api/scorecard/scorecard.controller.ts index 1bdf59d..0559537 100644 --- a/src/api/scorecard/scorecard.controller.ts +++ b/src/api/scorecard/scorecard.controller.ts @@ -9,6 +9,8 @@ import { Query, NotFoundException, InternalServerErrorException, + Res, + UseInterceptors, } from '@nestjs/common'; import { ApiTags, @@ -24,18 +26,24 @@ import { UserRole } from 'src/shared/enums/userRole.enum'; import { Scopes } from 'src/shared/decorators/scopes.decorator'; import { Scope } from 'src/shared/enums/scopes.enum'; import { + ScorecardPaginatedResponseDto, ScorecardRequestDto, ScorecardResponseDto, + ScorecardWithGroupResponseDto, mapScorecardRequestToDto, } from 'src/dto/scorecard.dto'; import { ChallengeTrack } from 'src/shared/enums/challengeTrack.enum'; import { PrismaService } from '../../shared/modules/global/prisma.service'; +import { ScoreCardService } from './scorecard.service'; +import { OkResponse } from 'src/dto/common.dto'; +import { Response } from 'express'; +import { PaginationHeaderInterceptor } from 'src/interceptors/PaginationHeaderInterceptor'; @ApiTags('Scorecard') @ApiBearerAuth() @Controller('/scorecards') export class ScorecardController { - constructor(private readonly prisma: PrismaService) {} + constructor(private readonly prisma: PrismaService, private readonly scorecardService: ScoreCardService) {} @Post() @Roles(UserRole.Admin) @@ -48,12 +56,12 @@ export class ScorecardController { @ApiResponse({ status: 201, description: 'Scorecard added successfully.', - type: ScorecardResponseDto, + type: ScorecardWithGroupResponseDto, }) @ApiResponse({ status: 403, description: 'Forbidden.' }) async addScorecard( @Body() body: ScorecardRequestDto, - ): Promise { + ): Promise { const data = await this.prisma.scorecard.create({ data: mapScorecardRequestToDto(body), include: { @@ -68,7 +76,7 @@ export class ScorecardController { }, }, }); - return data as ScorecardResponseDto; + return data as ScorecardWithGroupResponseDto; } @Put('/:id') @@ -87,14 +95,14 @@ export class ScorecardController { @ApiResponse({ status: 200, description: 'Scorecard updated successfully.', - type: ScorecardResponseDto, + type: ScorecardWithGroupResponseDto, }) @ApiResponse({ status: 403, description: 'Forbidden.' }) @ApiResponse({ status: 404, description: 'Scorecard not found.' }) async editScorecard( @Param('id') id: string, - @Body() body: ScorecardRequestDto, - ): Promise { + @Body() body: ScorecardWithGroupResponseDto, + ): Promise { console.log(JSON.stringify(body)); const data = await this.prisma.scorecard @@ -121,7 +129,7 @@ export class ScorecardController { message: `Error: ${error.code}`, }); }); - return data as ScorecardResponseDto; + return data as ScorecardWithGroupResponseDto; } @Delete(':id') @@ -139,7 +147,7 @@ export class ScorecardController { @ApiResponse({ status: 200, description: 'Scorecard deleted successfully.', - type: ScorecardResponseDto, + type: ScorecardWithGroupResponseDto, }) @ApiResponse({ status: 403, description: 'Forbidden.' }) @ApiResponse({ status: 404, description: 'Scorecard not found.' }) @@ -173,10 +181,10 @@ export class ScorecardController { @ApiResponse({ status: 200, description: 'Scorecard retrieved successfully.', - type: ScorecardResponseDto, + type: ScorecardWithGroupResponseDto, }) @ApiResponse({ status: 404, description: 'Scorecard not found.' }) - async viewScorecard(@Param('id') id: string): Promise { + async viewScorecard(@Param('id') id: string): Promise { const data = await this.prisma.scorecard .findUniqueOrThrow({ where: { id }, @@ -200,11 +208,11 @@ export class ScorecardController { message: `Error: ${error.code}`, }); }); - return data as ScorecardResponseDto; + return data as ScorecardWithGroupResponseDto; } @Get() - @Roles(UserRole.Admin, UserRole.Copilot) + @Roles(UserRole.Admin) @Scopes(Scope.ReadScorecard) @ApiOperation({ summary: 'Search scorecards', @@ -249,34 +257,31 @@ export class ScorecardController { description: 'List of matching scorecards', type: [ScorecardResponseDto], }) + @UseInterceptors(PaginationHeaderInterceptor) async searchScorecards( - @Query('challengeTrack') challengeTrack?: ChallengeTrack, - @Query('challengeType') challengeType?: string, + @Query('challengeTrack') challengeTrack?: ChallengeTrack | ChallengeTrack[], + @Query('challengeType') challengeType?: string | string[], @Query('name') name?: string, @Query('page') page: number = 1, @Query('perPage') perPage: number = 10, - ) { - const skip = (page - 1) * perPage; - const data = await this.prisma.scorecard.findMany({ - where: { - ...(challengeTrack && { challengeTrack }), - ...(challengeType && { challengeType }), - ...(name && { name: { contains: name, mode: 'insensitive' } }), - }, - include: { - scorecardGroups: { - include: { - sections: { - include: { - questions: true, - }, - }, - }, - }, - }, - skip, - take: perPage, + ): Promise { + const challengeTrackArray = Array.isArray(challengeTrack) + ? challengeTrack + : challengeTrack + ? [challengeTrack] + : []; + const challengeTypeArray = Array.isArray(challengeType) + ? challengeType + : challengeType + ? [challengeType] + : []; + const result = await this.scorecardService.getScoreCards({ + challengeTrack: challengeTrackArray, + challengeType: challengeTypeArray, + name, + page, + perPage, }); - return data as ScorecardResponseDto[]; + return result; } } diff --git a/src/api/scorecard/scorecard.service.ts b/src/api/scorecard/scorecard.service.ts new file mode 100644 index 0000000..ded9f74 --- /dev/null +++ b/src/api/scorecard/scorecard.service.ts @@ -0,0 +1,69 @@ +import { Injectable } from "@nestjs/common"; +import { ScorecardPaginatedResponseDto, ScorecardQueryDto, ScorecardResponseDto } from "src/dto/scorecard.dto"; +import { PrismaService } from "src/shared/modules/global/prisma.service"; + +@Injectable() +export class ScoreCardService { + constructor( + private readonly prisma: PrismaService, + ) {} + + /** + * Get list of score cards and send it in paginated way + * @param authUser auth user + * @param dto create data + * @returns response dto + */ + async getScoreCards( + query: ScorecardQueryDto + ): Promise { + const { page = 1, perPage = 10, challengeTrack, challengeType, name } = query; + const skip = (page - 1) * perPage; + const data = await this.prisma.scorecard.findMany({ + where: { + ...(challengeTrack?.length && { + challengeTrack: { + in: challengeTrack, + }, + }), + ...(challengeType?.length && { + challengeType: { + in: challengeType, + }, + }), + ...(name && { name: { contains: name, mode: 'insensitive' } }), + }, + skip, + take: perPage, + orderBy: { + name: 'asc', + }, + }); + + const totalCount = await this.prisma.scorecard.count({ + where: { + ...(challengeTrack?.length && { + challengeTrack: { + in: challengeTrack, + }, + }), + ...(challengeType?.length && { + challengeType: { + in: challengeType, + }, + }), + ...(name && { name: { contains: name, mode: 'insensitive' } }), + }, + }); + + return { + metadata: { + total: totalCount, + page, + perPage, + totalPages: Math.ceil(totalCount/perPage), + }, + scoreCards: data as ScorecardResponseDto[], + }; + } +} \ No newline at end of file diff --git a/src/dto/scorecard.dto.ts b/src/dto/scorecard.dto.ts index 09d835d..543efc4 100644 --- a/src/dto/scorecard.dto.ts +++ b/src/dto/scorecard.dto.ts @@ -203,11 +203,13 @@ export class ScorecardBaseDto { example: 'user456', }) updatedBy: string; +} +export class ScorecardBaseWithGroupsDto extends ScorecardBaseDto { scorecardGroups: any[]; } -export class ScorecardRequestDto extends ScorecardBaseDto { +export class ScorecardRequestDto extends ScorecardBaseWithGroupsDto { @ApiProperty({ description: 'The ID of the scorecard', example: 'abc123' }) id: string; @@ -221,6 +223,11 @@ export class ScorecardRequestDto extends ScorecardBaseDto { export class ScorecardResponseDto extends ScorecardBaseDto { @ApiProperty({ description: 'The ID of the scorecard', example: 'abc123' }) id: string; +} + +export class ScorecardWithGroupResponseDto extends ScorecardBaseDto { + @ApiProperty({ description: 'The ID of the scorecard', example: 'abc123' }) + id: string; @ApiProperty({ description: 'The list of groups associated with the scorecard', @@ -229,6 +236,39 @@ export class ScorecardResponseDto extends ScorecardBaseDto { scorecardGroups: ScorecardGroupResponseDto[]; } +export class PaginationMetaDto { + @ApiProperty({ example: 100 }) + total: number; + + @ApiProperty({ example: 1 }) + page: number; + + @ApiProperty({ example: 10 }) + perPage: number; + + @ApiProperty({ example: 10 }) + totalPages: number; +} + +export class ScorecardPaginatedResponseDto { + @ApiProperty({ description: 'This contains pagination metadata' }) + metadata: PaginationMetaDto; + + @ApiProperty({ + description: 'The list of score cards', + type: [ScorecardGroupResponseDto], + }) + scoreCards: ScorecardResponseDto[]; +} + +export class ScorecardQueryDto { + challengeTrack?: ChallengeTrack[]; + challengeType?: string[]; + name?: string; + page?: number; + perPage?: number; +} + export function mapScorecardRequestToDto(request: ScorecardRequestDto) { const userFields = { createdBy: request.createdBy, diff --git a/src/interceptors/PaginationHeaderInterceptor.ts b/src/interceptors/PaginationHeaderInterceptor.ts new file mode 100644 index 0000000..8a9aa87 --- /dev/null +++ b/src/interceptors/PaginationHeaderInterceptor.ts @@ -0,0 +1,21 @@ +import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from "@nestjs/common"; +import { Observable, tap } from "rxjs"; +import { Response } from "express"; + +@Injectable() +export class PaginationHeaderInterceptor implements NestInterceptor { + intercept(context: ExecutionContext, next: CallHandler): Observable { + const res = context.switchToHttp().getResponse(); + return next.handle().pipe( + tap((response) => { + if (response?.metadata) { + const { total, page, perPage, totalPages } = response.metadata; + res.setHeader('X-Total-Count', total); + res.setHeader('X-Page', page); + res.setHeader('X-Per-Page', perPage); + res.setHeader('X-Total-Pages', totalPages); + } + }), + ); + } +} From 7db87876a9cd5e1642ad9c50939636156bcde9e4 Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Thu, 7 Aug 2025 00:58:13 +0200 Subject: [PATCH 2/5] feat: modified search score cards endpoint --- src/api/scorecard/scorecard.service.ts | 45 ++++++++++---------------- 1 file changed, 17 insertions(+), 28 deletions(-) diff --git a/src/api/scorecard/scorecard.service.ts b/src/api/scorecard/scorecard.service.ts index ded9f74..4011ec6 100644 --- a/src/api/scorecard/scorecard.service.ts +++ b/src/api/scorecard/scorecard.service.ts @@ -1,4 +1,5 @@ import { Injectable } from "@nestjs/common"; +import { Prisma } from "@prisma/client"; import { ScorecardPaginatedResponseDto, ScorecardQueryDto, ScorecardResponseDto } from "src/dto/scorecard.dto"; import { PrismaService } from "src/shared/modules/global/prisma.service"; @@ -10,8 +11,7 @@ export class ScoreCardService { /** * Get list of score cards and send it in paginated way - * @param authUser auth user - * @param dto create data + * @param query query params * @returns response dto */ async getScoreCards( @@ -19,20 +19,21 @@ export class ScoreCardService { ): Promise { const { page = 1, perPage = 10, challengeTrack, challengeType, name } = query; const skip = (page - 1) * perPage; + const where: Prisma.scorecardWhereInput = { + ...(challengeTrack?.length && { + challengeTrack: { + in: challengeTrack, + }, + }), + ...(challengeType?.length && { + challengeType: { + in: challengeType, + }, + }), + ...(name && { name: { contains: name, mode: 'insensitive' } }), + }; const data = await this.prisma.scorecard.findMany({ - where: { - ...(challengeTrack?.length && { - challengeTrack: { - in: challengeTrack, - }, - }), - ...(challengeType?.length && { - challengeType: { - in: challengeType, - }, - }), - ...(name && { name: { contains: name, mode: 'insensitive' } }), - }, + where, skip, take: perPage, orderBy: { @@ -41,19 +42,7 @@ export class ScoreCardService { }); const totalCount = await this.prisma.scorecard.count({ - where: { - ...(challengeTrack?.length && { - challengeTrack: { - in: challengeTrack, - }, - }), - ...(challengeType?.length && { - challengeType: { - in: challengeType, - }, - }), - ...(name && { name: { contains: name, mode: 'insensitive' } }), - }, + where, }); return { From 278a90ce22064e1fb68aec929430b0aaa49e54c5 Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Thu, 7 Aug 2025 21:47:46 +0200 Subject: [PATCH 3/5] chore: refactored code based on review comments --- src/api/scorecard/scorecard.controller.ts | 90 +---------------- src/api/scorecard/scorecard.service.ts | 115 +++++++++++++++++++++- 2 files changed, 118 insertions(+), 87 deletions(-) diff --git a/src/api/scorecard/scorecard.controller.ts b/src/api/scorecard/scorecard.controller.ts index 0559537..b58b026 100644 --- a/src/api/scorecard/scorecard.controller.ts +++ b/src/api/scorecard/scorecard.controller.ts @@ -7,9 +7,6 @@ import { Body, Param, Query, - NotFoundException, - InternalServerErrorException, - Res, UseInterceptors, } from '@nestjs/common'; import { @@ -35,8 +32,6 @@ import { import { ChallengeTrack } from 'src/shared/enums/challengeTrack.enum'; import { PrismaService } from '../../shared/modules/global/prisma.service'; import { ScoreCardService } from './scorecard.service'; -import { OkResponse } from 'src/dto/common.dto'; -import { Response } from 'express'; import { PaginationHeaderInterceptor } from 'src/interceptors/PaginationHeaderInterceptor'; @ApiTags('Scorecard') @@ -62,21 +57,7 @@ export class ScorecardController { async addScorecard( @Body() body: ScorecardRequestDto, ): Promise { - const data = await this.prisma.scorecard.create({ - data: mapScorecardRequestToDto(body), - include: { - scorecardGroups: { - include: { - sections: { - include: { - questions: true, - }, - }, - }, - }, - }, - }); - return data as ScorecardWithGroupResponseDto; + return await this.scorecardService.addScorecard(body); } @Put('/:id') @@ -102,34 +83,8 @@ export class ScorecardController { async editScorecard( @Param('id') id: string, @Body() body: ScorecardWithGroupResponseDto, - ): Promise { - console.log(JSON.stringify(body)); - - const data = await this.prisma.scorecard - .update({ - where: { id }, - data: mapScorecardRequestToDto(body), - include: { - scorecardGroups: { - include: { - sections: { - include: { - questions: true, - }, - }, - }, - }, - }, - }) - .catch((error) => { - if (error.code !== 'P2025') { - throw new NotFoundException({ message: `Scorecard not found.` }); - } - throw new InternalServerErrorException({ - message: `Error: ${error.code}`, - }); - }); - return data as ScorecardWithGroupResponseDto; + ): Promise { + return await this.scorecardService.editScorecard(id, body); } @Delete(':id') @@ -152,19 +107,7 @@ export class ScorecardController { @ApiResponse({ status: 403, description: 'Forbidden.' }) @ApiResponse({ status: 404, description: 'Scorecard not found.' }) async deleteScorecard(@Param('id') id: string) { - await this.prisma.scorecard - .delete({ - where: { id }, - }) - .catch((error) => { - if (error.code !== 'P2025') { - throw new NotFoundException({ message: `Scorecard not found.` }); - } - throw new InternalServerErrorException({ - message: `Error: ${error.code}`, - }); - }); - return { message: `Scorecard ${id} deleted successfully.` }; + return await this.scorecardService.deleteScorecard(id); } @Get('/:id') @@ -185,30 +128,7 @@ export class ScorecardController { }) @ApiResponse({ status: 404, description: 'Scorecard not found.' }) async viewScorecard(@Param('id') id: string): Promise { - const data = await this.prisma.scorecard - .findUniqueOrThrow({ - where: { id }, - include: { - scorecardGroups: { - include: { - sections: { - include: { - questions: true, - }, - }, - }, - }, - }, - }) - .catch((error) => { - if (error.code !== 'P2025') { - throw new NotFoundException({ message: `Scorecard not found.` }); - } - throw new InternalServerErrorException({ - message: `Error: ${error.code}`, - }); - }); - return data as ScorecardWithGroupResponseDto; + return await this.scorecardService.viewScorecard(id); } @Get() diff --git a/src/api/scorecard/scorecard.service.ts b/src/api/scorecard/scorecard.service.ts index 4011ec6..8bc47f7 100644 --- a/src/api/scorecard/scorecard.service.ts +++ b/src/api/scorecard/scorecard.service.ts @@ -1,6 +1,6 @@ -import { Injectable } from "@nestjs/common"; +import { Injectable, InternalServerErrorException, NotFoundException } from "@nestjs/common"; import { Prisma } from "@prisma/client"; -import { ScorecardPaginatedResponseDto, ScorecardQueryDto, ScorecardResponseDto } from "src/dto/scorecard.dto"; +import { mapScorecardRequestToDto, ScorecardPaginatedResponseDto, ScorecardQueryDto, ScorecardRequestDto, ScorecardResponseDto, ScorecardWithGroupResponseDto } from "src/dto/scorecard.dto"; import { PrismaService } from "src/shared/modules/global/prisma.service"; @Injectable() @@ -9,6 +9,117 @@ export class ScoreCardService { private readonly prisma: PrismaService, ) {} + /** + * Adds score card + * @param body body from request + * @returns ScorecardWithGroupResponseDto + */ + async addScorecard(body: ScorecardRequestDto): Promise { + const data = await this.prisma.scorecard.create({ + data: mapScorecardRequestToDto(body), + include: { + scorecardGroups: { + include: { + sections: { + include: { + questions: true, + }, + }, + }, + }, + }, + }); + + return data as ScorecardWithGroupResponseDto; + } + + /** + * Edit score card + * @param body body from request + * @returns ScorecardWithGroupResponseDto + */ + async editScorecard(id: string, body: ScorecardWithGroupResponseDto): Promise { + const data = await this.prisma.scorecard + .update({ + where: { id }, + data: mapScorecardRequestToDto(body), + include: { + scorecardGroups: { + include: { + sections: { + include: { + questions: true, + }, + }, + }, + }, + }, + }) + .catch((error) => { + if (error.code !== 'P2025') { + throw new NotFoundException({ message: `Scorecard not found.` }); + } + throw new InternalServerErrorException({ + message: `Error: ${error.code}`, + }); + }); + + return data as ScorecardWithGroupResponseDto; + } + + /** + * Delete score card + * @param id score card id + * @returns + */ + async deleteScorecard(id: string): Promise<{ message: string }> { + await this.prisma.scorecard + .delete({ + where: { id }, + }) + .catch((error) => { + if (error.code !== 'P2025') { + throw new NotFoundException({ message: `Scorecard not found.` }); + } + throw new InternalServerErrorException({ + message: `Error: ${error.code}`, + }); + }); + return { message: `Scorecard ${id} deleted successfully.` }; + } + + /** + * View score card + * @param id score card id + * @returns + */ + async viewScorecard(id: string): Promise { + const data = await this.prisma.scorecard + .findUniqueOrThrow({ + where: { id }, + include: { + scorecardGroups: { + include: { + sections: { + include: { + questions: true, + }, + }, + }, + }, + }, + }) + .catch((error) => { + if (error.code !== 'P2025') { + throw new NotFoundException({ message: `Scorecard not found.` }); + } + throw new InternalServerErrorException({ + message: `Error: ${error.code}`, + }); + }); + return data as ScorecardWithGroupResponseDto; + } + /** * Get list of score cards and send it in paginated way * @param query query params From dd01d79c7c2ca7f6b8da1f8311566e75f47851c8 Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Thu, 7 Aug 2025 21:48:33 +0200 Subject: [PATCH 4/5] chore: refactored code based on review comments --- src/api/scorecard/scorecard.controller.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/api/scorecard/scorecard.controller.ts b/src/api/scorecard/scorecard.controller.ts index b58b026..1236057 100644 --- a/src/api/scorecard/scorecard.controller.ts +++ b/src/api/scorecard/scorecard.controller.ts @@ -27,7 +27,6 @@ import { ScorecardRequestDto, ScorecardResponseDto, ScorecardWithGroupResponseDto, - mapScorecardRequestToDto, } from 'src/dto/scorecard.dto'; import { ChallengeTrack } from 'src/shared/enums/challengeTrack.enum'; import { PrismaService } from '../../shared/modules/global/prisma.service'; @@ -38,7 +37,7 @@ import { PaginationHeaderInterceptor } from 'src/interceptors/PaginationHeaderIn @ApiBearerAuth() @Controller('/scorecards') export class ScorecardController { - constructor(private readonly prisma: PrismaService, private readonly scorecardService: ScoreCardService) {} + constructor(private readonly scorecardService: ScoreCardService) {} @Post() @Roles(UserRole.Admin) From ccefae6615d649d6f09b7f6f24aa2e9627e6a597 Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Thu, 7 Aug 2025 21:48:44 +0200 Subject: [PATCH 5/5] chore: refactored code based on review comments --- src/api/scorecard/scorecard.controller.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/api/scorecard/scorecard.controller.ts b/src/api/scorecard/scorecard.controller.ts index 1236057..b314171 100644 --- a/src/api/scorecard/scorecard.controller.ts +++ b/src/api/scorecard/scorecard.controller.ts @@ -29,7 +29,6 @@ import { ScorecardWithGroupResponseDto, } from 'src/dto/scorecard.dto'; import { ChallengeTrack } from 'src/shared/enums/challengeTrack.enum'; -import { PrismaService } from '../../shared/modules/global/prisma.service'; import { ScoreCardService } from './scorecard.service'; import { PaginationHeaderInterceptor } from 'src/interceptors/PaginationHeaderInterceptor';