text: // handle the "GET /reviews" request to fetch multiple code review objects
  
  'use strict';
  
  const GetManyRequest = require(process.env.CSSVC_BACKEND_ROOT + '/api_server/lib/util/restful/get_many_request');
  const Indexes = require('./indexes');
  
  class GetReviewsRequest extends GetManyRequest {
  
  	// authorize the request
  	async authorize () {
  		// authorize against the team, this is required
  		this.teamId = this.request.query.teamId;
  		if (!this.teamId) {
  			throw this.errorHandler.error('parameterRequired', { info: 'teamId' });
  		}
  		this.teamId = this.teamId.toLowerCase();
  		const authorized = await this.user.authorizeTeam(this.teamId, this);
  		if (!authorized) {
  			throw this.errorHandler.error('readAuth', { reason: 'user not on team' });
  		}
  	}
  
  	// process the request...
  	async process () {
  		await super.process();	// do the usual "get-many" processing
  		await this.getPosts();	// get associated posts, as needed
  		await this.getMarkers();	// get associated markers, as needed
  	}
  
  	// build the database query to use to fetch the reviews
  	buildQuery () {
  		if (this.request.query.streamId && this.request.query.byLastActivityAt) {
  			return 'can not query on streamId and also on lastActivityAt';
  		}
  		const query = {
  			teamId: this.teamId
  		};
  		if (this.request.query.streamId) {
  			query.streamId = this.request.query.streamId.toLowerCase();
  		}
  		const indexAttribute = this.request.query.byLastActivityAt ? 'lastActivityAt' : 'createdAt';
  		let { before, after, inclusive } = this.request.query;
  		inclusive = inclusive !== undefined;
  		if (before !== undefined) {
  			before = parseInt(before, 10);
  			if (!before) {
  				return 'before must be a number';
  			}
  			query[indexAttribute] = query[indexAttribute] || {};
  			if (inclusive) {
  				query[indexAttribute].$lte = before;
  			}
  			else {
  				query[indexAttribute].$lt = before;
  			}
  		}
  		if (after !== undefined) {
  			after = parseInt(after, 10);
  			if (!after) {
  				return 'after must be a number';
  			}
  			query[indexAttribute] = query[indexAttribute] || {};
  			if (inclusive) {
  				query[indexAttribute].$gte = after;
  			}
  			else {
  				query[indexAttribute].$gt = after;
  			}
  		}
  		return query;
  	}
  
  	// get database options to associate with the database fetch request
  	getQueryOptions () {
  		let hint;
  		if (this.request.query.streamId) {
  			throw 'fetching reviews by streamId is deprecated';
  			//hint = Indexes.byStreamId;
  		}
  		else if (this.request.query.byLastActivityAt) {
  			hint = Indexes.byLastActivityAt;
  		}
  		else {
  			hint = Indexes.byTeamId;
  		}
  		const sortAttribute = this.request.query.byLastActivityAt ? 'lastActivityAt' : 'createdAt';
  		return {
  			hint,
  			sort: { [sortAttribute]: -1 },
  			excludeFields: ['reviewDiffs', 'checkpointReviewDiffs']
  		};
  	}
  
  	// get the posts pointing to the fetched reviews, as needed
  	async getPosts () {
  		const postIds = this.models.map(review => review.get('postId'));
  		if (postIds.length === 0) {
  			return;
  		}
  		this.posts = await this.data.posts.getByIds(postIds);
  		this.responseData.posts = this.posts.map(post => post.getSanitizedObject({ request: this }));
  	}
  
  	// get the markers associated with the fetched codemarks, as needed
  	async getMarkers () {
  		const markerIds = this.models.reduce((markerIds, review) => {
  			markerIds.push(...(review.get('markerIds') || []));
  			return markerIds;
  		}, []);
  		if (markerIds.length === 0) {
  			return;
  		}
  		this.markers = await this.data.markers.getByIds(markerIds);
  		this.responseData.markers = this.markers.map(marker => marker.getSanitizedObject({ request: this }));
  	}
  
  	// describe this route for help
  	static describe (module) {
  		const description = GetManyRequest.describe(module);
  		description.description = 'Returns an array of reviews owned by a team, optionally providing a stream ID; always returns any associated posts';
  		description.access = 'User must be a member of the team specified';
  		Object.assign(description.input.looksLike, {
  			'teamId*': '<ID of the team for which codemarks are being fetched>',
  			'streamId': '<ID of the stream for which codemarks should be fetched>',
  			'byLastActivityAt': '<boolean indicates to fetch and sort by lastActivityAt, instead of createdAt>',
  			'before': '<Fetch codemarks created before this timestamp, inclusive if "inclusive" is set>',
  			'after': '<Fetch codemarks created after this timestamp, inclusive if "inclusive" is set>',
  			'inclusive': '<If before or after or both are set, indicates to include any codemarks with a timestamp exactly matching the before or after vaue (or both)>'
  		});
  		description.returns.summary = 'An array of review objects, plus possible post objects';
  		Object.assign(description.returns.looksLike, {
  			reviews: '<@@#review objects#review@@ fetched>',
  			posts: '<associated @@#post objects#post@@>'
  		});
  		description.errors = description.errors.concat([
  			'invalidParameter',
  			'parameterRequired'
  		]);
  		return description;
  	}
  }
  
  module.exports = GetReviewsRequest;
  