Skip to content

Commit 187af91

Browse files
authored
Merge pull request #483 from OpenBeta/feature/limit-offset-pagination
feat: add limit/offset pagination to all array-returning graphql queries
2 parents 2ca1978 + 9fc4174 commit 187af91

18 files changed

Lines changed: 240 additions & 41 deletions

src/db/AreaTypes.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,4 +262,6 @@ export enum OperationType {
262262

263263
export interface BulkAreasGQLQueryInput {
264264
ancestors: string[]
265+
limit?: number
266+
offset?: number
265267
}

src/db/ChangeLogType.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,12 +71,18 @@ export interface GetHistoryInputFilterType {
7171
userUuid: string
7272
fromDate: Date
7373
toDate: Date
74+
limit?: number
75+
offset?: number
7476
}
7577

7678
export interface GetAreaHistoryInputFilterType {
7779
areaId: string
80+
limit?: number
81+
offset?: number
7882
}
7983

8084
export interface GetOrganizationHistoryInputFilterType {
8185
orgId: MUUID
86+
limit?: number
87+
offset?: number
8288
}

src/db/TickTypes.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,4 +111,13 @@ export interface TickEditFilterType {
111111
export interface TickUserSelectors {
112112
userId?: MUUID
113113
username?: string
114+
limit?: number
115+
offset?: number
116+
}
117+
118+
export interface TickByClimbSelectors {
119+
climbId: string
120+
userId?: string
121+
limit?: number
122+
offset?: number
114123
}

src/graphql/area/AreaQueries.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,12 @@ const AreaQueries = {
1515

1616
bulkAreas: async (_: any, params, { dataSources }: GQLContext): Promise<AreaType[]> => {
1717
const { areas } = dataSources
18-
const { ancestors } = params as BulkAreasGQLQueryInput
19-
return await areas.bulkDownloadAreas(ancestors)
18+
const { ancestors, limit, offset } = params as BulkAreasGQLQueryInput
19+
const DEFAULT_LIMIT = 500
20+
const MAX_LIMIT = 2000
21+
const safeLimit = Math.min(limit ?? DEFAULT_LIMIT, MAX_LIMIT)
22+
const safeOffset = offset ?? 0
23+
return await areas.bulkDownloadAreas(ancestors, safeLimit, safeOffset)
2024
}
2125
}
2226

src/graphql/history/HistoryQueries.ts

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,29 +7,38 @@ import {
77
} from '../../db/ChangeLogType.js'
88
import { GQLContext } from '../../types.js'
99

10+
const DEFAULT_LIMIT = 50
11+
const MAX_LIMIT = 500
12+
1013
const HistoryQueries = {
1114
getChangeHistory: async (_, { filter }, { dataSources }: GQLContext): Promise<any> => {
1215
const { history } = dataSources
13-
const { uuidList }: GetHistoryInputFilterType = filter ?? {}
16+
const { uuidList, limit, offset }: GetHistoryInputFilterType = filter ?? {}
1417
// Note: userUuid, fromDate, toDate filters don't currently work.
1518
// Note: though we pull uuidList, we don't use it either.
1619

1720
// Convert array of uuid in string to UUID[]
1821
const muidList = uuidList?.map(entry => muid.from(entry)) ?? []
19-
return await history.getChangeSets(muidList)
22+
const safeLimit = Math.min(limit ?? DEFAULT_LIMIT, MAX_LIMIT)
23+
const safeOffset = offset ?? 0
24+
return await history.getChangeSets(muidList, safeLimit, safeOffset)
2025
},
2126

2227
getAreaHistory: async (_, { filter }, { dataSources }: GQLContext): Promise<any> => {
2328
const { history } = dataSources
24-
const { areaId }: GetAreaHistoryInputFilterType = filter ?? {}
25-
const id = muid.from(areaId)
26-
return await history.getAreaChangeSets(id)
29+
const { areaId, limit, offset }: GetAreaHistoryInputFilterType = filter ?? {}
30+
const id = areaId != null ? muid.from(areaId) : undefined
31+
const safeLimit = Math.min(limit ?? DEFAULT_LIMIT, MAX_LIMIT)
32+
const safeOffset = offset ?? 0
33+
return await history.getAreaChangeSets(id, safeLimit, safeOffset)
2734
},
2835

2936
getOrganizationHistory: async (_, { filter }, { dataSources }: GQLContext): Promise<any> => {
3037
const { history } = dataSources
31-
const { orgId }: GetOrganizationHistoryInputFilterType = filter ?? {}
32-
return await history.getOrganizationChangeSets(orgId)
38+
const { orgId, limit, offset }: GetOrganizationHistoryInputFilterType = filter ?? {}
39+
const safeLimit = Math.min(limit ?? DEFAULT_LIMIT, MAX_LIMIT)
40+
const safeOffset = offset ?? 0
41+
return await history.getOrganizationChangeSets(orgId, safeLimit, safeOffset)
3342
}
3443
}
3544

src/graphql/organization/OrganizationQueries.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,17 @@ const OrganizationQueries = {
1515

1616
organizations: async (
1717
_,
18-
{ filter, sort, limit = 40 }: { filter?: OrganizationGQLFilter, sort?: Sort, limit?: number },
18+
{ filter, sort, limit = 40, offset = 0 }: { filter?: OrganizationGQLFilter, sort?: Sort, limit?: number, offset?: number },
1919
{ dataSources }: GQLContext
2020
) => {
2121
const { organizations }: { organizations: OrganizationDataSource } = dataSources
22+
const MAX_LIMIT = 500
23+
const safeLimit = Math.min(limit, MAX_LIMIT)
2224
const filtered = await organizations.findOrganizationsByFilter(filter)
2325
if (sort != null) {
24-
return await filtered.collation({ locale: 'en' }).sort(sort).limit(limit).toArray()
26+
return await filtered.collation({ locale: 'en' }).sort(sort).skip(offset).limit(safeLimit).toArray()
2527
} else {
26-
return await filtered.limit(limit).toArray()
28+
return await filtered.skip(offset).limit(safeLimit).toArray()
2729
}
2830
}
2931
}

src/graphql/resolvers.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,12 +75,16 @@ const resolvers = {
7575

7676
areas: async (
7777
_,
78-
{ filter, sort }: { filter?: GQLFilter, sort?: Sort },
78+
{ filter, sort, limit, offset }: { filter?: GQLFilter, sort?: Sort, limit?: number, offset?: number },
7979
{ dataSources }: GQLContext
8080
) => {
8181
const { areas } = dataSources
82+
const DEFAULT_LIMIT = 50
83+
const MAX_LIMIT = 500
84+
const safeLimit = Math.min(limit ?? DEFAULT_LIMIT, MAX_LIMIT)
85+
const safeOffset = offset ?? 0
8286
const filtered = await areas.findAreasByFilter(filter)
83-
return filtered.collation({ locale: 'en' }).sort(sort).toArray()
87+
return filtered.collation({ locale: 'en' }).sort(sort).skip(safeOffset).limit(safeLimit).toArray()
8488
},
8589

8690
area: async (_: any,

src/graphql/schema/Area.gql

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
type Query {
22
area(uuid: ID): Area
3-
areas(filter: Filter, sort: Sort): [Area]
3+
areas(filter: Filter, sort: Sort, limit: Int, offset: Int): [Area]
44

55
"""
66
Bulk download an area and its children starting from ancestors paths (inclusive).
77
To keep payload at a reasonable size ancestors must have at least 2 elements.
88
"""
9-
bulkAreas(ancestors: [String!]!): [Area]
9+
bulkAreas(ancestors: [String!]!, limit: Int, offset: Int): [Area]
1010

1111
stats: Stats
1212
cragsNear(

src/graphql/schema/History.gql

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,20 @@ input AllHistoryFilter {
33
userUuid: ID
44
fromDate: Date
55
toDate: Date
6+
limit: Int
7+
offset: Int
68
}
79

810
input AreaHistoryFilter {
911
areaId: ID
12+
limit: Int
13+
offset: Int
1014
}
1115

1216
input OrganizationHistoryFilter {
1317
orgId: MUUID
18+
limit: Int
19+
offset: Int
1420
}
1521

1622
type UpdateDescription {

src/graphql/schema/Organization.gql

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
type Query {
22
organization(muuid: MUUID): Organization
3-
organizations(filter: OrgFilter, sort: OrgSort, limit: Int): [Organization]
3+
organizations(filter: OrgFilter, sort: OrgSort, limit: Int, offset: Int): [Organization]
44
}
55

66
"A climbing area, wall or crag"

0 commit comments

Comments
 (0)