@@ -191,8 +191,15 @@ class GroupService {
191
191
* @param {String } tokenV3 Optional. Auth token for Topcoder API v3.
192
192
*/
193
193
constructor ( tokenV3 ) {
194
+ const now = Date . now ( ) ;
194
195
this . private = {
195
196
api : getApiV3 ( tokenV3 ) ,
197
+ cache : {
198
+ groupTreeIds : {
199
+ lastCleanUp : now ,
200
+ data : { } ,
201
+ } ,
202
+ } ,
196
203
tokenV3,
197
204
} ;
198
205
}
@@ -269,6 +276,48 @@ class GroupService {
269
276
return Promise . all ( promises ) . then ( ( ) => res ) ;
270
277
}
271
278
279
+ /**
280
+ * Given a root group ID, returns an ID array that contains the root group ID,
281
+ * and IDs of all descendant groups in the group (sub-)tree rooted at the
282
+ * specified group.
283
+ *
284
+ * Results are cached inside the class instance to minimize the load on TC
285
+ * Group API. To take advantage of that, be sure to keep and reuse the same
286
+ * class instance.
287
+ *
288
+ * The reason to have such strange method and pay an extra attention to its
289
+ * optimization for smaller API load is that it is essential for authorization
290
+ * checks.
291
+ *
292
+ * @param {String } rootGroupId
293
+ * @param {Number } maxage Optional. Max age [ms] of records served from the
294
+ * cache. Defaults to 5 minutes.
295
+ * @return {Promise } Resolves to ID array.
296
+ */
297
+ async getGroupTreeIds ( rootGroupId , maxage = 5 * 60 * 1000 ) {
298
+ const now = Date . now ( ) ;
299
+ const cache = this . private . cache . groupTreeIds ;
300
+
301
+ /* Clean-up: removes stale records from the cache. */
302
+ const CLEAN_UP_INTERVAL = 24 * 60 * 60 * 1000 ; // 1 day in ms.
303
+ if ( now - cache . lastCleanUp > CLEAN_UP_INTERVAL ) {
304
+ _ . forOwn ( cache , ( { timestamp } , key ) => {
305
+ if ( now - timestamp > CLEAN_UP_INTERVAL ) delete cache [ key ] ;
306
+ } ) ;
307
+ cache . lastCleanUp = now ;
308
+ }
309
+
310
+ /* If result is found in cache, and is fresh enough, return it. */
311
+ const cached = cache [ rootGroupId ] ;
312
+ if ( cached && now - cached . timestamp < maxage ) return _ . clone ( cached . data ) ;
313
+
314
+ /* Otherwise, fetch result from the API, write it to the cache, and
315
+ * finally return that. */
316
+ const res = reduceGroupIds ( await this . getGroup ( rootGroupId ) ) ;
317
+ cache [ rootGroupId ] = { data : res , timestamp : now } ;
318
+ return _ . clone ( res ) ;
319
+ }
320
+
272
321
/**
273
322
* Gets group members.
274
323
* @param {String } groupId
@@ -296,6 +345,14 @@ class GroupService {
296
345
if ( ! res . success ) throw new Error ( res . content ) ;
297
346
return Number ( res . content ) ;
298
347
}
348
+
349
+ /**
350
+ * Returns TC Auth Token V3 used by the service instance.
351
+ * @return {String } Token.
352
+ */
353
+ getTokenV3 ( ) {
354
+ return this . private . tokenV3 ;
355
+ }
299
356
}
300
357
301
358
let lastInstance = null ;
0 commit comments