Skip to content

Commit bee5d5b

Browse files
authored
Merge pull request Lead-Studios#190 from Godsmiracle001/feature/pagination
Implemented pagination module and service in common folder(171) closes Lead-Studios#190
2 parents 4326263 + afa0368 commit bee5d5b

5 files changed

Lines changed: 138 additions & 1 deletion

File tree

backend/src/app.module.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,15 @@ import { RedisService } from './guest/provider/redis.service';
2828
import { GuestUserController } from './guest/guest.controller';
2929
import { GuestUserService } from './guest/guest.service';
3030
import { MailModule } from './mail/mail.module';
31+
32+
import { PaginationModule } from './common/pagination/pagination-controller.controller'; // Your change
33+
34+
35+
36+
3137
import { DictionaryModule } from './dictionary/dictionary.module';
3238

39+
3340
@Module({
3441
imports: [
3542
ConfigModule.forRoot({
@@ -62,6 +69,8 @@ import { DictionaryModule } from './dictionary/dictionary.module';
6269
ResultModule,
6370
SubAdminModule,
6471
GuestModule,
72+
PaginationModule,
73+
MailModule,
6574
GamemodeModule,
6675
GuestUserModule,
6776
GuestFeaturesModule,
@@ -71,4 +80,4 @@ import { DictionaryModule } from './dictionary/dictionary.module';
7180
controllers: [AppController, GuestUserController],
7281
providers: [AppService, GuestUserGuard, RedisService, GuestUserService], // Provide RedisService & GuestGuard globally
7382
})
74-
export class AppModule {}
83+
export class AppModule {}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { IsOptional, IsPositive, IsNumber, Min, IsString } from "class-validator";
2+
import { Type } from "class-transformer";
3+
4+
export class PaginationQueryDto {
5+
@IsOptional()
6+
@Type(() => Number)
7+
@IsNumber()
8+
@Min(1)
9+
page?: number = 1;
10+
11+
@IsOptional()
12+
@Type(() => Number)
13+
@IsNumber()
14+
@Min(1)
15+
limit?: number = 10;
16+
17+
@IsOptional()
18+
@IsString()
19+
sort?: string;
20+
21+
@IsOptional()
22+
@IsString()
23+
filter?: string;
24+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// an interface is a way to define the shape of an object, describing the structure and types of its properties
2+
export class paginated<T> {
3+
data: T[];
4+
meta: {
5+
itemsPerPage: number;
6+
totalItemsPerPage: number;
7+
currentPage: number;
8+
totalPages: number;
9+
};
10+
links: {
11+
firstPage: string;
12+
lastPage: string;
13+
previousPage: string;
14+
currentPage: string;
15+
nextPage: string;
16+
};
17+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { Module } from "@nestjs/common";
2+
import { PaginationService } from "./provider/pagination-service.service";
3+
4+
@Module({
5+
providers: [PaginationService],
6+
exports: [PaginationService],
7+
})
8+
export class PaginationModule {}
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import { Injectable, Inject } from "@nestjs/common";
2+
import {
3+
ObjectLiteral,
4+
Repository,
5+
FindOptionsWhere,
6+
FindOptionsOrder,
7+
} from "typeorm";
8+
import { Request } from "express";
9+
import { REQUEST } from "@nestjs/core";
10+
import { paginated } from "../interfaces/pagination-interface";
11+
import { PaginationQueryDto } from "src/common/pagination/dto/pagination-query-dto.dto";
12+
13+
@Injectable()
14+
export class PaginationService {
15+
constructor(
16+
@Inject(REQUEST)
17+
private readonly request: Request
18+
) {}
19+
20+
public async paginationQuery<T extends ObjectLiteral>(
21+
paginatedQueryDto: PaginationQueryDto,
22+
repository: Repository<T>
23+
): Promise<paginated<T>> {
24+
const { limit = 10, page = 1, sort, filter } = paginatedQueryDto;
25+
26+
// Handle sorting with correct type
27+
const order: FindOptionsOrder<T> = {};
28+
if (sort) {
29+
const isDescending = sort.startsWith("-");
30+
const columnName = isDescending ? sort.substring(1) : sort;
31+
(order as any)[columnName] = isDescending ? "DESC" : "ASC"; // Type assertion to match FindOptionsOrder<T>
32+
}
33+
34+
// Prepare filtering options
35+
const where: FindOptionsWhere<T>[] | undefined = filter
36+
? [
37+
{ name: filter } as unknown as FindOptionsWhere<T>,
38+
{ description: filter } as unknown as FindOptionsWhere<T>,
39+
]
40+
: undefined;
41+
42+
// Fetch paginated results
43+
const [result, totalItems] = await repository.findAndCount({
44+
where,
45+
order,
46+
skip: limit * (page - 1),
47+
take: limit,
48+
});
49+
50+
const totalPages = Math.ceil(totalItems / limit);
51+
const currentPage = page;
52+
const nextPage = page < totalPages ? page + 1 : totalPages;
53+
const previousPage = page > 1 ? page - 1 : 1;
54+
55+
// Construct base URL
56+
const baseUrl = `${this.request.protocol}://${this.request.headers.host}`;
57+
const newUrl = new URL(this.request.url, baseUrl);
58+
59+
// Construct paginated response
60+
const finalResponse: paginated<T> = {
61+
data: result,
62+
meta: {
63+
itemsPerPage: limit,
64+
totalItemsPerPage: totalItems,
65+
currentPage: page,
66+
totalPages: totalPages,
67+
},
68+
links: {
69+
firstPage: `${newUrl.origin}${newUrl.pathname}?limit=${limit}&page=1`,
70+
lastPage: `${newUrl.origin}${newUrl.pathname}?limit=${limit}&page=${totalPages}`,
71+
currentPage: `${newUrl.origin}${newUrl.pathname}?limit=${limit}&page=${currentPage}`,
72+
previousPage: `${newUrl.origin}${newUrl.pathname}?limit=${limit}&page=${previousPage}`,
73+
nextPage: `${newUrl.origin}${newUrl.pathname}?limit=${limit}&page=${nextPage}`,
74+
},
75+
};
76+
77+
return finalResponse;
78+
}
79+
}

0 commit comments

Comments
 (0)