Skip to content

Commit 3d266bf

Browse files
committed
feat(query): pagination, where, include, order sequelize
1 parent e330c20 commit 3d266bf

File tree

6 files changed

+1809
-32
lines changed

6 files changed

+1809
-32
lines changed

src/controllers/User/controller.ts

+1-15
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
/* eslint-disable no-unused-vars */
2-
import { FilterQueryAttributes } from 'models'
32
import { Request, Response } from 'express'
43
import routes from 'routes/public'
54
import asyncHandler from 'helpers/asyncHandler'
@@ -14,20 +13,7 @@ routes.get(
1413
'/user',
1514
Authorization,
1615
asyncHandler(async function getAll(req: Request, res: Response) {
17-
const {
18-
page,
19-
pageSize,
20-
filtered,
21-
sorted,
22-
}: FilterQueryAttributes = req.getQuery()
23-
24-
const { data, total } = await UserService.getAll(
25-
page,
26-
pageSize,
27-
filtered,
28-
sorted
29-
)
30-
16+
const { data, total } = await UserService.getAll(req)
3117
return res.status(200).json({ data, total })
3218
})
3319
)

src/controllers/User/service.ts

+11-17
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,13 @@
22
/* eslint-disable no-param-reassign */
33
import models from 'models'
44
import db from 'models/_instance'
5-
import { filterQueryObject } from 'helpers/Common'
65
import ResponseError from 'modules/ResponseError'
76
import useValidation from 'helpers/useValidation'
87
import { UserAttributes } from 'models/user'
98
import UserRole from 'models/userrole'
109
import { Transaction } from 'sequelize/types'
1110
import UserRoleService from 'controllers/UserRole/service'
11+
import PluginSqlizeQuery from 'modules/SqlizeQuery/PluginSqlizeQuery'
1212
import schema from './schema'
1313

