Browse Source

refactor: combined backend getData/GET_DATA into one db GET_DATA job

Kristian Vos 3 years ago
parent
commit
6b1cf17877

+ 27 - 99
backend/logic/actions/dataRequests.js

@@ -37,106 +37,34 @@ export default {
 
 		async.waterfall(
 			[
-				// Creates pipeline array
-				next => next(null, []),
-
-				// 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") {
-							newQuery[filter.property] = new RegExp(`${data.slice(1, data.length - 1)}`, "i");
-						} else if (filterType === "contains") {
-							newQuery[filter.property] = new RegExp(
-								`${data.replaceAll(/[.*+?^${}()|[\]\\]/g, "\\$&")}`,
-								"i"
-							);
-						} else if (filterType === "exact") {
-							newQuery[filter.property] = data.toString();
-						} else if (filterType === "datetimeBefore") {
-							newQuery[filter.property] = { $lte: new Date(data) };
-						} else if (filterType === "datetimeAfter") {
-							newQuery[filter.property] = { $gte: new Date(data) };
-						} else if (filterType === "numberLesserEqual") {
-							newQuery[filter.property] = { $lte: Number(data) };
-						} else if (filterType === "numberLesser") {
-							newQuery[filter.property] = { $lt: Number(data) };
-						} else if (filterType === "numberGreater") {
-							newQuery[filter.property] = { $gt: Number(data) };
-						} else if (filterType === "numberGreaterEqual") {
-							newQuery[filter.property] = { $gte: Number(data) };
-						} else if (filterType === "numberEquals") {
-							newQuery[filter.property] = { $eq: Number(data) };
-						} else if (filterType === "boolean") {
-							newQuery[filter.property] = { $eq: !!data };
-						}
-
-						return newQuery;
-					});
-					if (queryError) next(queryError);
-
-					const queryObject = {};
-					if (newQueries.length > 0) {
-						if (operator === "and") queryObject.$and = newQueries;
-						else if (operator === "or") queryObject.$or = newQueries;
-						else if (operator === "nor") queryObject.$nor = newQueries;
-					}
-
-					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);
-				},
-
-				// 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);
-				},
-
-				// Executes the aggregation pipeline
-				(pipeline, next) => {
-					dataRequestModel.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);
-					});
+				next => {
+					DBModule.runJob(
+						"GET_DATA",
+						{
+							page,
+							pageSize,
+							properties,
+							sort,
+							queries,
+							operator,
+							modelName: "dataRequest",
+							blacklistedProperties: [],
+							specialProperties: {
+							},
+							specialQueries: {
+							}
+						},
+						this
+					)
+						.then(response => {
+							next(null, response);
+						})
+						.catch(err => {
+							next(err);
+						});
 				}
 			],
