feat: repository relations and nested search and filters and nested sort (#186)
This commit is contained in:
parent
5b2fb73baf
commit
6e733f48e1
@ -2,7 +2,6 @@ import {
|
|||||||
Repository,
|
Repository,
|
||||||
FindConditions,
|
FindConditions,
|
||||||
SelectQueryBuilder,
|
SelectQueryBuilder,
|
||||||
ObjectLiteral,
|
|
||||||
FindOperator,
|
FindOperator,
|
||||||
Equal,
|
Equal,
|
||||||
MoreThan,
|
MoreThan,
|
||||||
@ -20,8 +19,33 @@ import { PaginateQuery } from './decorator'
|
|||||||
import { ServiceUnavailableException } from '@nestjs/common'
|
import { ServiceUnavailableException } from '@nestjs/common'
|
||||||
import { values, mapKeys } from 'lodash'
|
import { values, mapKeys } from 'lodash'
|
||||||
import { stringify } from 'querystring'
|
import { stringify } from 'querystring'
|
||||||
|
import { WherePredicateOperator } from 'typeorm/query-builder/WhereClause'
|
||||||
|
|
||||||
|
type Join<K, P> = K extends string ? (P extends string ? `${K}${'' extends P ? '' : '.'}${P}` : never) : never
|
||||||
|
|
||||||
|
type Prev = [never, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, ...0[]]
|
||||||
|
|
||||||
|
type Column<T, D extends number = 2> = [D] extends [never]
|
||||||
|
? never
|
||||||
|
: T extends Record<string, any>
|
||||||
|
? {
|
||||||
|
[K in keyof T]-?: K extends string
|
||||||
|
? T[K] extends Date
|
||||||
|
? `${K}`
|
||||||
|
: T[K] extends Array<infer U>
|
||||||
|
? `${K}` | Join<K, Column<U, Prev[D]>>
|
||||||
|
: `${K}` | Join<K, Column<T[K], Prev[D]>>
|
||||||
|
: never
|
||||||
|
}[keyof T]
|
||||||
|
: ''
|
||||||
|
|
||||||
|
type RelationColumn<T> = Extract<
|
||||||
|
Column<T>,
|
||||||
|
{
|
||||||
|
[K in Column<T>]: K extends `${infer R}.${string}` ? R : never
|
||||||
|
}[Column<T>]
|
||||||
|
>
|
||||||
|
|
||||||
type Column<T> = Extract<keyof T, string>
|
|
||||||
type Order<T> = [Column<T>, 'ASC' | 'DESC']
|
type Order<T> = [Column<T>, 'ASC' | 'DESC']
|
||||||
type SortBy<T> = Order<T>[]
|
type SortBy<T> = Order<T>[]
|
||||||
|
|
||||||
@ -47,6 +71,7 @@ export class Paginated<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface PaginateConfig<T> {
|
export interface PaginateConfig<T> {
|
||||||
|
relations?: RelationColumn<T>[]
|
||||||
sortableColumns: Column<T>[]
|
sortableColumns: Column<T>[]
|
||||||
searchableColumns?: Column<T>[]
|
searchableColumns?: Column<T>[]
|
||||||
maxLimit?: number
|
maxLimit?: number
|
||||||
@ -207,15 +232,21 @@ export async function paginate<T>(
|
|||||||
.createQueryBuilder('e')
|
.createQueryBuilder('e')
|
||||||
.take(limit)
|
.take(limit)
|
||||||
.skip((page - 1) * limit)
|
.skip((page - 1) * limit)
|
||||||
|
|
||||||
for (const order of sortBy) {
|
|
||||||
queryBuilder.addOrderBy('e.' + order[0], order[1])
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
queryBuilder = repo.take(limit).skip((page - 1) * limit)
|
queryBuilder = repo.take(limit).skip((page - 1) * limit)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.relations?.length) {
|
||||||
|
config.relations.forEach((relation) => {
|
||||||
|
queryBuilder.leftJoinAndSelect(`${queryBuilder.alias}.${relation}`, `${queryBuilder.alias}_${relation}`)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
for (const order of sortBy) {
|
for (const order of sortBy) {
|
||||||
queryBuilder.addOrderBy(repo.alias + '.' + order[0], order[1])
|
if (order[0].split('.').length > 1) {
|
||||||
|
queryBuilder.addOrderBy(`${queryBuilder.alias}_${order[0]}`, order[1])
|
||||||
|
} else {
|
||||||
|
queryBuilder.addOrderBy(`${queryBuilder.alias}.${order[0]}`, order[1])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -224,16 +255,51 @@ export async function paginate<T>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (query.search && searchBy.length) {
|
if (query.search && searchBy.length) {
|
||||||
const search: ObjectLiteral[] = []
|
queryBuilder.andWhere(
|
||||||
|
new Brackets((qb: SelectQueryBuilder<T>) => {
|
||||||
for (const column of searchBy) {
|
for (const column of searchBy) {
|
||||||
search.push({ [column]: ILike(`%${query.search}%`) })
|
const propertyPath = (column as string).split('.')
|
||||||
|
if (propertyPath.length > 1) {
|
||||||
|
const condition: WherePredicateOperator = {
|
||||||
|
operator: 'ilike',
|
||||||
|
parameters: [`${qb.alias}_${column}`, `:${column}`],
|
||||||
}
|
}
|
||||||
queryBuilder.andWhere(new Brackets((qb) => qb.andWhere(search)))
|
qb.orWhere(qb['createWhereConditionExpression'](condition), {
|
||||||
|
[column]: `%${query.search}%`,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
qb.orWhere({
|
||||||
|
[column]: ILike(`%${query.search}%`),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (query.filter) {
|
if (query.filter) {
|
||||||
const filter = parseFilter(query, config)
|
const filter = parseFilter(query, config)
|
||||||
queryBuilder.andWhere(new Brackets((qb) => qb.andWhere(filter)))
|
queryBuilder.andWhere(
|
||||||
|
new Brackets((qb: SelectQueryBuilder<T>) => {
|
||||||
|
for (const column in filter) {
|
||||||
|
const propertyPath = (column as string).split('.')
|
||||||
|
if (propertyPath.length > 1) {
|
||||||
|
const condition = qb['getWherePredicateCondition'](
|
||||||
|
column,
|
||||||
|
filter[column]
|
||||||
|
) as WherePredicateOperator
|
||||||
|
condition.parameters = [`${qb.alias}_${column}`, `:${column}`]
|
||||||
|
qb.andWhere(qb['createWhereConditionExpression'](condition), {
|
||||||
|
[column]: filter[column].value,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
qb.andWhere({
|
||||||
|
[column]: filter[column],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
;[items, totalItems] = await queryBuilder.getManyAndCount()
|
;[items, totalItems] = await queryBuilder.getManyAndCount()
|
||||||
|
@ -2,7 +2,7 @@ import { ROUTE_ARGS_METADATA } from '@nestjs/common/constants'
|
|||||||
import { HttpArgumentsHost, CustomParamFactory, ExecutionContext } from '@nestjs/common/interfaces'
|
import { HttpArgumentsHost, CustomParamFactory, ExecutionContext } from '@nestjs/common/interfaces'
|
||||||
import { Request as ExpressRequest } from 'express'
|
import { Request as ExpressRequest } from 'express'
|
||||||
import { FastifyRequest } from 'fastify'
|
import { FastifyRequest } from 'fastify'
|
||||||
import { Paginate, PaginateQuery } from './decorator'
|
import { Paginate, PaginateQuery } from '../index'
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||||
function getParamDecoratorFactory<T>(decorator: Function): CustomParamFactory {
|
function getParamDecoratorFactory<T>(decorator: Function): CustomParamFactory {
|
17
src/test/entity/cat-home.entity.ts
Normal file
17
src/test/entity/cat-home.entity.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import { Column, CreateDateColumn, Entity, OneToOne, PrimaryGeneratedColumn } from 'typeorm'
|
||||||
|
import { CatEntity } from './cat.entity'
|
||||||
|
|
||||||
|
@Entity()
|
||||||
|
export class CatHomeEntity {
|
||||||
|
@PrimaryGeneratedColumn()
|
||||||
|
id: number
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
name: string
|
||||||
|
|
||||||
|
@OneToOne(() => CatEntity, (cat) => cat.home)
|
||||||
|
cat: CatEntity
|
||||||
|
|
||||||
|
@CreateDateColumn()
|
||||||
|
createdAt: string
|
||||||
|
}
|
18
src/test/entity/cat-toy.entity.ts
Normal file
18
src/test/entity/cat-toy.entity.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import { Column, CreateDateColumn, Entity, JoinColumn, ManyToOne, PrimaryGeneratedColumn } from 'typeorm'
|
||||||
|
import { CatEntity } from './cat.entity'
|
||||||
|
|
||||||
|
@Entity()
|
||||||
|
export class CatToyEntity {
|
||||||
|
@PrimaryGeneratedColumn()
|
||||||
|
id: number
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
name: string
|
||||||
|
|
||||||
|
@ManyToOne(() => CatEntity, (cat) => cat.toys)
|
||||||
|
@JoinColumn()
|
||||||
|
cat: CatEntity
|
||||||
|
|
||||||
|
@CreateDateColumn()
|
||||||
|
createdAt: string
|
||||||
|
}
|
28
src/test/entity/cat.entity.ts
Normal file
28
src/test/entity/cat.entity.ts
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import { Column, CreateDateColumn, Entity, JoinColumn, OneToMany, OneToOne, PrimaryGeneratedColumn } from 'typeorm'
|
||||||
|
import { CatToyEntity } from './cat-toy.entity'
|
||||||
|
import { CatHomeEntity } from './cat-home.entity'
|
||||||
|
|
||||||
|
@Entity()
|
||||||
|
export class CatEntity {
|
||||||
|
@PrimaryGeneratedColumn()
|
||||||
|
id: number
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
name: string
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
color: string
|
||||||
|
|
||||||
|
@Column({ nullable: true })
|
||||||
|
age: number | null
|
||||||
|
|
||||||
|
@OneToMany(() => CatToyEntity, (catToy) => catToy.cat)
|
||||||
|
toys: CatToyEntity[]
|
||||||
|
|
||||||
|
@OneToOne(() => CatHomeEntity, (catHome) => catHome.cat, { nullable: true })
|
||||||
|
@JoinColumn()
|
||||||
|
home: CatHomeEntity
|
||||||
|
|
||||||
|
@CreateDateColumn()
|
||||||
|
createdAt: string
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
import { createConnection, Repository, Column, In, Connection } from 'typeorm'
|
import { createConnection, Repository, In, Connection } from 'typeorm'
|
||||||
import {
|
import {
|
||||||
Paginated,
|
Paginated,
|
||||||
paginate,
|
paginate,
|
||||||
@ -7,33 +7,22 @@ import {
|
|||||||
isOperator,
|
isOperator,
|
||||||
getFilterTokens,
|
getFilterTokens,
|
||||||
OperatorSymbolToFunction,
|
OperatorSymbolToFunction,
|
||||||
} from './paginate'
|
} from '../index'
|
||||||
import { PaginateQuery } from './decorator'
|
import { PaginateQuery } from '../index'
|
||||||
import { Entity, PrimaryGeneratedColumn, CreateDateColumn } from 'typeorm'
|
|
||||||
import { HttpException } from '@nestjs/common'
|
import { HttpException } from '@nestjs/common'
|
||||||
|
import { CatEntity } from './entity/cat.entity'
|
||||||
@Entity()
|
import { CatToyEntity } from './entity/cat-toy.entity'
|
||||||
export class CatEntity {
|
import { CatHomeEntity } from './entity/cat-home.entity'
|
||||||
@PrimaryGeneratedColumn()
|
import { clone } from 'lodash'
|
||||||
id: number
|
|
||||||
|
|
||||||
@Column()
|
|
||||||
name: string
|
|
||||||
|
|
||||||
@Column()
|
|
||||||
color: string
|
|
||||||
|
|
||||||
@Column({ nullable: true })
|
|
||||||
age: number | null
|
|
||||||
|
|
||||||
@CreateDateColumn()
|
|
||||||
createdAt: string
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('paginate', () => {
|
describe('paginate', () => {
|
||||||
let connection: Connection
|
let connection: Connection
|
||||||
let repo: Repository<CatEntity>
|
let catRepo: Repository<CatEntity>
|
||||||
|
let catToyRepo: Repository<CatToyEntity>
|
||||||
|
let catHomeRepo: Repository<CatHomeEntity>
|
||||||
let cats: CatEntity[]
|
let cats: CatEntity[]
|
||||||
|
let catToys: CatToyEntity[]
|
||||||
|
let catHomes: CatHomeEntity[]
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
connection = await createConnection({
|
connection = await createConnection({
|
||||||
@ -41,15 +30,27 @@ describe('paginate', () => {
|
|||||||
database: ':memory:',
|
database: ':memory:',
|
||||||
synchronize: true,
|
synchronize: true,
|
||||||
logging: false,
|
logging: false,
|
||||||
entities: [CatEntity],
|
entities: [CatEntity, CatToyEntity, CatHomeEntity],
|
||||||
})
|
})
|
||||||
repo = connection.getRepository(CatEntity)
|
catRepo = connection.getRepository(CatEntity)
|
||||||
cats = await repo.save([
|
catToyRepo = connection.getRepository(CatToyEntity)
|
||||||
repo.create({ name: 'Milo', color: 'brown', age: 6 }),
|
catHomeRepo = connection.getRepository(CatHomeEntity)
|
||||||
repo.create({ name: 'Garfield', color: 'ginger', age: 5 }),
|
cats = await catRepo.save([
|
||||||
repo.create({ name: 'Shadow', color: 'black', age: 4 }),
|
catRepo.create({ name: 'Milo', color: 'brown', age: 6 }),
|
||||||
repo.create({ name: 'George', color: 'white', age: 3 }),
|
catRepo.create({ name: 'Garfield', color: 'ginger', age: 5 }),
|
||||||
repo.create({ name: 'Leche', color: 'white', age: null }),
|
catRepo.create({ name: 'Shadow', color: 'black', age: 4 }),
|
||||||
|
catRepo.create({ name: 'George', color: 'white', age: 3 }),
|
||||||
|
catRepo.create({ name: 'Leche', color: 'white', age: null }),
|
||||||
|
])
|
||||||
|
catToys = await catToyRepo.save([
|
||||||
|
catToyRepo.create({ name: 'Fuzzy Thing', cat: cats[0] }),
|
||||||
|
catToyRepo.create({ name: 'Stuffed Mouse', cat: cats[0] }),
|
||||||
|
catToyRepo.create({ name: 'Mouse', cat: cats[0] }),
|
||||||
|
catToyRepo.create({ name: 'String', cat: cats[1] }),
|
||||||
|
])
|
||||||
|
catHomes = await catHomeRepo.save([
|
||||||
|
catHomeRepo.create({ name: 'Box', cat: cats[0] }),
|
||||||
|
catHomeRepo.create({ name: 'House', cat: cats[1] }),
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -63,7 +64,7 @@ describe('paginate', () => {
|
|||||||
path: '',
|
path: '',
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await paginate<CatEntity>(query, repo, config)
|
const result = await paginate<CatEntity>(query, catRepo, config)
|
||||||
|
|
||||||
expect(result).toBeInstanceOf(Paginated)
|
expect(result).toBeInstanceOf(Paginated)
|
||||||
expect(result.data).toStrictEqual(cats.slice(0, 1))
|
expect(result.data).toStrictEqual(cats.slice(0, 1))
|
||||||
@ -79,7 +80,7 @@ describe('paginate', () => {
|
|||||||
path: '',
|
path: '',
|
||||||
}
|
}
|
||||||
|
|
||||||
const queryBuilder = await repo.createQueryBuilder('cats')
|
const queryBuilder = await catRepo.createQueryBuilder('cats')
|
||||||
|
|
||||||
const result = await paginate<CatEntity>(query, queryBuilder, config)
|
const result = await paginate<CatEntity>(query, queryBuilder, config)
|
||||||
|
|
||||||
@ -117,7 +118,7 @@ describe('paginate', () => {
|
|||||||
page: -1,
|
page: -1,
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await paginate<CatEntity>(query, repo, config)
|
const result = await paginate<CatEntity>(query, catRepo, config)
|
||||||
|
|
||||||
expect(result.meta.currentPage).toBe(1)
|
expect(result.meta.currentPage).toBe(1)
|
||||||
expect(result.data).toStrictEqual(cats.slice(0, 1))
|
expect(result.data).toStrictEqual(cats.slice(0, 1))
|
||||||
@ -135,7 +136,7 @@ describe('paginate', () => {
|
|||||||
limit: 20,
|
limit: 20,
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await paginate<CatEntity>(query, repo, config)
|
const result = await paginate<CatEntity>(query, catRepo, config)
|
||||||
|
|
||||||
expect(result.data).toStrictEqual(cats.slice(0, 2))
|
expect(result.data).toStrictEqual(cats.slice(0, 2))
|
||||||
})
|
})
|
||||||
@ -150,7 +151,7 @@ describe('paginate', () => {
|
|||||||
limit: 2,
|
limit: 2,
|
||||||
}
|
}
|
||||||
|
|
||||||
const { links } = await paginate<CatEntity>(query, repo, config)
|
const { links } = await paginate<CatEntity>(query, catRepo, config)
|
||||||
|
|
||||||
expect(links.first).toBe('?page=1&limit=2&sortBy=id:ASC')
|
expect(links.first).toBe('?page=1&limit=2&sortBy=id:ASC')
|
||||||
expect(links.previous).toBe('?page=1&limit=2&sortBy=id:ASC')
|
expect(links.previous).toBe('?page=1&limit=2&sortBy=id:ASC')
|
||||||
@ -171,7 +172,7 @@ describe('paginate', () => {
|
|||||||
search: 'Pluto',
|
search: 'Pluto',
|
||||||
}
|
}
|
||||||
|
|
||||||
const { links } = await paginate<CatEntity>(query, repo, config)
|
const { links } = await paginate<CatEntity>(query, catRepo, config)
|
||||||
|
|
||||||
expect(links.first).toBe(undefined)
|
expect(links.first).toBe(undefined)
|
||||||
expect(links.previous).toBe(undefined)
|
expect(links.previous).toBe(undefined)
|
||||||
@ -189,7 +190,7 @@ describe('paginate', () => {
|
|||||||
path: '',
|
path: '',
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await paginate<CatEntity>(query, repo, config)
|
const result = await paginate<CatEntity>(query, catRepo, config)
|
||||||
|
|
||||||
expect(result.meta.sortBy).toStrictEqual([['id', 'DESC']])
|
expect(result.meta.sortBy).toStrictEqual([['id', 'DESC']])
|
||||||
expect(result.data).toStrictEqual(cats.slice(0).reverse())
|
expect(result.data).toStrictEqual(cats.slice(0).reverse())
|
||||||
@ -207,7 +208,7 @@ describe('paginate', () => {
|
|||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await paginate<CatEntity>(query, repo, config)
|
const result = await paginate<CatEntity>(query, catRepo, config)
|
||||||
|
|
||||||
expect(result.meta.sortBy).toStrictEqual([
|
expect(result.meta.sortBy).toStrictEqual([
|
||||||
['color', 'DESC'],
|
['color', 'DESC'],
|
||||||
@ -226,13 +227,129 @@ describe('paginate', () => {
|
|||||||
search: 'i',
|
search: 'i',
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await paginate<CatEntity>(query, repo, config)
|
const result = await paginate<CatEntity>(query, catRepo, config)
|
||||||
|
|
||||||
expect(result.meta.search).toStrictEqual('i')
|
expect(result.meta.search).toStrictEqual('i')
|
||||||
expect(result.data).toStrictEqual([cats[0], cats[1], cats[3], cats[4]])
|
expect(result.data).toStrictEqual([cats[0], cats[1], cats[3], cats[4]])
|
||||||
expect(result.links.current).toBe('?page=1&limit=20&sortBy=id:ASC&search=i')
|
expect(result.links.current).toBe('?page=1&limit=20&sortBy=id:ASC&search=i')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should return result based on search term on many-to-one relation', async () => {
|
||||||
|
const config: PaginateConfig<CatToyEntity> = {
|
||||||
|
relations: ['cat'],
|
||||||
|
sortableColumns: ['id', 'name'],
|
||||||
|
searchableColumns: ['name', 'cat.name'],
|
||||||
|
}
|
||||||
|
const query: PaginateQuery = {
|
||||||
|
path: '',
|
||||||
|
search: 'Milo',
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await paginate<CatToyEntity>(query, catToyRepo, config)
|
||||||
|
|
||||||
|
expect(result.meta.search).toStrictEqual('Milo')
|
||||||
|
expect(result.data).toStrictEqual([catToys[0], catToys[1], catToys[2]])
|
||||||
|
expect(result.links.current).toBe('?page=1&limit=20&sortBy=id:ASC&search=Milo')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should return result based on search term on one-to-many relation', async () => {
|
||||||
|
const config: PaginateConfig<CatEntity> = {
|
||||||
|
relations: ['toys'],
|
||||||
|
sortableColumns: ['id', 'name'],
|
||||||
|
searchableColumns: ['name', 'toys.name'],
|
||||||
|
}
|
||||||
|
const query: PaginateQuery = {
|
||||||
|
path: '',
|
||||||
|
search: 'Mouse',
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await paginate<CatEntity>(query, catRepo, config)
|
||||||
|
|
||||||
|
expect(result.meta.search).toStrictEqual('Mouse')
|
||||||
|
const toy = clone(catToys[1])
|
||||||
|
delete toy.cat
|
||||||
|
const toy2 = clone(catToys[2])
|
||||||
|
delete toy2.cat
|
||||||
|
expect(result.data).toStrictEqual([Object.assign(clone(cats[0]), { toys: [toy, toy2] })])
|
||||||
|
expect(result.links.current).toBe('?page=1&limit=20&sortBy=id:ASC&search=Mouse')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should return result based on search term on one-to-one relation', async () => {
|
||||||
|
const config: PaginateConfig<CatHomeEntity> = {
|
||||||
|
relations: ['cat'],
|
||||||
|
sortableColumns: ['id', 'name', 'cat.id'],
|
||||||
|
}
|
||||||
|
const query: PaginateQuery = {
|
||||||
|
path: '',
|
||||||
|
sortBy: [['cat.id', 'DESC']],
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await paginate<CatHomeEntity>(query, catHomeRepo, config)
|
||||||
|
expect(result.meta.sortBy).toStrictEqual([['cat.id', 'DESC']])
|
||||||
|
expect(result.data).toStrictEqual([catHomes[0], catHomes[1]].sort((a, b) => b.cat.id - a.cat.id))
|
||||||
|
expect(result.links.current).toBe('?page=1&limit=20&sortBy=cat.id:DESC')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should return result based on sort and search on many-to-one relation', async () => {
|
||||||
|
const config: PaginateConfig<CatToyEntity> = {
|
||||||
|
relations: ['cat'],
|
||||||
|
sortableColumns: ['id', 'name', 'cat.id'],
|
||||||
|
searchableColumns: ['name', 'cat.name'],
|
||||||
|
}
|
||||||
|
const query: PaginateQuery = {
|
||||||
|
path: '',
|
||||||
|
sortBy: [['cat.id', 'DESC']],
|
||||||
|
search: 'Milo',
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await paginate<CatToyEntity>(query, catToyRepo, config)
|
||||||
|
|
||||||
|
expect(result.meta.search).toStrictEqual('Milo')
|
||||||
|
expect(result.data).toStrictEqual([catToys[0], catToys[1], catToys[2]].sort((a, b) => b.cat.id - a.cat.id))
|
||||||
|
expect(result.links.current).toBe('?page=1&limit=20&sortBy=cat.id:DESC&search=Milo')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should return result based on sort on one-to-many relation', async () => {
|
||||||
|
const config: PaginateConfig<CatEntity> = {
|
||||||
|
relations: ['toys'],
|
||||||
|
sortableColumns: ['id', 'name', 'toys.id'],
|
||||||
|
searchableColumns: ['name', 'toys.name'],
|
||||||
|
}
|
||||||
|
const query: PaginateQuery = {
|
||||||
|
path: '',
|
||||||
|
sortBy: [['toys.id', 'DESC']],
|
||||||
|
search: 'Mouse',
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await paginate<CatEntity>(query, catRepo, config)
|
||||||
|
|
||||||
|
expect(result.meta.search).toStrictEqual('Mouse')
|
||||||
|
const toy1 = clone(catToys[1])
|
||||||
|
delete toy1.cat
|
||||||
|
const toy2 = clone(catToys[2])
|
||||||
|
delete toy2.cat
|
||||||
|
expect(result.data).toStrictEqual([Object.assign(clone(cats[0]), { toys: [toy2, toy1] })])
|
||||||
|
expect(result.links.current).toBe('?page=1&limit=20&sortBy=toys.id:DESC&search=Mouse')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should return result based on sort on one-to-one relation', async () => {
|
||||||
|
const config: PaginateConfig<CatHomeEntity> = {
|
||||||
|
relations: ['cat'],
|
||||||
|
sortableColumns: ['id', 'name'],
|
||||||
|
searchableColumns: ['name', 'cat.name'],
|
||||||
|
}
|
||||||
|
const query: PaginateQuery = {
|
||||||
|
path: '',
|
||||||
|
search: 'Garfield',
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await paginate<CatHomeEntity>(query, catHomeRepo, config)
|
||||||
|
|
||||||
|
expect(result.meta.search).toStrictEqual('Garfield')
|
||||||
|
expect(result.data).toStrictEqual([catHomes[1]])
|
||||||
|
expect(result.links.current).toBe('?page=1&limit=20&sortBy=id:ASC&search=Garfield')
|
||||||
|
})
|
||||||
|
|
||||||
it('should return result based on search term and searchBy columns', async () => {
|
it('should return result based on search term and searchBy columns', async () => {
|
||||||
const config: PaginateConfig<CatEntity> = {
|
const config: PaginateConfig<CatEntity> = {
|
||||||
sortableColumns: ['id', 'name', 'color'],
|
sortableColumns: ['id', 'name', 'color'],
|
||||||
@ -248,7 +365,7 @@ describe('paginate', () => {
|
|||||||
searchBy: ['color'],
|
searchBy: ['color'],
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await paginate<CatEntity>(query, repo, config)
|
const result = await paginate<CatEntity>(query, catRepo, config)
|
||||||
|
|
||||||
expect(result.meta.search).toStrictEqual(searchTerm)
|
expect(result.meta.search).toStrictEqual(searchTerm)
|
||||||
expect(result.meta.searchBy).toStrictEqual(['color'])
|
expect(result.meta.searchBy).toStrictEqual(['color'])
|
||||||
@ -273,7 +390,7 @@ describe('paginate', () => {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await paginate<CatEntity>(query, repo, config)
|
const result = await paginate<CatEntity>(query, catRepo, config)
|
||||||
|
|
||||||
expect(result.meta.filter).toStrictEqual({
|
expect(result.meta.filter).toStrictEqual({
|
||||||
name: '$not:Leche',
|
name: '$not:Leche',
|
||||||
@ -282,6 +399,89 @@ describe('paginate', () => {
|
|||||||
expect(result.links.current).toBe('?page=1&limit=20&sortBy=id:ASC&filter.name=$not:Leche')
|
expect(result.links.current).toBe('?page=1&limit=20&sortBy=id:ASC&filter.name=$not:Leche')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should return result based on filter on many-to-one relation', async () => {
|
||||||
|
const config: PaginateConfig<CatToyEntity> = {
|
||||||
|
relations: ['cat'],
|
||||||
|
sortableColumns: ['id', 'name'],
|
||||||
|
filterableColumns: {
|
||||||
|
'cat.name': [FilterOperator.NOT],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
const query: PaginateQuery = {
|
||||||
|
path: '',
|
||||||
|
filter: {
|
||||||
|
'cat.name': '$not:Milo',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await paginate<CatToyEntity>(query, catToyRepo, config)
|
||||||
|
|
||||||
|
expect(result.meta.filter).toStrictEqual({
|
||||||
|
'cat.name': '$not:Milo',
|
||||||
|
})
|
||||||
|
expect(result.data).toStrictEqual([catToys[3]])
|
||||||
|
expect(result.links.current).toBe('?page=1&limit=20&sortBy=id:ASC&filter.cat.name=$not:Milo')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should return result based on filter on one-to-many relation', async () => {
|
||||||
|
const config: PaginateConfig<CatEntity> = {
|
||||||
|
relations: ['toys'],
|
||||||
|
sortableColumns: ['id', 'name'],
|
||||||
|
filterableColumns: {
|
||||||
|
'toys.name': [FilterOperator.NOT],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
const query: PaginateQuery = {
|
||||||
|
path: '',
|
||||||
|
filter: {
|
||||||
|
'toys.name': '$not:Stuffed Mouse',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await paginate<CatEntity>(query, catRepo, config)
|
||||||
|
|
||||||
|
const cat1 = clone(cats[0])
|
||||||
|
const cat2 = clone(cats[1])
|
||||||
|
const catToys1 = clone(catToys[0])
|
||||||
|
const catToys2 = clone(catToys[2])
|
||||||
|
const catToys3 = clone(catToys[3])
|
||||||
|
delete catToys1.cat
|
||||||
|
delete catToys2.cat
|
||||||
|
delete catToys3.cat
|
||||||
|
cat1.toys = [catToys1, catToys2]
|
||||||
|
cat2.toys = [catToys3]
|
||||||
|
|
||||||
|
expect(result.meta.filter).toStrictEqual({
|
||||||
|
'toys.name': '$not:Stuffed Mouse',
|
||||||
|
})
|
||||||
|
expect(result.data).toStrictEqual([cat1, cat2])
|
||||||
|
expect(result.links.current).toBe('?page=1&limit=20&sortBy=id:ASC&filter.toys.name=$not:Stuffed Mouse')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should return result based on where config and filter on one-to-one relation', async () => {
|
||||||
|
const config: PaginateConfig<CatHomeEntity> = {
|
||||||
|
relations: ['cat'],
|
||||||
|
sortableColumns: ['id', 'name'],
|
||||||
|
filterableColumns: {
|
||||||
|
'cat.name': [FilterOperator.NOT],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
const query: PaginateQuery = {
|
||||||
|
path: '',
|
||||||
|
filter: {
|
||||||
|
'cat.name': '$not:Garfield',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await paginate<CatHomeEntity>(query, catHomeRepo, config)
|
||||||
|
|
||||||
|
expect(result.meta.filter).toStrictEqual({
|
||||||
|
'cat.name': '$not:Garfield',
|
||||||
|
})
|
||||||
|
expect(result.data).toStrictEqual([catHomes[0]])
|
||||||
|
expect(result.links.current).toBe('?page=1&limit=20&sortBy=id:ASC&filter.cat.name=$not:Garfield')
|
||||||
|
})
|
||||||
|
|
||||||
it('should return result based on where array and filter', async () => {
|
it('should return result based on where array and filter', async () => {
|
||||||
const config: PaginateConfig<CatEntity> = {
|
const config: PaginateConfig<CatEntity> = {
|
||||||
sortableColumns: ['id'],
|
sortableColumns: ['id'],
|
||||||
@ -304,7 +504,7 @@ describe('paginate', () => {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await paginate<CatEntity>(query, repo, config)
|
const result = await paginate<CatEntity>(query, catRepo, config)
|
||||||
|
|
||||||
expect(result.meta.filter).toStrictEqual({
|
expect(result.meta.filter).toStrictEqual({
|
||||||
name: '$not:Leche',
|
name: '$not:Leche',
|
||||||
@ -329,7 +529,7 @@ describe('paginate', () => {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await paginate<CatEntity>(query, repo, config)
|
const result = await paginate<CatEntity>(query, catRepo, config)
|
||||||
|
|
||||||
expect(result.meta.filter).toStrictEqual({
|
expect(result.meta.filter).toStrictEqual({
|
||||||
name: '$not:Leche',
|
name: '$not:Leche',
|
||||||
@ -355,7 +555,7 @@ describe('paginate', () => {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await paginate<CatEntity>(query, repo, config)
|
const result = await paginate<CatEntity>(query, catRepo, config)
|
||||||
|
|
||||||
expect(result.meta.search).toStrictEqual('white')
|
expect(result.meta.search).toStrictEqual('white')
|
||||||
expect(result.meta.filter).toStrictEqual({ id: '$not:$in:1,2,5' })
|
expect(result.meta.filter).toStrictEqual({ id: '$not:$in:1,2,5' })
|
||||||
@ -380,7 +580,7 @@ describe('paginate', () => {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await paginate<CatEntity>(query, repo, config)
|
const result = await paginate<CatEntity>(query, catRepo, config)
|
||||||
|
|
||||||
expect(result.data).toStrictEqual([cats[2], cats[3]])
|
expect(result.data).toStrictEqual([cats[2], cats[3]])
|
||||||
expect(result.links.current).toBe('?page=1&limit=20&sortBy=id:ASC&filter.id=$not:$in:1,2,5')
|
expect(result.links.current).toBe('?page=1&limit=20&sortBy=id:ASC&filter.id=$not:$in:1,2,5')
|
||||||
@ -400,7 +600,7 @@ describe('paginate', () => {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await paginate<CatEntity>(query, repo, config)
|
const result = await paginate<CatEntity>(query, catRepo, config)
|
||||||
|
|
||||||
expect(result.data).toStrictEqual([cats[0], cats[1], cats[2]])
|
expect(result.data).toStrictEqual([cats[0], cats[1], cats[2]])
|
||||||
expect(result.links.current).toBe('?page=1&limit=20&sortBy=id:ASC&filter.age=$gte:4')
|
expect(result.links.current).toBe('?page=1&limit=20&sortBy=id:ASC&filter.age=$gte:4')
|
||||||
@ -420,7 +620,7 @@ describe('paginate', () => {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await paginate<CatEntity>(query, repo, config)
|
const result = await paginate<CatEntity>(query, catRepo, config)
|
||||||
|
|
||||||
expect(result.data).toStrictEqual([cats[1], cats[2]])
|
expect(result.data).toStrictEqual([cats[1], cats[2]])
|
||||||
expect(result.links.current).toBe('?page=1&limit=20&sortBy=id:ASC&filter.age=$btw:4,5')
|
expect(result.links.current).toBe('?page=1&limit=20&sortBy=id:ASC&filter.age=$btw:4,5')
|
||||||
@ -440,7 +640,7 @@ describe('paginate', () => {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await paginate<CatEntity>(query, repo, config)
|
const result = await paginate<CatEntity>(query, catRepo, config)
|
||||||
|
|
||||||
expect(result.data).toStrictEqual([cats[4]])
|
expect(result.data).toStrictEqual([cats[4]])
|
||||||
expect(result.links.current).toBe('?page=1&limit=20&sortBy=id:ASC&filter.age=$null')
|
expect(result.links.current).toBe('?page=1&limit=20&sortBy=id:ASC&filter.age=$null')
|
||||||
@ -460,7 +660,7 @@ describe('paginate', () => {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await paginate<CatEntity>(query, repo, config)
|
const result = await paginate<CatEntity>(query, catRepo, config)
|
||||||
|
|
||||||
expect(result.data).toStrictEqual([cats[0], cats[1], cats[2], cats[3]])
|
expect(result.data).toStrictEqual([cats[0], cats[1], cats[2], cats[3]])
|
||||||
expect(result.links.current).toBe('?page=1&limit=20&sortBy=id:ASC&filter.age=$not:$null')
|
expect(result.links.current).toBe('?page=1&limit=20&sortBy=id:ASC&filter.age=$not:$null')
|
||||||
@ -480,7 +680,7 @@ describe('paginate', () => {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await paginate<CatEntity>(query, repo, config)
|
const result = await paginate<CatEntity>(query, catRepo, config)
|
||||||
|
|
||||||
expect(result.data).toStrictEqual(cats)
|
expect(result.data).toStrictEqual(cats)
|
||||||
expect(result.links.current).toBe('?page=1&limit=20&sortBy=id:ASC&filter.age=$not:$null')
|
expect(result.links.current).toBe('?page=1&limit=20&sortBy=id:ASC&filter.age=$not:$null')
|
||||||
@ -500,7 +700,7 @@ describe('paginate', () => {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await paginate<CatEntity>(query, repo, config)
|
const result = await paginate<CatEntity>(query, catRepo, config)
|
||||||
|
|
||||||
expect(result.data).toStrictEqual(cats)
|
expect(result.data).toStrictEqual(cats)
|
||||||
expect(result.links.current).toBe('?page=1&limit=20&sortBy=id:ASC&filter.age=$not:$null')
|
expect(result.links.current).toBe('?page=1&limit=20&sortBy=id:ASC&filter.age=$not:$null')
|
||||||
@ -515,7 +715,7 @@ describe('paginate', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await paginate<CatEntity>(query, repo, config)
|
await paginate<CatEntity>(query, catRepo, config)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
expect(err).toBeInstanceOf(HttpException)
|
expect(err).toBeInstanceOf(HttpException)
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user