1414
const { Sequelize } = db
@@ -21,26 +21,20 @@ class UserService {
2121
/**
2222
* Get All User
2323
*/
24-
public static async getAll(
25-
page: string | number,
26-
pageSize: string | number,
27-
filtered: string,
28-
sorted: string
29-
) {
30-
if (!page) page = 0
31-
if (!pageSize) pageSize = 10
32-
33-
const filterObject = filtered ? filterQueryObject(JSON.parse(filtered)) : []
24+
public static async getAll(req: any) {
25+
const { includeCount, order, ...queryFind } = PluginSqlizeQuery.generate(
26+
req,
27+
User,
28+
including
29+
)
3430

3531
const data = await User.findAll({
36-
where: filterObject,
37-
include: including,
38-
offset: Number(pageSize) * Number(page),
39-
limit: Number(pageSize),
40-
order: [['createdAt', 'desc']],
32+
...queryFind,
33+
order: order.length ? order : [['createdAt', 'desc']],
4134
})
4235
const total = await User.count({
43-
where: filterObject,
36+
include: includeCount,
37+
where: queryFind.where,
4438
})
4539

4640
return { data, total }
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,284 @@
1+
import SqlizeQuery from 'modules/SqlizeQuery/index'
2+
import {
3+
getPrimitiveDataType,
4+
transfromIncludeToQueryable,
5+
} from 'modules/SqlizeQuery/SqlizeQuery'
6+
import { Op, ModelCtor, Includeable, IncludeOptions } from 'sequelize'
7+
import { cloneDeep, unset } from 'lodash'
8+
9+
const parserString = (value: any) => {
10+
return typeof value === 'string' ? JSON.parse(value) : value || []
11+
}
12+
13+
function getExactQueryIdModel(id: string, prefixName: any) {
14+
if (id === undefined) {
15+
return undefined
16+
}
17+
const splitId = id.split('.')
18+
if (!prefixName && splitId.length > 1) {
19+
return undefined
20+
}
21+
const indexId = splitId.findIndex((str) => str === prefixName)
22+
if (prefixName && indexId < 0) {
23+
return undefined
24+
}
25+
26+
const curId = prefixName
27+
? splitId
28+
.filter((str, index) => {
29+
return [indexId, indexId + 1].includes(index)
30+
})
31+
.pop()
32+
: id
33+
34+
if (!curId || (prefixName && splitId.indexOf(curId) !== splitId.length - 1)) {
35+
return undefined
36+
}
37+
38+
return curId
39+
}
40+
41+
function getFilteredQuery(model?: ModelCtor<any>, prefixName?: string) {
42+
const sequelizeQuery = new SqlizeQuery()
43+
sequelizeQuery.addValueParser(parserString)
44+
sequelizeQuery.addQueryBuilder(
45+
(filterData: { id: string; value: any }, queryHelper) => {
46+
const { id, value } = filterData || {}
47+
const curId = getExactQueryIdModel(id, prefixName)
48+
if (!curId) {
49+
return
50+
}
51+
52+
const type = typeof getPrimitiveDataType(
53+
model?.rawAttributes?.[curId]?.type
54+
)
55+
56+
if (type === 'number') {
57+
queryHelper.setQuery(
58+
curId,
59+
curId.endsWith('Id')
60+
? value
61+
: {
62+
[Op.like]: `%${value}%`,
63+
}
64+
)
65+
} else {
66+
queryHelper.setQuery(curId, {
67+
[Op.like]: `%${value}%`,
68+
})
69+
}
70+
}
71+
)
72+
return sequelizeQuery
73+
}
74+
75+
function getSortedQuery() {
76+
const sequelizeQuery = new SqlizeQuery()
77+
sequelizeQuery.addValueParser(parserString)
78+
sequelizeQuery.addQueryBuilder((value, queryHelper) => {
79+
if (value?.id) {
80+
queryHelper.setQuery(value.id, value.desc === true ? 'DESC' : 'ASC')
81+
}
82+
})
83+
sequelizeQuery.addTransformBuild((buildValue, transformHelper) => {
84+
transformHelper.setValue(
85+
Object.entries(buildValue).map(([id, value]) => {
86+
return [...id.split('.'), value]
87+
})
88+
)
89+
})
90+
return sequelizeQuery
91+
}
92+
93+
function getPaginationQuery() {
94+
const sequelizeQuery = new SqlizeQuery()
95+
const offsetId = 'page'
96+
const limitId = 'pageSize'
97+
const defaultOffset = 0
98+
const defaultLimit = 10
99+
sequelizeQuery.addValueParser((value) => {
100+
return [
101+
{
102+
id: offsetId,
103+
value: Number(value.page),
104+
},
105+
{
106+
id: limitId,
107+
value: Number(value.pageSize),
108+
},
109+
]
110+
})
111+
112+
sequelizeQuery.addQueryBuilder(({ id, value }, queryHelper) => {
113+
if (id === offsetId) {
114+
const offsetValue = queryHelper.getDataValueById(limitId) * (value - 1)
115+
queryHelper.setQuery(
116+
'offset',
117+
offsetValue > 0 ? offsetValue : defaultOffset
118+
)
119+
}
120+
if (id === limitId) {
121+
queryHelper.setQuery('limit', value || defaultLimit)
122+
}
123+
})
124+
125+
return sequelizeQuery
126+
}
127+
128+
function getIncludeFilteredQuery(
129+
filteredValue: any,
130+
model: any,
131+
prefixName: any,
132+
options?: IncludeOptions
133+
) {
134+
const where = getFilteredQuery(model, prefixName).build(filteredValue)
135+
136+
let extraProps = {}
137+
138+
if (Object.keys(where).length > 0) {
139+
extraProps = {
140+
...extraProps,
141+
where,
142+
required: true,
143+
}
144+
}
145+
146+
return {
147+
model,
148+
...extraProps,
149+
...options,
150+
}
151+
}
152+
153+
function filterIncludeHandledOnly({
154+
include,
155+
filteredInclude,
156+
}: {
157+
include: any
158+
filteredInclude?: any
159+
}) {
160+
const curFilteredInclude = filteredInclude || []
161+
if (include) {
162+
for (let i = 0; i < include.length; i += 1) {
163+
const curModel = include[i]
164+
let childIncludes = []
165+
if (curModel.include) {
166+
childIncludes = filterIncludeHandledOnly({
167+
include: curModel.include,
168+
})
169+
}
170+
171+
if (curModel.where || curModel.required || childIncludes.length > 0) {
172+
const clonedInclude = cloneDeep(curModel)
173+
unset(clonedInclude, 'include')
174+
if (childIncludes.length > 0) {
175+
clonedInclude.include = [...childIncludes]
176+
}
177+
curFilteredInclude.push(clonedInclude)
178+
}
179+
}
180+
}
181+
return curFilteredInclude
182+
}
183+
184+
function injectRequireInclude(include: Includeable[]) {
185+
function test(dataInclude: Includeable[]) {
186+
for (let i = 0; i < dataInclude.length; i += 1) {
187+
const optionInclude = dataInclude[i] as IncludeOptions
188+
let data
189+
if (optionInclude.include) {
190+
data = test(optionInclude.include)
191+
}
192+
193+
if (optionInclude.required) return true
194+
if (data && optionInclude.required === undefined) {
195+
optionInclude.required = true
196+
return true
197+
}
198+
}
199+
return false
200+
}
201+
202+
test(include)
203+
204+
return include
205+
}
206+
207+
function makeIncludeQueryable(filteredValue: any, includes: Includeable[]) {
208+
return transfromIncludeToQueryable(includes, (value) => {
209+
const { model, key, ...restValue } = value
210+
return getIncludeFilteredQuery(filteredValue, model, value.key, {
211+
key,
212+
...restValue,
213+
} as IncludeOptions)
214+
})
215+
}
216+
217+
interface OnBeforeBuildQuery {
218+
paginationQuery: SqlizeQuery
219+
filteredQuery: SqlizeQuery
220+
sortedQuery: SqlizeQuery
221+
}
222+
223+
interface GenerateOptions {
224+
onBeforeBuild: (query: OnBeforeBuildQuery) => void
225+
}
226+
227+
interface ReqGenerate {
228+
query:
229+
| {
230+
filtered?: { id: any; value: any }[]
231+
sorted?: { id: any; desc: boolean }[]
232+
page?: number
233+
pageSize?: number
234+
}
235+
| any
236+
}
237+
238+
function generate(
239+
req: ReqGenerate,
240+
model: any,
241+
includeRule?: Includeable | Includeable[],
242+
options?: GenerateOptions
243+
) {
244+
const { onBeforeBuild } = options || {}
245+
246+
const paginationQuery = getPaginationQuery()
247+
const filteredQuery = getFilteredQuery(model)
248+
const sortedQuery = getSortedQuery()
249+
const includeCountRule = filterIncludeHandledOnly({
250+
include: includeRule,
251+
})
252+
const include = injectRequireInclude(cloneDeep(includeRule) as Includeable[])
253+
const includeCount = injectRequireInclude(
254+
cloneDeep(includeCountRule) as Includeable[]
255+
)
256+
257+
if (onBeforeBuild) {
258+
onBeforeBuild({
259+
filteredQuery,
260+
paginationQuery,
261+
sortedQuery,
262+
})
263+
}
264+
265+
const pagination = paginationQuery.build(req.query)
266+
const filter = filteredQuery.build(req.query.filtered)
267+
const sort = sortedQuery.build(req.query.sorted)
268+
269+
return {
270+
include,
271+
includeCount,
272+
where: filter,
273+
order: sort as any,
274+
offset: pagination.offset,
275+
limit: pagination.limit,
276+
}
277+
}
278+
279+
const PluginSqlizeQuery = {
280+
generate,
281+
makeIncludeQueryable,
282+
}
283+
284+
export default PluginSqlizeQuery

0 commit comments

Comments
 (0)