@@ -9,12 +9,17 @@ const helper = require('../common/helper')
9
9
const eshelper = require ( '../common/eshelper' )
10
10
const logger = require ( '../common/logger' )
11
11
const errors = require ( '../common/errors' )
12
+ const { BOOLEAN_OPERATOR } = require ( '../../app-constants' )
12
13
13
14
const MEMBER_FIELDS = [ 'userId' , 'handle' , 'handleLower' , 'firstName' , 'lastName' ,
14
15
'status' , 'addresses' , 'photoURL' , 'homeCountryCode' , 'competitionCountryCode' ,
15
16
'description' , 'email' , 'tracks' , 'maxRating' , 'wins' , 'createdAt' , 'createdBy' ,
16
17
'updatedAt' , 'updatedBy' , 'skills' , 'stats' , 'emsiSkills' ]
17
18
19
+ const MEMBER_SORT_BY_FIELDS = [ 'userId' , 'country' , 'handle' , 'firstName' , 'lastName' ,
20
+ 'accountAge' , 'numberOfChallengesWon' ,
21
+ 'numberOfChallengesPlaced' ]
22
+
18
23
const MEMBER_AUTOCOMPLETE_FIELDS = [ 'userId' , 'handle' , 'handleLower' ,
19
24
'status' , 'email' , 'createdAt' , 'updatedAt' ]
20
25
@@ -39,19 +44,42 @@ async function searchMembers (currentUser, query) {
39
44
}
40
45
41
46
if ( query . email != null && query . email . length > 0 ) {
42
- if ( currentUser == null ) {
43
- throw new errors . UnauthorizedError ( " Authentication token is required to query users by email" ) ;
44
- }
45
- if ( ! helper . hasSearchByEmailRole ( currentUser ) ) {
46
- throw new errors . BadRequestError ( " Admin role is required to query users by email" ) ;
47
- }
47
+ if ( currentUser == null ) {
48
+ throw new errors . UnauthorizedError ( ' Authentication token is required to query users by email' )
49
+ }
50
+ if ( ! helper . hasSearchByEmailRole ( currentUser ) ) {
51
+ throw new errors . BadRequestError ( ' Admin role is required to query users by email' )
52
+ }
48
53
}
49
54
50
55
// search for the members based on query
51
56
const docsMembers = await eshelper . getMembers ( query , esClient , currentUser )
52
57
58
+ return fillMembers ( docsMembers , query , fields )
59
+ }
60
+
61
+ searchMembers . schema = {
62
+ currentUser : Joi . any ( ) ,
63
+ query : Joi . object ( ) . keys ( {
64
+ handleLower : Joi . string ( ) ,
65
+ handlesLower : Joi . array ( ) ,
66
+ handle : Joi . string ( ) ,
67
+ handles : Joi . array ( ) ,
68
+ email : Joi . string ( ) ,
69
+ userId : Joi . number ( ) ,
70
+ userIds : Joi . array ( ) ,
71
+ term : Joi . string ( ) ,
72
+ fields : Joi . string ( ) ,
73
+ page : Joi . page ( ) ,
74
+ perPage : Joi . perPage ( ) ,
75
+ sort : Joi . sort ( )
76
+ } )
77
+ }
78
+
79
+ async function fillMembers ( docsMembers , query , fields ) {
53
80
// get the total
54
81
const total = eshelper . getTotal ( docsMembers )
82
+
55
83
let results = [ ]
56
84
if ( total > 0 ) {
57
85
// extract member profiles from hits
@@ -108,24 +136,68 @@ async function searchMembers (currentUser, query) {
108
136
return { total : total , page : query . page , perPage : query . perPage , result : results }
109
137
}
110
138
111
- searchMembers . schema = {
139
+ // TODO - use some caching approach to replace these in-memory objects
140
+ /**
141
+ * Search members by the given search query
142
+ *
143
+ * @param query The search query by which to search members
144
+ *
145
+ * @returns {Promise<[]> } The array of members matching the given query
146
+ */
147
+ const searchMembersBySkills = async ( currentUser , query ) => {
148
+ const esClient = await helper . getESClient ( )
149
+ let skillIds = await helper . getParamsFromQueryAsArray ( query , 'skillId' )
150
+ const result = searchMembersBySkillsWithOptions ( currentUser , query , skillIds , BOOLEAN_OPERATOR . AND , query . page , query . perPage , query . sortBy , query . sortOrder , esClient )
151
+ return result
152
+ }
153
+
154
+ searchMembersBySkills . schema = {
112
155
currentUser : Joi . any ( ) ,
113
156
query : Joi . object ( ) . keys ( {
114
- handleLower : Joi . string ( ) ,
115
- handlesLower : Joi . array ( ) ,
116
- handle : Joi . string ( ) ,
117
- handles : Joi . array ( ) ,
118
- email : Joi . string ( ) ,
119
- userId : Joi . number ( ) ,
120
- userIds : Joi . array ( ) ,
121
- term : Joi . string ( ) ,
122
- fields : Joi . string ( ) ,
157
+ skillId : Joi . alternatives ( ) . try ( Joi . string ( ) , Joi . array ( ) . items ( Joi . string ( ) ) ) ,
123
158
page : Joi . page ( ) ,
124
159
perPage : Joi . perPage ( ) ,
125
- sort : Joi . sort ( )
160
+ sortBy : Joi . string ( ) . valid ( MEMBER_SORT_BY_FIELDS ) . default ( 'numberOfChallengesWon' ) ,
161
+ sortOrder : Joi . string ( ) . valid ( 'asc' , 'desc' ) . default ( 'desc' )
126
162
} )
127
163
}
128
164
165
+ /**
166
+ * Search members matching the given skills
167
+ *
168
+ * @param currentUser
169
+ * @param skillsFilter
170
+ * @param skillsBooleanOperator
171
+ * @param page
172
+ * @param perPage
173
+ * @param sortBy
174
+ * @param sortOrder
175
+ * @param esClient
176
+ * @returns {Promise<*[]|{total, perPage, numberOfPages: number, data: *[], page}> }
177
+ */
178
+ const searchMembersBySkillsWithOptions = async ( currentUser , query , skillsFilter , skillsBooleanOperator , page , perPage , sortBy , sortOrder , esClient ) => {
179
+ let fields = helper . parseCommaSeparatedString ( query . fields , MEMBER_FIELDS ) || MEMBER_FIELDS
180
+ // if current user is not admin and not M2M, then exclude the admin/M2M only fields
181
+ if ( ! currentUser || ( ! currentUser . isMachine && ! helper . hasAdminRole ( currentUser ) ) ) {
182
+ fields = _ . without ( fields , ...config . SEARCH_SECURE_FIELDS )
183
+ MEMBER_STATS_FIELDS = _ . without ( MEMBER_STATS_FIELDS , ...config . STATISTICS_SECURE_FIELDS )
184
+ }
185
+
186
+ const emptyResult = {
187
+ total : 0 ,
188
+ page,
189
+ perPage,
190
+ numberOfPages : 0 ,
191
+ data : [ ]
192
+ }
193
+ if ( _ . isEmpty ( skillsFilter ) ) {
194
+ return emptyResult
195
+ }
196
+
197
+ const membersSkillsDocs = await eshelper . searchMembersSkills ( skillsFilter , skillsBooleanOperator , page , perPage , esClient )
198
+
199
+ return fillMembers ( membersSkillsDocs , query , fields )
200
+ }
129
201
/**
130
202
* members autocomplete.
131
203
* @param {Object } currentUser the user who performs operation
@@ -148,7 +220,7 @@ async function autocomplete (currentUser, query) {
148
220
// custom filter & sort
149
221
let regex = new RegExp ( `^${ query . term } ` , `i` )
150
222
// sometimes .payload is not defined. so use _source instead
151
- results = results . map ( x => ( { ...x , payload : x . payload || x . _source } ) )
223
+ results = results . map ( x => ( { ...x , payload : x . payload || x . _source } ) )
152
224
results = results
153
225
. filter ( x => regex . test ( x . payload . handle ) )
154
226
. sort ( ( a , b ) => a . payload . handle . localeCompare ( b . payload . handle ) )
@@ -175,6 +247,7 @@ autocomplete.schema = {
175
247
176
248
module . exports = {
177
249
searchMembers,
250
+ searchMembersBySkills,
178
251
autocomplete
179
252
}
180
253
0 commit comments