Browse Source

refactor(SongsModule/GET_SONGS): replaced query with aggregation

Kristian Vos 3 years ago
parent
commit
729b9f6a5a
1 changed files with 118 additions and 17 deletions
  1. 118 17
      backend/logic/songs.js

+ 118 - 17
backend/logic/songs.js

@@ -222,11 +222,63 @@ class _SongsModule extends CoreClass {
 		return new Promise((resolve, reject) => {
 			async.waterfall(
 				[
-					next => {
+					// 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"
+						} });
+
+						// Adds requestedByUsername field from the requestedByUser username
+						pipeline.push({ $addFields: {
+							requestedByUsername: "$requestedByUser.username"
+						} });
+
+						// 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);
+					},
+
+					// Adds the match stage to aggregation pipeline, which is responsible for filtering
+					(pipeline, next) => {
 						const { queries, operator } = payload;
 
 						let queryError;
-						const newQueries = queries.map(query => {
+						const newQueries = queries.flatMap(query => {
 							const { data, filter, filterType } = query;
 							const newQuery = {};
 							if (filterType === "regex") {
@@ -253,6 +305,9 @@ class _SongsModule extends CoreClass {
 							} else if (filterType === "numberEquals") {
 								newQuery[filter.property] = { $eq: data };
 							}
+
+							if (filter.property === "requestedBy") return { $or: [ newQuery, { requestedByUsername: newQuery["requestedBy"] } ] };
+
 							return newQuery;
 						});
 						if (queryError) next(queryError);
@@ -264,26 +319,72 @@ class _SongsModule extends CoreClass {
 							else if (operator === "nor") queryObject.$nor = newQueries;
 						}
 
-						next(null, queryObject);
+						pipeline.push({ $match: queryObject });
+
+						next(null, pipeline);
 					},
 
-					(queryObject, next) => {
-						SongsModule.SongModel.find(queryObject).count((err, count) => {
-							next(err, queryObject, count);
-						});
+					// 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);
 					},
 
-					(queryObject, count, next) => {
-						const { page, pageSize, properties, sort } = payload;
+					// Adds first project stage to aggregation pipeline, responsible for including only the requested properties
+					(pipeline, next) => {
+						const { properties } = payload;
 
-						SongsModule.SongModel.find(queryObject)
-							.sort(sort)
-							.skip(pageSize * (page - 1))
-							.limit(pageSize)
-							.select(properties.join(" "))
-							.exec((err, songs) => {
-								next(err, count, songs);
-							});
+						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.log(pipeline);
+
+						next(null, pipeline);
+					},
+
+					// Executes the aggregation pipeline
+					(pipeline, next) => {
+						SongsModule.SongModel.aggregate(
+							pipeline
+						).exec((err, result) => {
+							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) => {