Skip to content

Commit

Permalink
feat: add base pagination (#56)
Browse files Browse the repository at this point in the history
  • Loading branch information
khuongln-1346 authored Nov 12, 2024
1 parent e3e3f72 commit 18c64df
Show file tree
Hide file tree
Showing 15 changed files with 769 additions and 4 deletions.
20 changes: 18 additions & 2 deletions apps/admin-api/src/api/article/article.service.spec.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,28 @@
import { Test, TestingModule } from '@nestjs/testing';
import { getRepositoryToken } from '@nestjs/typeorm';
import { ArticleEntity } from '@repo/database-typeorm';
import { Repository } from 'typeorm';
import { ArticleService } from './article.service';

describe('ArticleService', () => {
let service: ArticleService;
let articleRepositoryValue: Partial<
Record<keyof Repository<ArticleEntity>, jest.Mock>
>;

beforeAll(async () => {
articleRepositoryValue = {
findOne: jest.fn(),
};

beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [ArticleService],
providers: [
ArticleService,
{
provide: getRepositoryToken(ArticleEntity),
useValue: articleRepositoryValue,
},
],
}).compile();

service = module.get<ArticleService>(ArticleService);
Expand Down
10 changes: 8 additions & 2 deletions apps/admin-api/src/api/article/article.service.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { ArticleEntity } from '@repo/database-typeorm';
import { Repository } from 'typeorm';

@Injectable()
export class ArticleService {
constructor() {}
constructor(
@InjectRepository(ArticleEntity)
private readonly articleRepository: Repository<ArticleEntity>,
) {}

async list() {
throw new Error('Method not implemented.');
this.articleRepository.find();
}

async feed() {
Expand Down
8 changes: 8 additions & 0 deletions packages/api/src/constants/app.constant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,11 @@ export enum LogService {
GOOGLE_LOGGING = 'google_logging',
AWS_CLOUDWATCH = 'aws_cloudwatch',
}

export enum Order {
ASC = 'ASC',
DESC = 'DESC',
}

export const DEFAULT_PAGE_LIMIT = 10;
export const DEFAULT_CURRENT_PAGE = 1;
33 changes: 33 additions & 0 deletions packages/api/src/dto/cursor-pagination/cursor-pagination.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { ApiProperty } from '@nestjs/swagger';
import { Expose } from 'class-transformer';
import { PageOptionsDto } from './page-options.dto';

export class CursorPaginationDto {
@ApiProperty()
@Expose()
readonly limit: number;

@ApiProperty()
@Expose()
readonly afterCursor?: string;

@ApiProperty()
@Expose()
readonly beforeCursor?: string;

@ApiProperty()
@Expose()
readonly totalRecords: number;

constructor(
totalRecords: number,
afterCursor: string,
beforeCursor: string,
pageOptions: PageOptionsDto,
) {
this.limit = pageOptions.limit;
this.afterCursor = afterCursor;
this.beforeCursor = beforeCursor;
this.totalRecords = totalRecords;
}
}
20 changes: 20 additions & 0 deletions packages/api/src/dto/cursor-pagination/page-options.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { DEFAULT_PAGE_LIMIT } from '../../constants';
import { NumberFieldOptional, StringFieldOptional } from '../../decorators';

export class PageOptionsDto {
@StringFieldOptional()
afterCursor?: string;

@StringFieldOptional()
beforeCursor?: string;

@NumberFieldOptional({
minimum: 1,
default: DEFAULT_PAGE_LIMIT,
int: true,
})
readonly limit: number = DEFAULT_PAGE_LIMIT;

@StringFieldOptional()
readonly q?: string;
}
18 changes: 18 additions & 0 deletions packages/api/src/dto/cursor-pagination/paginated.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { ApiProperty } from '@nestjs/swagger';
import { Expose } from 'class-transformer';
import { CursorPaginationDto } from './cursor-pagination.dto';

export class CursorPaginatedDto<TData> {
@ApiProperty({ type: [Object] })
@Expose()
readonly data: TData[];

@ApiProperty()
@Expose()
pagination: CursorPaginationDto;

constructor(data: TData[], meta: CursorPaginationDto) {
this.data = data;
this.pagination = meta;
}
}
43 changes: 43 additions & 0 deletions packages/api/src/dto/offset-pagination/offset-pagination.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { ApiProperty } from '@nestjs/swagger';
import { Expose } from 'class-transformer';
import { PageOptionsDto } from './page-options.dto';

export class OffsetPaginationDto {
@ApiProperty()
@Expose()
readonly limit: number;

@ApiProperty()
@Expose()
readonly currentPage: number;

@ApiProperty()
@Expose()
readonly nextPage?: number;

@ApiProperty()
@Expose()
readonly previousPage?: number;

@ApiProperty()
@Expose()
readonly totalRecords: number;

@ApiProperty()
@Expose()
readonly totalPages: number;

constructor(totalRecords: number, pageOptions: PageOptionsDto) {
this.limit = pageOptions.limit;
this.currentPage = pageOptions.offset / pageOptions.limit + 1;
this.nextPage =
this.currentPage < this.totalPages ? this.currentPage + 1 : undefined;
this.previousPage =
this.currentPage > 1 && this.currentPage - 1 < this.totalPages
? this.currentPage - 1
: undefined;
this.totalRecords = totalRecords;
this.totalPages =
this.limit > 0 ? Math.ceil(totalRecords / pageOptions.limit) : 0;
}
}
28 changes: 28 additions & 0 deletions packages/api/src/dto/offset-pagination/page-options.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { DEFAULT_PAGE_LIMIT, Order } from '../../constants';
import {
EnumFieldOptional,
NumberFieldOptional,
StringFieldOptional,
} from '../../decorators';

export class PageOptionsDto {
@NumberFieldOptional({
minimum: 1,
default: DEFAULT_PAGE_LIMIT,
int: true,
})
readonly limit: number = DEFAULT_PAGE_LIMIT;

@NumberFieldOptional({
minimum: 0,
default: 0,
int: true,
})
readonly offset: number = 0;

@StringFieldOptional()
readonly q?: string;

@EnumFieldOptional(() => Order, { default: Order.ASC })
readonly order: Order = Order.ASC;
}
18 changes: 18 additions & 0 deletions packages/api/src/dto/offset-pagination/paginated.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { ApiProperty } from '@nestjs/swagger';
import { Expose } from 'class-transformer';
import { OffsetPaginationDto } from './offset-pagination.dto';

export class OffsetPaginatedDto<TData> {
@ApiProperty({ type: [Object] })
@Expose()
readonly data: TData[];

@ApiProperty()
@Expose()
pagination: OffsetPaginationDto;

constructor(data: TData[], meta: OffsetPaginationDto) {
this.data = data;
this.pagination = meta;
}
}
36 changes: 36 additions & 0 deletions packages/api/src/dto/page-pagination/page-options.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import {
DEFAULT_CURRENT_PAGE,
DEFAULT_PAGE_LIMIT,
Order,
} from '../../constants';
import {
EnumFieldOptional,
NumberFieldOptional,
StringFieldOptional,
} from '../../decorators';

export class PageOptionsDto {
@NumberFieldOptional({
minimum: 1,
default: DEFAULT_PAGE_LIMIT,
int: true,
})
readonly limit: number = DEFAULT_PAGE_LIMIT;

@NumberFieldOptional({
minimum: 1,
default: DEFAULT_CURRENT_PAGE,
int: true,
})
readonly page: number = DEFAULT_CURRENT_PAGE;

@StringFieldOptional()
readonly q?: string;

@EnumFieldOptional(() => Order, { default: Order.ASC })
readonly order: Order = Order.ASC;

get offset() {
return this.page ? (this.page - 1) * this.limit : 0;
}
}
43 changes: 43 additions & 0 deletions packages/api/src/dto/page-pagination/page-pagination.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { ApiProperty } from '@nestjs/swagger';
import { Expose } from 'class-transformer';
import { PageOptionsDto } from './page-options.dto';

export class PagePaginationDto {
@ApiProperty()
@Expose()
readonly limit: number;

@ApiProperty()
@Expose()
readonly currentPage: number;

@ApiProperty()
@Expose()
readonly nextPage?: number;

@ApiProperty()
@Expose()
readonly previousPage?: number;

@ApiProperty()
@Expose()
readonly totalRecords: number;

@ApiProperty()
@Expose()
readonly totalPages: number;

constructor(totalRecords: number, pageOptions: PageOptionsDto) {
this.limit = pageOptions.limit;
this.currentPage = pageOptions.page;
this.nextPage =
this.currentPage < this.totalPages ? this.currentPage + 1 : undefined;
this.previousPage =
this.currentPage > 1 && this.currentPage - 1 < this.totalPages
? this.currentPage - 1
: undefined;
this.totalRecords = totalRecords;
this.totalPages =
this.limit > 0 ? Math.ceil(totalRecords / pageOptions.limit) : 0;
}
}
18 changes: 18 additions & 0 deletions packages/api/src/dto/page-pagination/paginated.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { ApiProperty } from '@nestjs/swagger';
import { Expose } from 'class-transformer';
import { PagePaginationDto } from './page-pagination.dto';

export class PagePaginatedDto<TData> {
@ApiProperty({ type: [Object] })
@Expose()
readonly data: TData[];

@ApiProperty()
@Expose()
pagination: PagePaginationDto;

constructor(data: TData[], meta: PagePaginationDto) {
this.data = data;
this.pagination = meta;
}
}
Loading

0 comments on commit 18c64df

Please sign in to comment.