-
Notifications
You must be signed in to change notification settings - Fork 70
Expand file tree
/
Copy pathsanitizeParams.ts
More file actions
115 lines (103 loc) · 4.11 KB
/
sanitizeParams.ts
File metadata and controls
115 lines (103 loc) · 4.11 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
import _ from 'underscore';
import bsonUrlEncoding from './bsonUrlEncoding';
import getPropertyViaDotNotation from './getPropertyViaDotNotation';
import config from '../config';
import { Encodable } from './bsonUrlEncoding';
export interface SanitizeParams {
previous?: string | [unknown, unknown] | Encodable;
next?: string | [unknown, unknown] | Encodable;
after?: string;
before?: string;
limit?: number;
paginatedField?: string;
sortCaseInsensitive?: boolean;
fields?: Record<string, number>;
}
export default async function sanitizeParams(
collection: any,
params: SanitizeParams
): Promise<SanitizeParams> {
if (params.previous) params.previous = bsonUrlEncoding.decode(params.previous as string);
if (params.next) params.next = bsonUrlEncoding.decode(params.next as string);
params = _.defaults(params, {
limit: config.DEFAULT_LIMIT,
paginatedField: '_id',
});
if (params.limit < 1) params.limit = 1;
if (params.limit > config.MAX_LIMIT) params.limit = config.MAX_LIMIT;
// If the paginated field is not _id, then it might have duplicate values in it. This is bad
// because then we can't exclusively use it for our range queries (that use $lt and $gt). So
// to fix this, we secondarily sort on _id, which is always unique.
const shouldSecondarySortOnId = params.paginatedField !== '_id';
//
// params.after - overides params.next
//
// The 'after' param sets the start position for the next page. This is similar to the
// 'next' param, with the difference that 'after' takes a plain _id instead of an encoded
// string of both _id and paginatedField values.
if (params.after) {
if (shouldSecondarySortOnId) {
// Since the primary sort field is not provided by the 'after' pagination cursor we
// have to look it up when the paginated field is not _id.
const doc = await collection.findOne(
{ _id: params.after },
{ [params.paginatedField]: true, _id: false }
);
if (doc) {
// Handle usage of dot notation in paginatedField
let prop = getPropertyViaDotNotation(params.paginatedField, doc);
if (params.sortCaseInsensitive && typeof prop === 'string') {
prop = prop.toLowerCase();
}
params.next = [prop, params.after];
}
} else {
params.next = params.after;
}
}
//
// params.before - overides params.previous
//
// The 'before' param sets the start position for the previous page. This is similar to the
// 'previous' param, with the difference that 'before' takes a plain _id instead of an encoded
// string of both _id and paginatedField values.
if (params.before) {
if (shouldSecondarySortOnId) {
// Since the primary sort field is not provided by the 'before' pagination cursor we
// have to look it up when the paginated field is not _id.
const doc = await collection.findOne(
{ _id: params.before },
{ [params.paginatedField]: true, _id: false }
);
if (doc) {
// Handle usage of dot notation in paginatedField
let prop = getPropertyViaDotNotation(params.paginatedField, doc);
if (params.sortCaseInsensitive && typeof prop === 'string') {
prop = prop.toLowerCase();
}
params.previous = [prop, params.before];
}
} else {
params.previous = params.before;
}
}
// The query must always include the paginatedField so we can construct the cursor.
if (params.fields) {
params.fields = _.extend(
{
_id: 0, // Mongo includes this field by default, so don't request it unless the user wants it.
},
params.fields
);
if (!params.fields[params.paginatedField]) {
params.fields[params.paginatedField] = 1;
}
// When using secondary sort (paginatedField !== '_id'), we need _id for cursor encoding.
// Cursors are encoded as [paginatedFieldValue, _id] tuples (see encodePaginationTokens in query.ts).
// Without _id, the cursor would be encoded as a string, breaking pagination on subsequent pages.
if (shouldSecondarySortOnId && !params.fields._id) {
params.fields._id = 1;
}
}
return params;
}