-			async (err, count, dataRequests) => {
+			async (err, response) => {
 				if (err && err !== true) {
 					err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
 					this.log("ERROR", "DATA_REQUESTS_GET_DATA", `Failed to get data from data requests. "${err}"`);
@@ -146,7 +74,7 @@ export default {
 				return cb({
 					status: "success",
 					message: "Successfully got data from data requests.",
-					data: { data: dataRequests, count }
+					data: response
 				});
 			}
 		);

+ 68 - 161
backend/logic/actions/news.js

@@ -73,169 +73,76 @@ export default {
 
 		async.waterfall(
 			[
-				// Creates pipeline array
-				next => next(null, []),
-
-				// If a filter exists for createdBy, add createdByUsername property to all documents
-				(pipeline, next) => {
-					// Check if a filter with the createdBy property exists
-					const createdByFilterExists =
-						queries.map(query => query.filter.property).indexOf("createdBy") !== -1;
-					// If no such filter exists, skip this function
-					if (!createdByFilterExists) return next(null, pipeline);
-
-					// Adds createdByOID field, which is an ObjectId version of createdBy
-					pipeline.push({
-						$addFields: {
-							createdByOID: {
-								$convert: {
-									input: "$createdBy",
-									to: "objectId",
-									onError: "unknown",
-									onNull: "unknown"
-								}
-							}
-						}
-					});
-
-					// Looks up user(s) with the same _id as the createdByOID and puts the result in the createdByUser field
-					pipeline.push({
-						$lookup: {
-							from: "users",
-							localField: "createdByOID",
-							foreignField: "_id",
-							as: "createdByUser"
-						}
-					});
-
-					// Unwinds the createdByUser array field into an object
-					pipeline.push({
-						$unwind: {
-							path: "$createdByUser",
-							preserveNullAndEmptyArrays: true
-						}
-					});
-
-					// Adds createdByUsername field from the createdByUser username, or unknown if it doesn't exist
-					pipeline.push({
-						$addFields: {
-							createdByUsername: {
-								$ifNull: ["$createdByUser.username", "unknown"]
+				next => {
+					DBModule.runJob(
+						"GET_DATA",
+						{
+							page,
+							pageSize,
+							properties,
+							sort,
+							queries,
+							operator,
+							modelName: "news",
+							blacklistedProperties: [],
+							specialProperties: {
+								"createdBy": [
+									{
+										$addFields: {
+											createdByOID: {
+												$convert: {
+													input: "$createdBy",
+													to: "objectId",
+													onError: "unknown",
+													onNull: "unknown"
+												}
+											}
+										}
+									},
+									{
+										$lookup: {
+											from: "users",
+											localField: "createdByOID",
+											foreignField: "_id",
+											as: "createdByUser"
+										}
+									},
+									{
+										$unwind: {
+											path: "$createdByUser",
+											preserveNullAndEmptyArrays: true
+										}
+									},
+									{
+										$addFields: {
+											createdByUsername: {
+												$ifNull: ["$createdByUser.username", "unknown"]
+											}
+										}
+									},
+									{
+										$project: {
+											createdByOID: 0,
+											createdByUser: 0
+										}
+									}
+								]
+							},
+							specialQueries: {
+								createdBy: newQuery => ({ $or: [newQuery, { createdByUsername: newQuery.createdBy }] })
 							}
-						}
-					});
-
-					// Removes the createdByOID and createdByUser property, just in case it doesn't get removed at a later stage
-					pipeline.push({
-						$project: {
-							createdByOID: 0,
-							createdByUser: 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") {
-							newQuery[filter.property] = new RegExp(`${data.slice(1, data.length - 1)}`, "i");
-						} else if (filterType === "contains") {
-							newQuery[filter.property] = new RegExp(
-								`${data.replaceAll(/[.*+?^${}()|[\]\\]/g, "\\$&")}`,
-								"i"
-							);
-						} else if (filterType === "exact") {
-							newQuery[filter.property] = data.toString();
-						} else if (filterType === "datetimeBefore") {
-							newQuery[filter.property] = { $lte: new Date(data) };
-						} else if (filterType === "datetimeAfter") {
-							newQuery[filter.property] = { $gte: new Date(data) };
-						} else if (filterType === "numberLesserEqual") {
-							newQuery[filter.property] = { $lte: Number(data) };
-						} else if (filterType === "numberLesser") {
-							newQuery[filter.property] = { $lt: Number(data) };
-						} else if (filterType === "numberGreater") {
-							newQuery[filter.property] = { $gt: Number(data) };
-						} else if (filterType === "numberGreaterEqual") {
-							newQuery[filter.property] = { $gte: Number(data) };
-						} else if (filterType === "numberEquals") {
-							newQuery[filter.property] = { $eq: Number(data) };
-						} else if (filterType === "boolean") {
-							newQuery[filter.property] = { $eq: !!data };
-						}
-
-						if (filter.property === "createdBy")
-							return { $or: [newQuery, { createdByUsername: newQuery.createdBy }] };
-
-						return newQuery;
-					});
-					if (queryError) next(queryError);
-
-					const queryObject = {};
-					if (newQueries.length > 0) {
-						if (operator === "and") queryObject.$and = newQueries;
-						else if (operator === "or") queryObject.$or = newQueries;
-						else if (operator === "nor") queryObject.$nor = newQueries;
-					}
-
-					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);
-				},
-
-				// 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);
-				},
-
-				// Executes the aggregation pipeline
-				(pipeline, next) => {
-					newsModel.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);
-					});
+						},
+						this
+					)
+						.then(response => {
+							next(null, response);
+						})
+						.catch(err => {
+							next(err);
+						});
 				}
 			],
-			async (err, count, news) => {
+			async (err, response) => {
 				if (err && err !== true) {
 					err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
 					this.log("ERROR", "NEWS_GET_DATA", `Failed to get data from news. "${err}"`);
@@ -245,7 +152,7 @@ export default {
 				return cb({
 					status: "success",
 					message: "Successfully got data from news.",
-					data: { data: news, count }
+					data: response
 				});
 			}
 		);

+ 68 - 2
backend/logic/actions/playlists.js

@@ -275,7 +275,7 @@ export default {
 		async.waterfall(
 			[
 				next => {
-					PlaylistsModule.runJob(
+					DBModule.runJob(
 						"GET_DATA",
 						{
 							page,
@@ -283,7 +283,73 @@ export default {
 							properties,
 							sort,
 							queries,
-							operator
+							operator,
+							modelName: "playlist",
+							blacklistedProperties: [],
+							specialProperties: {
+								"totalLength": [
+									{
+										$addFields: {
+											totalLength: { $sum: "$songs.duration" }
+										}
+									}
+								],
+								"songsCount": [
+									{
+										$addFields: {
+											songsCount: { $size: "$songs" }
+										}
+									}
+								],
+								"createdBy": [
+									{
+										$addFields: {
+											createdByOID: {
+												$convert: {
+													input: "$createdBy",
+													to: "objectId",
+													onError: "unknown",
+													onNull: "unknown"
+												}
+											}
+										}
+									},
+									{
+										$lookup: {
+											from: "users",
+											localField: "createdByOID",
+											foreignField: "_id",
+											as: "createdByUser"
+										}
+									},
+									{
+										$unwind: {
+											path: "$createdByUser",
+											preserveNullAndEmptyArrays: true
+										}
+									},
+									{
+										$addFields: {
+											createdByUsername: {
+												$cond: [
+													{ $eq: ["$createdBy", "Musare"] },
+													"Musare",
+													{ $ifNull: ["$createdByUser.username", "unknown"] }
+												]
+											}
+										}
+									},
+									{
+										$project: {
+											createdByOID: 0,
+											createdByUser: 0
+										}
+									}
+								]
+							},
+							specialQueries: {
+								createdBy: newQuery => ({ $or: [newQuery, { createdByUsername: newQuery.createdBy }] })
+							}
 						},
 						this
 					)

+ 126 - 249
backend/logic/actions/punishments.js

@@ -44,258 +44,135 @@ export default {
 
 		async.waterfall(
 			[
-				// Creates pipeline array
-				next => next(null, []),
-
-				// If a filter or property exists for status, add status property to all documents
-				(pipeline, next) => {
-					// Check if a filter with the status property exists
-					const statusFilterExists = queries.map(query => query.filter.property).indexOf("status") !== -1;
-					// Check if a property with the status property exists
-					const statusPropertyExists = properties.indexOf("status") !== -1;
-					// If no such filter or property exists, skip this function
-					if (!statusFilterExists && !statusPropertyExists) return next(null, pipeline);
-
-					// Adds status field, set to Inactive if active is false, otherwise it sets it to Inactive if expiresAt has already passed, Active if not
-					pipeline.push({
-						$addFields: {
-							status: {
-								$cond: [
-									{ $eq: ["$active", true] },
-									{ $cond: [{ $gt: [new Date(), "$expiresAt"] }, "Inactive", "Active"] },
-									"Inactive"
-								]
-							}
-						}
-					});
-
-					return next(null, pipeline);
-				},
-
-				// 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
+				next => {
+					DBModule.runJob(
+						"GET_DATA",
+						{
+							page,
+							pageSize,
+							properties,
+							sort,
+							queries,
+							operator,
+							modelName: "punishment",
+							blacklistedProperties: [],
+							specialProperties: {
+								status: [
+									{
+										$addFields: {
+											status: {
+												$cond: [
+													{ $eq: ["$active", true] },
+													{ $cond: [{ $gt: [new Date(), "$expiresAt"] }, "Inactive", "Active"] },
+													"Inactive"
+												]
+											}
+										}
+									}
+								],
+								value: [
+									{
+										$addFields: {
+											valueOID: {
+												$convert: {
+													input: "$value",
+													to: "objectId",
+													onError: "unknown",
+													onNull: "unknown"
+												}
+											}
+										}
+									},
+									{
+										$lookup: {
+											from: "users",
+											localField: "valueOID",
+											foreignField: "_id",
+											as: "valueUser"
+										}
+									},
+									{
+										$unwind: {
+											path: "$valueUser",
+											preserveNullAndEmptyArrays: true
+										}
+									},
+									{
+										$addFields: {
+											valueUsername: {
+												$cond: [
+													{ $eq: ["$type", "banUserId"] },
+													{ $ifNull: ["$valueUser.username", "unknown"] },
+													null
+												]
+											}
+										}
+									},
+									{
+										$project: {
+											valueOID: 0,
+											valueUser: 0
+										}
+									}
+								],
+								punishedBy: [
+									{
+										$addFields: {
+											punishedByOID: {
+												$convert: {
+													input: "$punishedBy",
+													to: "objectId",
+													onError: "unknown",
+													onNull: "unknown"
+												}
+											}
+										}
+									},
+									{
+										$lookup: {
+											from: "users",
+											localField: "punishedByOID",
+											foreignField: "_id",
+											as: "punishedByUser"
+										}
+									},
+									{
+										$unwind: {
+											path: "$punishedByUser",
+											preserveNullAndEmptyArrays: true
+										}
+									},
+									{
+										$addFields: {
+											punishedByUsername: {
+												$ifNull: ["$punishedByUser.username", "unknown"]
+											}
+										}
+									},
+									{
+										$project: {
+											punishedByOID: 0,
+											punishedByUser: 0
+										}
+									}
 								]
+							},
+							specialQueries: {
+								value: newQuery => ({ $or: [newQuery, { valueUsername: newQuery.value }] }),
+								punishedBy: newQuery => ({ $or: [newQuery, { punishedByUsername: newQuery.punishedBy }] })
 							}
-						}
-					});
-
-					// 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") {
-							newQuery[filter.property] = new RegExp(`${data.slice(1, data.length - 1)}`, "i");
-						} else if (filterType === "contains") {
-							newQuery[filter.property] = new RegExp(
-								`${data.replaceAll(/[.*+?^${}()|[\]\\]/g, "\\$&")}`,
-								"i"
-							);
-						} else if (filterType === "exact") {
-							newQuery[filter.property] = data.toString();
-						} else if (filterType === "datetimeBefore") {
-							newQuery[filter.property] = { $lte: new Date(data) };
-						} else if (filterType === "datetimeAfter") {
-							newQuery[filter.property] = { $gte: new Date(data) };
-						} else if (filterType === "numberLesserEqual") {
-							newQuery[filter.property] = { $lte: Number(data) };
-						} else if (filterType === "numberLesser") {
-							newQuery[filter.property] = { $lt: Number(data) };
-						} else if (filterType === "numberGreater") {
-							newQuery[filter.property] = { $gt: Number(data) };
-						} else if (filterType === "numberGreaterEqual") {
-							newQuery[filter.property] = { $gte: Number(data) };
-						} else if (filterType === "numberEquals") {
-							newQuery[filter.property] = { $eq: Number(data) };
-						} else if (filterType === "boolean") {
-							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) {
-						if (operator === "and") queryObject.$and = newQueries;
-						else if (operator === "or") queryObject.$or = newQueries;
-						else if (operator === "nor") queryObject.$nor = newQueries;
-					}
-
-					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);
-				},
-
-				// 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);
-				},
-
-				// 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);
-					});
+						},
+						this
+					)
+						.then(response => {
+							next(null, response);
+						})
+						.catch(err => {
+							next(err);
+						});
 				}
 			],
-			async (err, count, punishments) => {
+			async (err, response) => {
 				if (err && err !== true) {
 					err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
 					this.log("ERROR", "PUNISHMENTS_GET_DATA", `Failed to get data from punishments. "${err}"`);
@@ -305,7 +182,7 @@ export default {
 				return cb({
 					status: "success",
 					message: "Successfully got data from punishments.",
-					data: { data: punishments, count }
+					data: response
 				});
 			}
 		);

+ 68 - 161
backend/logic/actions/reports.js

@@ -76,169 +76,76 @@ export default {
 
 		async.waterfall(
 			[
-				// Creates pipeline array
-				next => next(null, []),
-
-				// If a filter exists for createdBy, add createdByUsername property to all documents
-				(pipeline, next) => {
-					// Check if a filter with the createdBy property exists
-					const createdByFilterExists =
-						queries.map(query => query.filter.property).indexOf("createdBy") !== -1;
-					// If no such filter exists, skip this function
-					if (!createdByFilterExists) return next(null, pipeline);
-
-					// Adds createdByOID field, which is an ObjectId version of createdBy
-					pipeline.push({
-						$addFields: {
-							createdByOID: {
-								$convert: {
-									input: "$createdBy",
-									to: "objectId",
-									onError: "unknown",
-									onNull: "unknown"
-								}
-							}
-						}
-					});
-
-					// Looks up user(s) with the same _id as the createdByOID and puts the result in the createdByUser field
-					pipeline.push({
-						$lookup: {
-							from: "users",
-							localField: "createdByOID",
-							foreignField: "_id",
-							as: "createdByUser"
-						}
-					});
-
-					// Unwinds the createdByUser array field into an object
-					pipeline.push({
-						$unwind: {
-							path: "$createdByUser",
-							preserveNullAndEmptyArrays: true
-						}
-					});
-
-					// Adds createdByUsername field from the createdByUser username, or unknown if it doesn't exist
-					pipeline.push({
-						$addFields: {
-							createdByUsername: {
-								$ifNull: ["$createdByUser.username", "unknown"]
+				next => {
+					DBModule.runJob(
+						"GET_DATA",
+						{
+							page,
+							pageSize,
+							properties,
+							sort,
+							queries,
+							operator,
+							modelName: "report",
+							blacklistedProperties: [],
+							specialProperties: {
+								"createdBy": [
+									{
+										$addFields: {
+											createdByOID: {
+												$convert: {
+													input: "$createdBy",
+													to: "objectId",
+													onError: "unknown",
+													onNull: "unknown"
+												}
+											}
+										}
+									},
+									{
+										$lookup: {
+											from: "users",
+											localField: "createdByOID",
+											foreignField: "_id",
+											as: "createdByUser"
+										}
+									},
+									{
+										$unwind: {
+											path: "$createdByUser",
+											preserveNullAndEmptyArrays: true
+										}
+									},
+									{
+										$addFields: {
+											createdByUsername: {
+												$ifNull: ["$createdByUser.username", "unknown"]
+											}
+										}
+									},
+									{
+										$project: {
+											createdByOID: 0,
+											createdByUser: 0
+										}
+									}
+								]
+							},
+							specialQueries: {
+								createdBy: newQuery => ({ $or: [newQuery, { createdByUsername: newQuery.createdBy }] })
 							}
-						}
-					});
-
-					// Removes the createdByOID and createdByUser property, just in case it doesn't get removed at a later stage
-					pipeline.push({
-						$project: {
-							createdByOID: 0,
-							createdByUser: 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") {
-							newQuery[filter.property] = new RegExp(`${data.slice(1, data.length - 1)}`, "i");
-						} else if (filterType === "contains") {
-							newQuery[filter.property] = new RegExp(
-								`${data.replaceAll(/[.*+?^${}()|[\]\\]/g, "\\$&")}`,
-								"i"
-							);
-						} else if (filterType === "exact") {
-							newQuery[filter.property] = data.toString();
-						} else if (filterType === "datetimeBefore") {
-							newQuery[filter.property] = { $lte: new Date(data) };
-						} else if (filterType === "datetimeAfter") {
-							newQuery[filter.property] = { $gte: new Date(data) };
-						} else if (filterType === "numberLesserEqual") {
-							newQuery[filter.property] = { $lte: Number(data) };
-						} else if (filterType === "numberLesser") {
-							newQuery[filter.property] = { $lt: Number(data) };
-						} else if (filterType === "numberGreater") {
-							newQuery[filter.property] = { $gt: Number(data) };
-						} else if (filterType === "numberGreaterEqual") {
-							newQuery[filter.property] = { $gte: Number(data) };
-						} else if (filterType === "numberEquals") {
-							newQuery[filter.property] = { $eq: Number(data) };
-						} else if (filterType === "boolean") {
-							newQuery[filter.property] = { $eq: !!data };
-						}
-
-						if (filter.property === "createdBy")
-							return { $or: [newQuery, { createdByUsername: newQuery.createdBy }] };
-
-						return newQuery;
-					});
-					if (queryError) next(queryError);
-
-					const queryObject = {};
-					if (newQueries.length > 0) {
-						if (operator === "and") queryObject.$and = newQueries;
-						else if (operator === "or") queryObject.$or = newQueries;
-						else if (operator === "nor") queryObject.$nor = newQueries;
-					}
-
-					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);
-				},
-
-				// 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);
-				},
-
-				// Executes the aggregation pipeline
-				(pipeline, next) => {
-					reportModel.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);
-					});
+						},
+						this
+					)
+						.then(response => {
+							next(null, response);
+						})
+						.catch(err => {
+							next(err);
+						});
 				}
 			],
-			async (err, count, reports) => {
+			async (err, response) => {
 				if (err && err !== true) {
 					err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
 					this.log("ERROR", "REPORTS_GET_DATA", `Failed to get data from reports. "${err}"`);
@@ -248,7 +155,7 @@ export default {
 				return cb({
 					status: "success",
 					message: "Successfully got data from reports.",
-					data: { data: reports, count }
+					data: response
 				});
 			}
 		);

+ 86 - 2
backend/logic/actions/songs.js

@@ -191,7 +191,7 @@ export default {
 		async.waterfall(
 			[
 				next => {
-					SongsModule.runJob(
+					DBModule.runJob(
 						"GET_DATA",
 						{
 							page,
@@ -199,7 +199,91 @@ export default {
 							properties,
 							sort,
 							queries,
-							operator
+							operator,
+							modelName: "song",
+							blacklistedProperties: [],
+							specialProperties: {
+								"requestedBy": [
+									{
+										$addFields: {
+											requestedByOID: {
+												$convert: {
+													input: "$requestedBy",
+													to: "objectId",
+													onError: "unknown",
+													onNull: "unknown"
+												}
+											}
+										}
+									},
+									{
+										$lookup: {
+											from: "users",
+											localField: "requestedByOID",
+											foreignField: "_id",
+											as: "requestedByUser"
+										}
+									},
+									{
+										$addFields: {
+											requestedByUsername: {
+												$ifNull: ["$requestedByUser.username", "unknown"]
+											}
+										}
+									},
+									{
+										$project: {
+											requestedByOID: 0,
+											requestedByUser: 0
+										}
+									}
+								],
+								"verifiedBy": [
+									{
+										$addFields: {
+											verifiedByOID: {
+												$convert: {
+													input: "$verifiedBy",
+													to: "objectId",
+													onError: "unknown",
+													onNull: "unknown"
+												}
+											}
+										}
+									},
+									{
+										$lookup: {
+											from: "users",
+											localField: "verifiedByOID",
+											foreignField: "_id",
+											as: "verifiedByUser"
+										}
+									},
+									{
+										$unwind: {
+											path: "$verifiedByUser",
+											preserveNullAndEmptyArrays: true
+										}
+									},
+									{
+										$addFields: {
+											verifiedByUsername: {
+												$ifNull: ["$verifiedByUser.username", "unknown"]
+											}
+										}
+									},
+									{
+										$project: {
+											verifiedByOID: 0,
+											verifiedByUser: 0
+										}
+									}
+								]
+							},
+							specialQueries: {
+								"requestedBy": newQuery => ({ $or: [newQuery, { requestedByUsername: newQuery.requestedBy }] }),
+								"verifiedBy": newQuery => ({ $or: [newQuery, { verifiedByUsername: newQuery.verifiedBy }] })
+							}
 						},
 						this
 					)

+ 54 - 2
backend/logic/actions/stations.js

@@ -654,7 +654,7 @@ export default {
 		async.waterfall(
 			[
 				next => {
-					StationsModule.runJob(
+					DBModule.runJob(
 						"GET_DATA",
 						{
 							page,
@@ -662,7 +662,59 @@ export default {
 							properties,
 							sort,
 							queries,
-							operator
+							operator,
+							modelName: "station",
+							blacklistedProperties: [],
+							specialProperties: {
+								"owner": [
+									{
+										$addFields: {
+											ownerOID: {
+												$convert: {
+													input: "$owner",
+													to: "objectId",
+													onError: "unknown",
+													onNull: "unknown"
+												}
+											}
+										}
+									},
+									{
+										$lookup: {
+											from: "users",
+											localField: "ownerOID",
+											foreignField: "_id",
+											as: "ownerUser"
+										}
+									},
+									{
+										$unwind: {
+											path: "$ownerUser",
+											preserveNullAndEmptyArrays: true
+										}
+									},
+									{
+										$addFields: {
+											ownerUsername: {
+												$cond: [
+													{ $eq: [{ $type: "$owner" }, "string"] },
+													{ $ifNull: ["$ownerUser.username", "unknown"] },
+													"none"
+												]
+											}
+										}
+									},
+									{
+										$project: {
+											ownerOID: 0,
+											ownerUser: 0
+										}
+									}
+								]
+							},
+							specialQueries: {
+								owner: newQuery => ({ $or: [newQuery, { ownerUsername: newQuery.owner }] })
+							}
 						},
 						this
 					)

+ 43 - 158
backend/logic/actions/users.js

@@ -215,169 +215,54 @@ export default {
 	 */
 	getData: isAdminRequired(async function getSet(session, page, pageSize, properties, sort, queries, operator, cb) {
 		const userModel = await DBModule.runJob("GET_MODEL", { modelName: "user" }, this);
-		const blacklistedProperties = [
-			"services.password.password",
-			"services.password.reset.code",
-			"services.password.reset.expires",
-			"services.password.set.code",
-			"services.password.set.expires",
-			"services.github.access_token",
-			"email.verificationToken"
-		];
 
 		async.waterfall(
 			[
-				// Creates pipeline array
-				next => next(null, []),
-
-				// If a query filter property is blacklisted throw error
-				(pipeline, next) => {
-					if (
-						queries.some(query =>
-							blacklistedProperties.some(blacklistedProperty =>
-								blacklistedProperty.startsWith(query.filter.property)
-							)
-						)
-					)
-						return next("Unable to filter by blacklisted property.");
-					if (
-						Object.keys(sort).some(property =>
-							blacklistedProperties.some(blacklistedProperty => blacklistedProperty.startsWith(property))
-						)
-					)
-						return next("Unable to sort by blacklisted property.");
-
-					return next(null, pipeline);
-				},
-
-				// If a filter or property exists for hasPassword, add hasPassword property to all documents
-				(pipeline, next) => {
-					// Check if a filter with the hasPassword property exists
-					const hasPasswordFilterExists =
-						queries.map(query => query.filter.property).indexOf("hasPassword") !== -1;
-					// Check if a property with the hasPassword property exists
-					const hasPasswordPropertyExists = properties.indexOf("hasPassword") !== -1;
-					// If no such filter or property exists, skip this function
-					if (!hasPasswordFilterExists && !hasPasswordPropertyExists) return next(null, pipeline);
-
-					// Adds hasPassword field, set it to true if services.password.password is a string, false if not
-					pipeline.push({
-						$addFields: {
-							hasPassword: {
-								$cond: [{ $eq: [{ $type: "$services.password.password" }, "string"] }, true, false]
+				next => {
+					DBModule.runJob(
+						"GET_DATA",
+						{
+							page,
+							pageSize,
+							properties,
+							sort,
+							queries,
+							operator,
+							modelName: "user",
+							blacklistedProperties: [
+								"services.password.password",
+								"services.password.reset.code",
+								"services.password.reset.expires",
+								"services.password.set.code",
+								"services.password.set.expires",
+								"services.github.access_token",
+								"email.verificationToken"
+							],
+							specialProperties: {
+								hasPassword: [
+									{
+										$addFields: {
+											hasPassword: {
+												$cond: [{ $eq: [{ $type: "$services.password.password" }, "string"] }, true, false]
+											}
+										}
+									}
+								]
+							},
+							specialQueries: {
 							}
-						}
-					});
-
-					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") {
-							newQuery[filter.property] = new RegExp(`${data.slice(1, data.length - 1)}`, "i");
-						} else if (filterType === "contains") {
-							newQuery[filter.property] = new RegExp(
-								`${data.replaceAll(/[.*+?^${}()|[\]\\]/g, "\\$&")}`,
-								"i"
-							);
-						} else if (filterType === "exact") {
-							newQuery[filter.property] = data.toString();
-						} else if (filterType === "datetimeBefore") {
-							newQuery[filter.property] = { $lte: new Date(data) };
-						} else if (filterType === "datetimeAfter") {
-							newQuery[filter.property] = { $gte: new Date(data) };
-						} else if (filterType === "numberLesserEqual") {
-							newQuery[filter.property] = { $lte: Number(data) };
-						} else if (filterType === "numberLesser") {
-							newQuery[filter.property] = { $lt: Number(data) };
-						} else if (filterType === "numberGreater") {
-							newQuery[filter.property] = { $gt: Number(data) };
-						} else if (filterType === "numberGreaterEqual") {
-							newQuery[filter.property] = { $gte: Number(data) };
-						} else if (filterType === "numberEquals") {
-							newQuery[filter.property] = { $eq: Number(data) };
-						} else if (filterType === "boolean") {
-							newQuery[filter.property] = { $eq: !!data };
-						}
-
-						return newQuery;
-					});
-					if (queryError) next(queryError);
-
-					const queryObject = {};
-					if (newQueries.length > 0) {
-						if (operator === "and") queryObject.$and = newQueries;
-						else if (operator === "or") queryObject.$or = newQueries;
-						else if (operator === "nor") queryObject.$nor = newQueries;
-					}
-
-					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);
-				},
-
-				// Adds second project stage to aggregation pipeline, responsible for excluding some specific properties
-				(pipeline, next) => {
-					pipeline.push({
-						$project: Object.fromEntries(blacklistedProperties.map(property => [property, 0]))
-					});
-
-					next(null, pipeline);
-				},
-
-				// 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);
-				},
-
-				// Executes the aggregation pipeline
-				(pipeline, next) => {
-					userModel.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);
-					});
+						},
+						this
+					)
+						.then(response => {
+							next(null, response);
+						})
+						.catch(err => {
+							next(err);
+						});
 				}
 			],
-			async (err, count, users) => {
+			async (err, response) => {
 				if (err && err !== true) {
 					err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
 					this.log("ERROR", "USERS_GET_DATA", `Failed to get data from users. "${err}"`);
@@ -387,7 +272,7 @@ export default {
 				return cb({
 					status: "success",
 					message: "Successfully got data from users.",
-					data: { data: users, count }
+					data: response
 				});
 			}
 		);

+ 205 - 0
backend/logic/db/index.js

@@ -308,6 +308,211 @@ class _DBModule extends CoreClass {
 		});
 	}
 
+	/**
+	 * Gets data
+	 *
+	 * @param {object} payload - object containing the payload
+	 * @param {string} payload.page - the page
+	 * @param {string} payload.pageSize - the page size
+	 * @param {string} payload.properties - the properties to return for each song
+	 * @param {string} payload.sort - the sort object
+	 * @param {string} payload.queries - the queries array
+	 * @param {string} payload.operator - the operator for queries
+	 * @param {string} payload.modelName - the db collection modal name
+	 * @param {string} payload.blacklistedProperties - the properties that are not allowed to be returned, filtered by or sorted by
+	 * @param {string} payload.specialProperties - the special properties
+	 * @param {string} payload.specialQueries - the special queries
+	 * @returns {Promise} - returns a promise (resolve, reject)
+	 */
+	GET_DATA(payload) {
+		return new Promise((resolve, reject) => {
+			async.waterfall(
+				[
+					// Creates pipeline array
+					next => next(null, []),
+
+					// If a query filter property or sort property is blacklisted, throw error
+					(pipeline, next) => {
+						const { sort, queries, blacklistedProperties } = payload;
+						if (
+							queries.some(query =>
+								blacklistedProperties.some(blacklistedProperty =>
+									blacklistedProperty.startsWith(query.filter.property)
+								)
+							)
+						)
+							return next("Unable to filter by blacklisted property.");
+						if (
+							Object.keys(sort).some(property =>
+								blacklistedProperties.some(blacklistedProperty => blacklistedProperty.startsWith(property))
+							)
+						)
+							return next("Unable to sort by blacklisted property.");
+
+						return next(null, pipeline);
+					},
+
+					// If a filter or property exists for a special property, add some custom pipeline steps
+					(pipeline, next) => {
+						const { properties, queries, specialProperties } = payload;
+
+						async.eachLimit(
+							Object.entries(specialProperties),
+							1,
+							([specialProperty, pipelineSteps], next) => {
+								// Check if a filter with the special property exists
+								const filterExists = queries.map(query => query.filter.property).indexOf(specialProperty) !== -1;
+								// Check if a property with the special property exists
+								const propertyExists = properties.indexOf(specialProperty) !== -1;
+								// If no such filter or property exists, skip this function
+								if (!filterExists && !propertyExists) return next();
+								// Add the specified pipeline steps into the pipeline
+								pipeline.push(...pipelineSteps);
+								return next();
+							},
+							err => {
+								next(err, pipeline);
+							}
+						);
+					},
+
+					// Adds the match stage to aggregation pipeline, which is responsible for filtering
+					(pipeline, next) => {
+						const { queries, operator, specialQueries } = payload;
+
+						let queryError;
+						const newQueries = queries.flatMap(query => {
+							const { data, filter, filterType } = query;
+							const newQuery = {};
+							if (filterType === "regex") {
+								newQuery[filter.property] = new RegExp(`${data.slice(1, data.length - 1)}`, "i");
+							} else if (filterType === "contains") {
+								newQuery[filter.property] = new RegExp(
+									`${data.replaceAll(/[.*+?^${}()|[\]\\]/g, "\\$&")}`,
+									"i"
+								);
+							} else if (filterType === "exact") {
+								newQuery[filter.property] = data.toString();
+							} else if (filterType === "datetimeBefore") {
+								newQuery[filter.property] = { $lte: new Date(data) };
+							} else if (filterType === "datetimeAfter") {
+								newQuery[filter.property] = { $gte: new Date(data) };
+							} else if (filterType === "numberLesserEqual") {
+								newQuery[filter.property] = { $lte: Number(data) };
+							} else if (filterType === "numberLesser") {
+								newQuery[filter.property] = { $lt: Number(data) };
+							} else if (filterType === "numberGreater") {
+								newQuery[filter.property] = { $gt: Number(data) };
+							} else if (filterType === "numberGreaterEqual") {
+								newQuery[filter.property] = { $gte: Number(data) };
+							} else if (filterType === "numberEquals") {
+								newQuery[filter.property] = { $eq: Number(data) };
+							} else if (filterType === "boolean") {
+								newQuery[filter.property] = { $eq: !!data };
+							}
+
+							if (specialQueries[filter.property]) {
+								return specialQueries[filter.property](newQuery);
+							}
+
+							return newQuery;
+						});
+						if (queryError) next(queryError);
+
+						const queryObject = {};
+						if (newQueries.length > 0) {
+							if (operator === "and") queryObject.$and = newQueries;
+							else if (operator === "or") queryObject.$or = newQueries;
+							else if (operator === "nor") queryObject.$nor = newQueries;
+						}
+
+						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 { sort } = payload;
+						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) => {
+						const { properties } = payload;
+
+						pipeline.push({ $project: Object.fromEntries(properties.map(property => [property, 1])) });
+
+						next(null, pipeline);
+					},
+
+					// Adds second project stage to aggregation pipeline, responsible for excluding some specific properties
+					(pipeline, next) => {
+						const { blacklistedProperties } = payload;
+						if (blacklistedProperties.length > 0)
+							pipeline.push({
+								$project: Object.fromEntries(blacklistedProperties.map(property => [property, 0]))
+							});
+
+						next(null, pipeline);
+					},
+
+					// 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) => {
+						const { page, pageSize } = payload;
+
+						pipeline.push({
+							$facet: {
+								count: [{ $count: "count" }],
+								documents: [{ $skip: pageSize * (page - 1) }, { $limit: pageSize }]
+							}
+						});
+
+						// console.dir(pipeline, { depth: 6 });
+
+						next(null, pipeline);
+					},
+
+					(pipeline, next) => {
+						const { modelName } = payload;
+
+						DBModule.runJob("GET_MODEL", { modelName }, this).then(model => {
+							if (!model) return next("Invalid model.");
+							return next(null, pipeline, model);
+						}).catch(err => {
+							next(err);
+						});
+					},
+
+					// Executes the aggregation pipeline
+					(pipeline, model, next) => {
+						model.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);
+						});
+					}
+				],
+				(err, count, documents) => {
+					if (err && err !== true) return reject(new Error(err));
+					return resolve({ data: documents, count });
+				}
+			);
+		});
+	}
+
 	/**
 	 * Checks if a password to be stored in the database has a valid length
 	 *

+ 0 - 243
backend/logic/playlists.js

@@ -863,249 +863,6 @@ class _PlaylistsModule extends CoreClass {
 		);
 	}
 
-	/**
-	 * Gets playlists data
-	 *
-	 * @param {object} payload - object containing the payload
-	 * @param {string} payload.page - the page
-	 * @param {string} payload.pageSize - the page size
-	 * @param {string} payload.properties - the properties to return for each playlist
-	 * @param {string} payload.sort - the sort object
-	 * @param {string} payload.queries - the queries array
-	 * @param {string} payload.operator - the operator for queries
-	 * @returns {Promise} - returns a promise (resolve, reject)
-	 */
-	GET_DATA(payload) {
-		return new Promise((resolve, reject) => {
-			async.waterfall(
-				[
-					// Creates pipeline array
-					next => next(null, []),
-
-					// If a filter or property exists for totalLength, add totalLength property to all documents
-					(pipeline, next) => {
-						const { queries, properties } = payload;
-
-						// Check if a filter with the totalLength property exists
-						const totalLengthFilterExists =
-							queries.map(query => query.filter.property).indexOf("totalLength") !== -1;
-						// Check if a property with the totalLength property exists
-						const totalLengthPropertyExists = properties.indexOf("totalLength") !== -1;
-						// If no such filter or property exists, skip this function
-						if (!totalLengthFilterExists && !totalLengthPropertyExists) return next(null, pipeline);
-
-						// Adds totalLength field which is the sum of all durations of all songs in the songs array
-						pipeline.push({
-							$addFields: {
-								totalLength: { $sum: "$songs.duration" }
-							}
-						});
-
-						return next(null, pipeline);
-					},
-
-					// If a filter or property exists for songsCount, add songsCount property to all documents
-					(pipeline, next) => {
-						const { queries, properties } = payload;
-
-						// Check if a filter with the songsCount property exists
-						const songsCountFilterExists =
-							queries.map(query => query.filter.property).indexOf("songsCount") !== -1;
-						// Check if a property with the songsCount property exists
-						const songsCountPropertyExists = properties.indexOf("songsCount") !== -1;
-						// If no such filter or property exists, skip this function
-						if (!songsCountFilterExists && !songsCountPropertyExists) return next(null, pipeline);
-
-						// Adds songsCount field which is the length of the songs array
-						pipeline.push({
-							$addFields: {
-								songsCount: { $size: "$songs" }
-							}
-						});
-
-						return next(null, pipeline);
-					},
-
-					// If a filter exists for createdBy, add createdByUsername property to all documents
-					(pipeline, next) => {
-						const { queries } = payload;
-
-						// Check if a filter with the createdBy property exists
-						const createdByFilterExists =
-							queries.map(query => query.filter.property).indexOf("createdBy") !== -1;
-						// If no such filter exists, skip this function
-						if (!createdByFilterExists) return next(null, pipeline);
-
-						// Adds createdByOID field, which is an ObjectId version of createdBy
-						pipeline.push({
-							$addFields: {
-								createdByOID: {
-									$convert: {
-										input: "$createdBy",
-										to: "objectId",
-										onError: "unknown",
-										onNull: "unknown"
-									}
-								}
-							}
-						});
-
-						// Looks up user(s) with the same _id as the createdByOID and puts the result in the createdByUser field
-						pipeline.push({
-							$lookup: {
-								from: "users",
-								localField: "createdByOID",
-								foreignField: "_id",
-								as: "createdByUser"
-							}
-						});
-
-						// Unwinds the createdByUser array field into an object
-						pipeline.push({
-							$unwind: {
-								path: "$createdByUser",
-								preserveNullAndEmptyArrays: true
-							}
-						});
-
-						// Adds createdByUsername field from the createdByUser username, or unknown if it doesn't exist, or Musare if it's set to Musare
-						pipeline.push({
-							$addFields: {
-								createdByUsername: {
-									$cond: [
-										{ $eq: ["$createdBy", "Musare"] },
-										"Musare",
-										{ $ifNull: ["$createdByUser.username", "unknown"] }
-									]
-								}
-							}
-						});
-
-						// Removes the createdByOID and createdByUser property, just in case it doesn't get removed at a later stage
-						pipeline.push({
-							$project: {
-								createdByOID: 0,
-								createdByUser: 0
-							}
-						});
-
-						return next(null, pipeline);
-					},
-
-					// Adds the match stage to aggregation pipeline, which is responsible for filtering
-					(pipeline, next) => {
-						const { queries, operator } = payload;
-
-						let queryError;
-						const newQueries = queries.flatMap(query => {
-							const { data, filter, filterType } = query;
-							const newQuery = {};
-							if (filterType === "regex") {
-								newQuery[filter.property] = new RegExp(`${data.slice(1, data.length - 1)}`, "i");
-							} else if (filterType === "contains") {
-								newQuery[filter.property] = new RegExp(
-									`${data.replaceAll(/[.*+?^${}()|[\]\\]/g, "\\$&")}`,
-									"i"
-								);
-							} else if (filterType === "exact") {
-								newQuery[filter.property] = data.toString();
-							} else if (filterType === "datetimeBefore") {
-								newQuery[filter.property] = { $lte: new Date(data) };
-							} else if (filterType === "datetimeAfter") {
-								newQuery[filter.property] = { $gte: new Date(data) };
-							} else if (filterType === "numberLesserEqual") {
-								newQuery[filter.property] = { $lte: Number(data) };
-							} else if (filterType === "numberLesser") {
-								newQuery[filter.property] = { $lt: Number(data) };
-							} else if (filterType === "numberGreater") {
-								newQuery[filter.property] = { $gt: Number(data) };
-							} else if (filterType === "numberGreaterEqual") {
-								newQuery[filter.property] = { $gte: Number(data) };
-							} else if (filterType === "numberEquals") {
-								newQuery[filter.property] = { $eq: Number(data) };
-							} else if (filterType === "boolean") {
-								newQuery[filter.property] = { $eq: !!data };
-							}
-
-							if (filter.property === "createdBy")
-								return { $or: [newQuery, { createdByUsername: newQuery.createdBy }] };
-
-							return newQuery;
-						});
-						if (queryError) next(queryError);
-
-						const queryObject = {};
-						if (newQueries.length > 0) {
-							if (operator === "and") queryObject.$and = newQueries;
-							else if (operator === "or") queryObject.$or = newQueries;
-							else if (operator === "nor") queryObject.$nor = newQueries;
-						}
-
-						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 { sort } = payload;
-						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) => {
-						const { properties } = payload;
-
-						pipeline.push({ $project: Object.fromEntries(properties.map(property => [property, 1])) });
-
-						next(null, pipeline);
-					},
-
-					// 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) => {
-						const { page, pageSize } = payload;
-
-						pipeline.push({
-							$facet: {
-								count: [{ $count: "count" }],
-								documents: [{ $skip: pageSize * (page - 1) }, { $limit: pageSize }]
-							}
-						});
-
-						// console.dir(pipeline, { depth: 6 });
-
-						next(null, pipeline);
-					},
-
-					// Executes the aggregation pipeline
-					(pipeline, next) => {
-						PlaylistsModule.playlistModel.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);
-						});
-					}
-				],
-				(err, count, playlists) => {
-					if (err && err !== true) return reject(new Error(err));
-					return resolve({ data: playlists, count });
-				}
-			);
-		});
-	}
-
 	/**
 	 * Gets a playlist from id from Mongo and updates the cache with it
 	 *

+ 0 - 270
backend/logic/songs.js

@@ -205,276 +205,6 @@ class _SongsModule extends CoreClass {
 		);
 	}
 
-	/**
-	 * Gets songs data
-	 *
-	 * @param {object} payload - object containing the payload
-	 * @param {string} payload.page - the page
-	 * @param {string} payload.pageSize - the page size
-	 * @param {string} payload.properties - the properties to return for each song
-	 * @param {string} payload.sort - the sort object
-	 * @param {string} payload.queries - the queries array
-	 * @param {string} payload.operator - the operator for queries
-	 * @returns {Promise} - returns a promise (resolve, reject)
-	 */
-	GET_DATA(payload) {
-		return new Promise((resolve, reject) => {
-			async.waterfall(
-				[
-					// Creates pipeline array
-					next => next(null, []),
-
-					// If a filter exists for requestedBy, add requestedByUsername property to all documents
-					(pipeline, next) => {
-						const { queries } = payload;
-
-						// Check if a filter with the requestedBy property exists
-						const requestedByFilterExists =
-							queries.map(query => query.filter.property).indexOf("requestedBy") !== -1;
-						// If no such filter exists, skip this function
-						if (!requestedByFilterExists) return next(null, pipeline);
-
-						// Adds requestedByOID field, which is an ObjectId version of requestedBy
-						pipeline.push({
-							$addFields: {
-								requestedByOID: {
-									$convert: {
-										input: "$requestedBy",
-										to: "objectId",
-										onError: "unknown",
-										onNull: "unknown"
-									}
-								}
-							}
-						});
-
-						// Looks up user(s) with the same _id as the requestedByOID and puts the result in the requestedByUser field
-						pipeline.push({
-							$lookup: {
-								from: "users",
-								localField: "requestedByOID",
-								foreignField: "_id",
-								as: "requestedByUser"
-							}
-						});
-
-						// Unwinds the requestedByUser array field into an object
-						pipeline.push({
-							$unwind: {
-								path: "$requestedByUser",
-								preserveNullAndEmptyArrays: true
-							}
-						});
-
-						// Adds requestedByUsername field from the requestedByUser username, or unknown if it doesn't exist
-						pipeline.push({
-							$addFields: {
-								requestedByUsername: {
-									$ifNull: ["$requestedByUser.username", "unknown"]
-								}
-							}
-						});
-
-						// Removes the requestedByOID and requestedByUser property, just in case it doesn't get removed at a later stage
-						pipeline.push({
-							$project: {
-								requestedByOID: 0,
-								requestedByUser: 0
-							}
-						});
-
-						return next(null, pipeline);
-					},
-
-					// If a filter exists for createdBy, add createdByUsername property to all documents
-					(pipeline, next) => {
-						const { queries } = payload;
-
-						// Check if a filter with the createdBy property exists
-						const createdByFilterExists =
-							queries.map(query => query.filter.property).indexOf("createdBy") !== -1;
-						// If no such filter exists, skip this function
-						if (!createdByFilterExists) return next(null, pipeline);
-
-						// Adds createdByOID field, which is an ObjectId version of createdBy
-						pipeline.push({
-							$addFields: {
-								createdByOID: {
-									$convert: {
-										input: "$createdBy",
-										to: "objectId",
-										onError: "unknown",
-										onNull: "unknown"
-									}
-								}
-							}
-						});
-
-						// Looks up user(s) with the same _id as the createdByOID and puts the result in the createdByUser field
-						pipeline.push({
-							$lookup: {
-								from: "users",
-								localField: "createdByOID",
-								foreignField: "_id",
-								as: "createdByUser"
-							}
-						});
-
-						// Unwinds the createdByUser array field into an object
-						pipeline.push({
-							$unwind: {
-								path: "$createdByUser",
-								preserveNullAndEmptyArrays: true
-							}
-						});
-
-						// Adds createdByUsername field from the createdByUser username, or unknown if it doesn't exist
-						pipeline.push({
-							$addFields: {
-								createdByUsername: {
-									$ifNull: ["$createdByUser.username", "unknown"]
-								}
-							}
-						});
-
-						// Removes the createdByOID and createdByUser property, just in case it doesn't get removed at a later stage
-						pipeline.push({
-							$project: {
-								createdByOID: 0,
-								createdByUser: 0
-							}
-						});
-
-						return next(null, pipeline);
-					},
-
-					// Adds the match stage to aggregation pipeline, which is responsible for filtering
-					(pipeline, next) => {
-						const { queries, operator } = payload;
-
-						let queryError;
-						const newQueries = queries.flatMap(query => {
-							const { data, filter, filterType } = query;
-							const newQuery = {};
-							if (filterType === "regex") {
-								newQuery[filter.property] = new RegExp(`${data.slice(1, data.length - 1)}`, "i");
-							} else if (filterType === "contains") {
-								newQuery[filter.property] = new RegExp(
-									`${data.replaceAll(/[.*+?^${}()|[\]\\]/g, "\\$&")}`,
-									"i"
-								);
-							} else if (filterType === "exact") {
-								newQuery[filter.property] = data.toString();
-							} else if (filterType === "datetimeBefore") {
-								newQuery[filter.property] = { $lte: new Date(data) };
-							} else if (filterType === "datetimeAfter") {
-								newQuery[filter.property] = { $gte: new Date(data) };
-							} else if (filterType === "numberLesserEqual") {
-								newQuery[filter.property] = { $lte: Number(data) };
-							} else if (filterType === "numberLesser") {
-								newQuery[filter.property] = { $lt: Number(data) };
-							} else if (filterType === "numberGreater") {
-								newQuery[filter.property] = { $gt: Number(data) };
-							} else if (filterType === "numberGreaterEqual") {
-								newQuery[filter.property] = { $gte: Number(data) };
-							} else if (filterType === "numberEquals") {
-								newQuery[filter.property] = { $eq: Number(data) };
-							} else if (filterType === "boolean") {
-								newQuery[filter.property] = { $eq: !!data };
-							}
-
-							if (filter.property === "requestedBy")
-								return { $or: [newQuery, { requestedByUsername: newQuery.requestedBy }] };
-							if (filter.property === "createdBy")
-								return { $or: [newQuery, { createdByUsername: newQuery.createdBy }] };
-
-							return newQuery;
-						});
-						if (queryError) next(queryError);
-
-						const queryObject = {};
-						if (newQueries.length > 0) {
-							if (operator === "and") queryObject.$and = newQueries;
-							else if (operator === "or") queryObject.$or = newQueries;
-							else if (operator === "nor") queryObject.$nor = newQueries;
-						}
-
-						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 { sort } = payload;
-						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) => {
-						const { properties } = payload;
-
-						pipeline.push({ $project: Object.fromEntries(properties.map(property => [property, 1])) });
-
-						next(null, pipeline);
-					},
-
-					// // Adds second project stage to aggregation pipeline, responsible for excluding some specific properties
-					// (pipeline, next) => {
-					// 	pipeline.push({
-					// 		$project: {
-					// 			title: 0
-					// 		}
-					// 	});
-
-					// 	next(null, pipeline);
-					// },
-
-					// 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) => {
-						const { page, pageSize } = payload;
-
-						pipeline.push({
-							$facet: {
-								count: [{ $count: "count" }],
-								documents: [{ $skip: pageSize * (page - 1) }, { $limit: pageSize }]
-							}
-						});
-
-						// console.dir(pipeline, { depth: 6 });
-
-						next(null, pipeline);
-					},
-
-					// Executes the aggregation pipeline
-					(pipeline, next) => {
-						SongsModule.SongModel.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);
-						});
-					}
-				],
-				(err, count, songs) => {
-					if (err && err !== true) return reject(new Error(err));
-					return resolve({ data: songs, count });
-				}
-			);
-		});
-	}
-
 	/**
 	 * Makes sure that if a song is not currently in the songs db, to add it
 	 *

+ 0 - 198
backend/logic/stations.js

@@ -391,204 +391,6 @@ class _StationsModule extends CoreClass {
 		);
 	}
 
-	/**
-	 * Gets stations data
-	 *
-	 * @param {object} payload - object containing the payload
-	 * @param {string} payload.page - the page
-	 * @param {string} payload.pageSize - the page size
-	 * @param {string} payload.properties - the properties to return for each station
-	 * @param {string} payload.sort - the sort object
-	 * @param {string} payload.queries - the queries array
-	 * @param {string} payload.operator - the operator for queries
-	 * @returns {Promise} - returns a promise (resolve, reject)
-	 */
-	GET_DATA(payload) {
-		return new Promise((resolve, reject) => {
-			async.waterfall(
-				[
-					// Creates pipeline array
-					next => next(null, []),
-
-					// If a filter exists for owner, add ownerUsername property to all documents
-					(pipeline, next) => {
-						const { queries } = payload;
-
-						// Check if a filter with the owner property exists
-						const ownerFilterExists = queries.map(query => query.filter.property).indexOf("owner") !== -1;
-						// If no such filter exists, skip this function
-						if (!ownerFilterExists) return next(null, pipeline);
-
-						// Adds ownerOID field, which is an ObjectId version of owner
-						pipeline.push({
-							$addFields: {
-								ownerOID: {
-									$convert: {
-										input: "$owner",
-										to: "objectId",
-										onError: "unknown",
-										onNull: "unknown"
-									}
-								}
-							}
-						});
-
-						// Looks up user(s) with the same _id as the ownerOID and puts the result in the ownerUser field
-						pipeline.push({
-							$lookup: {
-								from: "users",
-								localField: "ownerOID",
-								foreignField: "_id",
-								as: "ownerUser"
-							}
-						});
-
-						// Unwinds the ownerUser array field into an object
-						pipeline.push({
-							$unwind: {
-								path: "$ownerUser",
-								preserveNullAndEmptyArrays: true
-							}
-						});
-
-						// Adds ownerUsername field from the ownerUser username, if owner doesn't exist then it's none, or if user/username doesn't exist then it's unknown
-						pipeline.push({
-							$addFields: {
-								ownerUsername: {
-									$cond: [
-										{ $eq: [{ $type: "$owner" }, "string"] },
-										{ $ifNull: ["$ownerUser.username", "unknown"] },
-										"none"
-									]
-								}
-							}
-						});
-
-						// Removes the ownerOID and ownerUser property, just in case it doesn't get removed at a later stage
-						pipeline.push({
-							$project: {
-								ownerOID: 0,
-								ownerUser: 0
-							}
-						});
-
-						return next(null, pipeline);
-					},
-
-					// Adds the match stage to aggregation pipeline, which is responsible for filtering
-					(pipeline, next) => {
-						const { queries, operator } = payload;
-
-						let queryError;
-						const newQueries = queries.flatMap(query => {
-							const { data, filter, filterType } = query;
-							const newQuery = {};
-							if (filterType === "regex") {
-								newQuery[filter.property] = new RegExp(`${data.slice(1, data.length - 1)}`, "i");
-							} else if (filterType === "contains") {
-								newQuery[filter.property] = new RegExp(
-									`${data.replaceAll(/[.*+?^${}()|[\]\\]/g, "\\$&")}`,
-									"i"
-								);
-							} else if (filterType === "exact") {
-								newQuery[filter.property] = data.toString();
-							} else if (filterType === "datetimeBefore") {
-								newQuery[filter.property] = { $lte: new Date(data) };
-							} else if (filterType === "datetimeAfter") {
-								newQuery[filter.property] = { $gte: new Date(data) };
-							} else if (filterType === "numberLesserEqual") {
-								newQuery[filter.property] = { $lte: Number(data) };
-							} else if (filterType === "numberLesser") {
-								newQuery[filter.property] = { $lt: Number(data) };
-							} else if (filterType === "numberGreater") {
-								newQuery[filter.property] = { $gt: Number(data) };
-							} else if (filterType === "numberGreaterEqual") {
-								newQuery[filter.property] = { $gte: Number(data) };
-							} else if (filterType === "numberEquals") {
-								newQuery[filter.property] = { $eq: Number(data) };
-							} else if (filterType === "boolean") {
-								newQuery[filter.property] = { $eq: !!data };
-							}
-
-							if (filter.property === "owner")
-								return { $or: [newQuery, { ownerUsername: newQuery.owner }] };
-
-							return newQuery;
-						});
-						if (queryError) next(queryError);
-
-						const queryObject = {};
-						if (newQueries.length > 0) {
-							if (operator === "and") queryObject.$and = newQueries;
-							else if (operator === "or") queryObject.$or = newQueries;
-							else if (operator === "nor") queryObject.$nor = newQueries;
-						}
-
-						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 { sort } = payload;
-						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) => {
-						const { properties } = payload;
-
-						pipeline.push({ $project: Object.fromEntries(properties.map(property => [property, 1])) });
-
-						next(null, pipeline);
-					},
-
-					// 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) => {
-						const { page, pageSize } = payload;
-
-						pipeline.push({
-							$facet: {
-								count: [{ $count: "count" }],
-								documents: [{ $skip: pageSize * (page - 1) }, { $limit: pageSize }]
-							}
-						});
-
-						// console.dir(pipeline, { depth: 6 });
-
-						next(null, pipeline);
-					},
-
-					// Executes the aggregation pipeline
-					(pipeline, next) => {
-						StationsModule.stationModel.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);
-						});
-					}
-				],
-				(err, count, stations) => {
-					if (err && err !== true) return reject(new Error(err));
-					return resolve({ data: stations, count });
-				}
-			);
-		});
-	}
-
 	/**
 	 * Updates the station in cache from mongo or deletes station in cache if no longer in mongo.
 	 *