|  | @@ -44,8 +44,137 @@ export default {
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  		async.waterfall(
 | 
	
		
			
				|  |  |  			[
 | 
	
		
			
				|  |  | -				next => {
 | 
	
		
			
				|  |  | -					const newQueries = queries.map(query => {
 | 
	
		
			
				|  |  | +				// Creates pipeline array
 | 
	
		
			
				|  |  | +				next => next(null, []),
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +				// If a filter exists for value, add valueUsername property to all documents
 | 
	
		
			
				|  |  | +				(pipeline, next) => {
 | 
	
		
			
				|  |  | +					// Check if a filter with the value property exists
 | 
	
		
			
				|  |  | +					const valueFilterExists =
 | 
	
		
			
				|  |  | +						queries.map(query => query.filter.property).indexOf("value") !== -1;
 | 
	
		
			
				|  |  | +					// If no such filter exists, skip this function
 | 
	
		
			
				|  |  | +					if (!valueFilterExists) return next(null, pipeline);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +					// Adds valueOID field, which is an ObjectId version of value
 | 
	
		
			
				|  |  | +					pipeline.push({
 | 
	
		
			
				|  |  | +						$addFields: {
 | 
	
		
			
				|  |  | +							valueOID: {
 | 
	
		
			
				|  |  | +								$convert: {
 | 
	
		
			
				|  |  | +									input: "$value",
 | 
	
		
			
				|  |  | +									to: "objectId",
 | 
	
		
			
				|  |  | +									onError: "unknown",
 | 
	
		
			
				|  |  | +									onNull: "unknown"
 | 
	
		
			
				|  |  | +								}
 | 
	
		
			
				|  |  | +							}
 | 
	
		
			
				|  |  | +						}
 | 
	
		
			
				|  |  | +					});
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +					// Looks up user(s) with the same _id as the valueOID and puts the result in the valueUser field
 | 
	
		
			
				|  |  | +					pipeline.push({
 | 
	
		
			
				|  |  | +						$lookup: {
 | 
	
		
			
				|  |  | +							from: "users",
 | 
	
		
			
				|  |  | +							localField: "valueOID",
 | 
	
		
			
				|  |  | +							foreignField: "_id",
 | 
	
		
			
				|  |  | +							as: "valueUser"
 | 
	
		
			
				|  |  | +						}
 | 
	
		
			
				|  |  | +					});
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +					// Unwinds the valueUser array field into an object
 | 
	
		
			
				|  |  | +					pipeline.push({
 | 
	
		
			
				|  |  | +						$unwind: {
 | 
	
		
			
				|  |  | +							path: "$valueUser",
 | 
	
		
			
				|  |  | +							preserveNullAndEmptyArrays: true
 | 
	
		
			
				|  |  | +						}
 | 
	
		
			
				|  |  | +					});
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +					// Adds valueUsername field from the valueUser username, or unknown if it doesn't exist, or Musare if it's set to Musare
 | 
	
		
			
				|  |  | +					pipeline.push({
 | 
	
		
			
				|  |  | +						$addFields: {
 | 
	
		
			
				|  |  | +							valueUsername: {
 | 
	
		
			
				|  |  | +								$cond: [
 | 
	
		
			
				|  |  | +									{ $eq: [ "$type", "banUserId" ] },
 | 
	
		
			
				|  |  | +									{ $ifNull: ["$valueUser.username", "unknown"] },
 | 
	
		
			
				|  |  | +									null
 | 
	
		
			
				|  |  | +								]
 | 
	
		
			
				|  |  | +							}
 | 
	
		
			
				|  |  | +						}
 | 
	
		
			
				|  |  | +					});
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +					// Removes the valueOID and valueUser property, just in case it doesn't get removed at a later stage
 | 
	
		
			
				|  |  | +					pipeline.push({
 | 
	
		
			
				|  |  | +						$project: {
 | 
	
		
			
				|  |  | +							valueOID: 0,
 | 
	
		
			
				|  |  | +							valueUser: 0
 | 
	
		
			
				|  |  | +						}
 | 
	
		
			
				|  |  | +					});
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +					return next(null, pipeline);
 | 
	
		
			
				|  |  | +				},
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +				// If a filter exists for punishedBy, add punishedByUsername property to all documents
 | 
	
		
			
				|  |  | +				(pipeline, next) => {
 | 
	
		
			
				|  |  | +					// Check if a filter with the punishedBy property exists
 | 
	
		
			
				|  |  | +					const punishedByFilterExists =
 | 
	
		
			
				|  |  | +						queries.map(query => query.filter.property).indexOf("punishedBy") !== -1;
 | 
	
		
			
				|  |  | +					// If no such filter exists, skip this function
 | 
	
		
			
				|  |  | +					if (!punishedByFilterExists) return next(null, pipeline);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +					// Adds punishedByOID field, which is an ObjectId version of punishedBy
 | 
	
		
			
				|  |  | +					pipeline.push({
 | 
	
		
			
				|  |  | +						$addFields: {
 | 
	
		
			
				|  |  | +							punishedByOID: {
 | 
	
		
			
				|  |  | +								$convert: {
 | 
	
		
			
				|  |  | +									input: "$punishedBy",
 | 
	
		
			
				|  |  | +									to: "objectId",
 | 
	
		
			
				|  |  | +									onError: "unknown",
 | 
	
		
			
				|  |  | +									onNull: "unknown"
 | 
	
		
			
				|  |  | +								}
 | 
	
		
			
				|  |  | +							}
 | 
	
		
			
				|  |  | +						}
 | 
	
		
			
				|  |  | +					});
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +					// Looks up user(s) with the same _id as the punishedByOID and puts the result in the punishedByUser field
 | 
	
		
			
				|  |  | +					pipeline.push({
 | 
	
		
			
				|  |  | +						$lookup: {
 | 
	
		
			
				|  |  | +							from: "users",
 | 
	
		
			
				|  |  | +							localField: "punishedByOID",
 | 
	
		
			
				|  |  | +							foreignField: "_id",
 | 
	
		
			
				|  |  | +							as: "punishedByUser"
 | 
	
		
			
				|  |  | +						}
 | 
	
		
			
				|  |  | +					});
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +					// Unwinds the punishedByUser array field into an object
 | 
	
		
			
				|  |  | +					pipeline.push({
 | 
	
		
			
				|  |  | +						$unwind: {
 | 
	
		
			
				|  |  | +							path: "$punishedByUser",
 | 
	
		
			
				|  |  | +							preserveNullAndEmptyArrays: true
 | 
	
		
			
				|  |  | +						}
 | 
	
		
			
				|  |  | +					});
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +					// Adds punishedByUsername field from the punishedByUser username, or unknown if it doesn't exist
 | 
	
		
			
				|  |  | +					pipeline.push({
 | 
	
		
			
				|  |  | +						$addFields: {
 | 
	
		
			
				|  |  | +							punishedByUsername: {
 | 
	
		
			
				|  |  | +								$ifNull: ["$punishedByUser.username", "unknown"]
 | 
	
		
			
				|  |  | +							}
 | 
	
		
			
				|  |  | +						}
 | 
	
		
			
				|  |  | +					});
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +					// Removes the punishedByOID and punishedByUser property, just in case it doesn't get removed at a later stage
 | 
	
		
			
				|  |  | +					pipeline.push({
 | 
	
		
			
				|  |  | +						$project: {
 | 
	
		
			
				|  |  | +							punishedByOID: 0,
 | 
	
		
			
				|  |  | +							punishedByUser: 0
 | 
	
		
			
				|  |  | +						}
 | 
	
		
			
				|  |  | +					});
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +					return next(null, pipeline);
 | 
	
		
			
				|  |  | +				},
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +				// Adds the match stage to aggregation pipeline, which is responsible for filtering
 | 
	
		
			
				|  |  | +				(pipeline, next) => {
 | 
	
		
			
				|  |  | +					let queryError;
 | 
	
		
			
				|  |  | +					const newQueries = queries.flatMap(query => {
 | 
	
		
			
				|  |  |  						const { data, filter, filterType } = query;
 | 
	
		
			
				|  |  |  						const newQuery = {};
 | 
	
		
			
				|  |  |  						if (filterType === "regex") {
 | 
	
	
		
			
				|  | @@ -72,8 +201,15 @@ export default {
 | 
	
		
			
				|  |  |  						} else if (filterType === "numberEquals") {
 | 
	
		
			
				|  |  |  							newQuery[filter.property] = { $eq: data };
 | 
	
		
			
				|  |  |  						}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +						if (filter.property === "value")
 | 
	
		
			
				|  |  | +							return { $or: [newQuery, { valueUsername: newQuery.value }] };
 | 
	
		
			
				|  |  | +						if (filter.property === "punishedBy")
 | 
	
		
			
				|  |  | +							return { $or: [newQuery, { punishedByUsername: newQuery.punishedBy }] };
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |  						return newQuery;
 | 
	
		
			
				|  |  |  					});
 | 
	
		
			
				|  |  | +					if (queryError) next(queryError);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  					const queryObject = {};
 | 
	
		
			
				|  |  |  					if (newQueries.length > 0) {
 | 
	
	
		
			
				|  | @@ -82,25 +218,56 @@ export default {
 | 
	
		
			
				|  |  |  						else if (operator === "nor") queryObject.$nor = newQueries;
 | 
	
		
			
				|  |  |  					}
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -					next(null, queryObject);
 | 
	
		
			
				|  |  | +					pipeline.push({ $match: queryObject });
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +					next(null, pipeline);
 | 
	
		
			
				|  |  | +				},
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +				// Adds sort stage to aggregation pipeline if there is at least one column being sorted, responsible for sorting data
 | 
	
		
			
				|  |  | +				(pipeline, next) => {
 | 
	
		
			
				|  |  | +					const newSort = Object.fromEntries(
 | 
	
		
			
				|  |  | +						Object.entries(sort).map(([property, direction]) => [
 | 
	
		
			
				|  |  | +							property,
 | 
	
		
			
				|  |  | +							direction === "ascending" ? 1 : -1
 | 
	
		
			
				|  |  | +						])
 | 
	
		
			
				|  |  | +					);
 | 
	
		
			
				|  |  | +					if (Object.keys(newSort).length > 0) pipeline.push({ $sort: newSort });
 | 
	
		
			
				|  |  | +					next(null, pipeline);
 | 
	
		
			
				|  |  | +				},
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +				// Adds first project stage to aggregation pipeline, responsible for including only the requested properties
 | 
	
		
			
				|  |  | +				(pipeline, next) => {
 | 
	
		
			
				|  |  | +					pipeline.push({ $project: Object.fromEntries(properties.map(property => [property, 1])) });
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +					next(null, pipeline);
 | 
	
		
			
				|  |  |  				},
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -				(queryObject, next) => {
 | 
	
		
			
				|  |  | -					punishmentModel.find(queryObject).count((err, count) => {
 | 
	
		
			
				|  |  | -						next(err, queryObject, count);
 | 
	
		
			
				|  |  | +				// Adds the facet stage to aggregation pipeline, responsible for returning a total document count, skipping and limitting the documents that will be returned
 | 
	
		
			
				|  |  | +				(pipeline, next) => {
 | 
	
		
			
				|  |  | +					pipeline.push({
 | 
	
		
			
				|  |  | +						$facet: {
 | 
	
		
			
				|  |  | +							count: [{ $count: "count" }],
 | 
	
		
			
				|  |  | +							documents: [{ $skip: pageSize * (page - 1) }, { $limit: pageSize }]
 | 
	
		
			
				|  |  | +						}
 | 
	
		
			
				|  |  |  					});
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +					// console.dir(pipeline, { depth: 6 });
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +					next(null, pipeline);
 | 
	
		
			
				|  |  |  				},
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -				(queryObject, count, next) => {
 | 
	
		
			
				|  |  | -					punishmentModel
 | 
	
		
			
				|  |  | -						.find(queryObject)
 | 
	
		
			
				|  |  | -						.sort(sort)
 | 
	
		
			
				|  |  | -						.skip(pageSize * (page - 1))
 | 
	
		
			
				|  |  | -						.limit(pageSize)
 | 
	
		
			
				|  |  | -						.select(properties.join(" "))
 | 
	
		
			
				|  |  | -						.exec((err, punishments) => {
 | 
	
		
			
				|  |  | -							next(err, count, punishments);
 | 
	
		
			
				|  |  | -						});
 | 
	
		
			
				|  |  | +				// Executes the aggregation pipeline
 | 
	
		
			
				|  |  | +				(pipeline, next) => {
 | 
	
		
			
				|  |  | +					punishmentModel.aggregate(pipeline).exec((err, result) => {
 | 
	
		
			
				|  |  | +						// console.dir(err);
 | 
	
		
			
				|  |  | +						// console.dir(result, { depth: 6 });
 | 
	
		
			
				|  |  | +						if (err) return next(err);
 | 
	
		
			
				|  |  | +						if (result[0].count.length === 0) return next(null, 0, []);
 | 
	
		
			
				|  |  | +						const { count } = result[0].count[0];
 | 
	
		
			
				|  |  | +						const { documents } = result[0];
 | 
	
		
			
				|  |  | +						// console.log(111, err, result, count, documents[0]);
 | 
	
		
			
				|  |  | +						return next(null, count, documents);
 | 
	
		
			
				|  |  | +					});
 | 
	
		
			
				|  |  |  				}
 | 
	
		
			
				|  |  |  			],
 | 
	
		
			
				|  |  |  			async (err, count, punishments) => {
 |