Skip to content

Commit

Permalink
fix: LC-61 add generic pagination
Browse files Browse the repository at this point in the history
  • Loading branch information
tomaszbilka committed Aug 12, 2024
1 parent e92ca5e commit d36cca5
Show file tree
Hide file tree
Showing 6 changed files with 189 additions and 157 deletions.
27 changes: 18 additions & 9 deletions apps/api/src/categories/api/categories.controller.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,37 @@
import { Controller, Get, Query } from "@nestjs/common";
import { Controller, Get } from "@nestjs/common";
import { Validate } from "nestjs-typebox";

import {
AllCategoriesResponse,
allCategoriesSchema,
} from "../schemas/categoty.schema";
import { BaseResponse, baseResponse, QueryParamsSchema } from "src/common";
import {
basePaginatedResponse,
BasePaginatedResponse,
BasePagination,
PaginationValidation,
} from "src/common";
import { CategorieService } from "../categories.service";
import { CategoriesQuery } from "./types";

@Controller("categories")
export class CategorieController {
constructor(private readonly categoriesService: CategorieService) {}

@Get()
@Validate({
// request: [{ type: "query", name: "categories", schema: QueryParamsSchema }],
response: baseResponse(allCategoriesSchema),
request: PaginationValidation,
response: basePaginatedResponse(allCategoriesSchema),
})
async getAllCategories(
@Query() query: CategoriesQuery,
): Promise<BaseResponse<AllCategoriesResponse>> {
const categories = await this.categoriesService.getCategories(query);
filter?: string,
limit?: number,
offset?: number,
sort?: string,
): Promise<BasePaginatedResponse<AllCategoriesResponse, BasePagination>> {
const query = { filter, limit, offset, sort };

const data = await this.categoriesService.getCategories(query);

return new BaseResponse(categories) as any;
return new BasePaginatedResponse(data.categories, data.pagination);
}
}
76 changes: 42 additions & 34 deletions apps/api/src/categories/categories.service.ts
Original file line number Diff line number Diff line change
@@ -1,47 +1,55 @@
// import { asc, count, desc, like, sql } from "drizzle-orm";
import { count, like, sql } from "drizzle-orm";
import { Inject, Injectable } from "@nestjs/common";

import { DatabasePg } from "src/common";
import { CategoriesQuery } from "./api/types";
import { categories, users } from "src/storage/schema";
import { getDataPagination } from "src/utils/pagination";
import { categories } from "src/storage/schema";
import {
addPagination,
DEFAULT_PAGINATION_LIMIT,
getPageAndPageSize,
getSortOptions,
} from "src/utils/pagination";

@Injectable()
export class CategorieService {
constructor(@Inject("DB") private readonly db: DatabasePg) {}

// private createLikeFilter(filter: string) {
// return like(categories.title, `%${filter}%`);
// }
private createLikeFilter(filter: string) {
return like(categories.title, `%${filter}%`);
}

public async getCategories(query: CategoriesQuery) {
// const { sort = "", limit = 10, offset = 0, filter = "" } = query;

// const sortOrder = sort.startsWith("-") ? desc : asc;
// const filterCondition = this.createLikeFilter(query.filter as string);

// const queryDB = this.db
// .select()
// .from(categories)
// .where(like(categories.title, `%${filter}%`))
// .orderBy(sortOrder(sql`LOWER(${categories.title})`))
// .limit(limit)
// .offset(offset);

// const data = await queryDB;

// const [totalItems] = await this.db
// .select({ count: count() })
// .from(categories)
// .where(filterCondition);

//---- external function ------
const data = await getDataPagination({
model: categories,
query,
db: this.db,
});
//-----------------------
return { categories: data.categories, totalItems: data.totalItems };
const {
sort = "",
limit = DEFAULT_PAGINATION_LIMIT,
offset = 0,
filter = "",
} = query;

const { sortOrder } = getSortOptions(sort);
const filterCondition = this.createLikeFilter(filter);

const queryDB = this.db
.select()
.from(categories)
.where(filterCondition)
.orderBy(sortOrder(sql`LOWER(${categories.title})`));

const { pageSize, page } = getPageAndPageSize({ limit, offset });

const paginatedData = addPagination({ queryDB, page, pageSize });

const data = await paginatedData;

const [totalItems] = await this.db
.select({ count: count() })
.from(categories)
.where(filterCondition);

return {
categories: data,
pagination: { totalItems: totalItems.count, page, pageSize },
};
}
}
7 changes: 3 additions & 4 deletions apps/api/src/categories/schemas/categoty.schema.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { Type, Static } from "@sinclair/typebox";
import { commonCategorySchema } from "src/categories/schemas/common-category.schema";

export const allCategoriesSchema = Type.Object({
categories: Type.Array(Type.Pick(commonCategorySchema, ["id", "title"])),
totalItems: Type.Number(),
});
export const allCategoriesSchema = Type.Array(
Type.Pick(commonCategorySchema, ["id", "title"]),
);

export type AllCategoriesResponse = Static<typeof allCategoriesSchema>;
52 changes: 47 additions & 5 deletions apps/api/src/common/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,16 @@ export class BaseResponse<T> {
}
}

export class BasePaginatedResponse<T, K> {
data: T;
pagination: K;

constructor(data: T, pagination: K) {
this.data = data;
this.pagination = pagination;
}
}

export const UUIDSchema = Type.String({ format: "uuid" });
export type UUIDType = Static<typeof UUIDSchema>;

Expand All @@ -26,13 +36,45 @@ export function baseResponse(data: TSchema) {
});
}

export function basePaginatedResponse(data: TSchema) {
return Type.Object({
data,
pagination: basePagination,
});
}

export function nullResponse() {
return Type.Null();
}

export const QueryParamsSchema = Type.Object({
limit: Type.Optional(Type.Number()),
offset: Type.Optional(Type.Number()),
sort: Type.Optional(Type.String()),
filter: Type.Optional(Type.String()),
export const PaginationValidation = [
{
name: "limit" as const,
type: "query" as const,
schema: Type.Optional(Type.Number()),
coerceTypes: true,
},
{
name: "offset" as const,
type: "query" as const,
schema: Type.Optional(Type.Number()),
coerceTypes: true,
},
{
name: "sort" as const,
type: "query" as const,
schema: Type.Optional(Type.String()),
},
{
name: "filter" as const,
type: "query" as const,
schema: Type.Optional(Type.String()),
},
];

export const basePagination = Type.Object({
totalItems: Type.Number(),
page: Type.Number(),
pageSize: Type.Number(),
});
export type BasePagination = Static<typeof basePagination>;
81 changes: 57 additions & 24 deletions apps/api/src/swagger/api-schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,40 @@
"/categories": {
"get": {
"operationId": "CategorieController_getAllCategories",
"parameters": [],
"parameters": [
{
"name": "filter",
"required": false,
"in": "query",
"schema": {
"type": "string"
}
},
{
"name": "sort",
"required": false,
"in": "query",
"schema": {
"type": "string"
}
},
{
"name": "offset",
"required": false,
"in": "query",
"schema": {
"type": "number"
}
},
{
"name": "limit",
"required": false,
"in": "query",
"schema": {
"type": "number"
}
}
],
"responses": {
"200": {
"content": {
Expand Down Expand Up @@ -611,37 +644,37 @@
"type": "object",
"properties": {
"data": {
"type": "object",
"properties": {
"categories": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"title": {
"type": "string"
}
},
"required": [
"id",
"title"
]
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"title": {
"type": "string"
}
},
"required": ["id", "title"]
}
},
"pagination": {
"type": "object",
"properties": {
"totalItems": {
"type": "number"
},
"page": {
"type": "number"
},
"pageSize": {
"type": "number"
}
},
"required": [
"categories",
"totalItems"
]
"required": ["totalItems", "page", "pageSize"]
}
},
"required": ["data"]
"required": ["data", "pagination"]
}
}
}
Expand Down
Loading

0 comments on commit d36cca5

Please sign in to comment.