Browse Source

Merge branch 'master' into staging

Owen Diffey 2 years ago
parent
commit
1147a2d831

+ 18 - 0
CHANGELOG.md

@@ -1,5 +1,23 @@
 # Changelog
 
+## [v3.5.1] - 2022-05-06
+
+Upgrade instructions can be found at [.wiki/Upgrading](.wiki/Upgrading.md).
+
+### Fixed
+- fix: Songs requestSet could return null songs
+- fix: Prevent adding duplicate items with bulk actions
+- fix: Throw error if unknown job is called
+- fix: EditSongs missing modalUuid socker parameter
+- fix: Unable to focus/blur AdvancedTable rows
+- fix: EditSong song duration can reset on video state change
+- fix: Backend exception if an empty playlist is updated
+- fix: False-positive like/dislike playlist activity
+- fix: Removed debug console.log
+- fix: Station current/next song items requestedAt not updated on song changing
+- fix: AdvancedTable max-width not a number
+- fix: AdvancedTable history.replaceState throws warning
+
 ## [v3.5.0] - 2022-04-28
 
 This release includes all changes from v3.5.0-rc1 and v3.5.0-rc2, in addition to the following. Upgrade instructions can be found at [.wiki/Upgrading](.wiki/Upgrading.md).

+ 79 - 76
backend/core.js

@@ -550,84 +550,87 @@ export default class CoreClass {
 
 			if (previousStatus === "QUEUED") {
 				if (!options.isQuiet) this.log("INFO", `Job ${job.name} (${job.toString()}) is queued, so calling it`);
-				// TODO check if job exists
-				this[job.name]
-					.apply(job, [job.payload])
-					.then(response => {
-						if (!options.isQuiet) this.log("INFO", `Ran job ${job.name} (${job.toString()}) successfully`);
-						job.setStatus("FINISHED");
-						job.setResponse(response);
-						this.jobStatistics[job.name].successful += 1;
-						job.setResponseType("RESOLVE");
-						if (
-							config.debug &&
-							config.debug.stationIssue === true &&
-							config.debug.captureJobs &&
-							config.debug.captureJobs.indexOf(job.name) !== -1
-						) {
-							this.moduleManager.debugJobs.completed.push({
-								status: "success",
-								job,
-								priority: job.task.priority,
-								response
-							});
-						}
-					})
-					.catch(error => {
-						this.log("INFO", `Running job ${job.name} (${job.toString()}) failed`);
-						job.setStatus("FINISHED");
-						job.setResponse(error);
-						job.setResponseType("REJECT");
-						this.jobStatistics[job.name].failed += 1;
-						if (
-							config.debug &&
-							config.debug.stationIssue === true &&
-							config.debug.captureJobs &&
-							config.debug.captureJobs.indexOf(job.name) !== -1
-						) {
-							this.moduleManager.debugJobs.completed.push({
-								status: "error",
-								job,
-								error
-							});
-						}
-					})
-					.finally(() => {
-						const endTime = Date.now();
-						const executionTime = endTime - startTime;
-						this.jobStatistics[job.name].total += 1;
-						this.jobStatistics[job.name].averageTiming.update(executionTime);
-						this.moduleManager.jobManager.removeJob(job);
-						job.cleanup();
-
-						if (!job.parentJob) {
-							if (job.responseType === "RESOLVE") {
-								job.onFinish.resolve(job.response);
-								job.responseType = "RESOLVED";
-							} else if (job.responseType === "REJECT") {
-								job.onFinish.reject(job.response);
-								job.responseType = "REJECTED";
+
+				if (this[job.name])
+					this[job.name]
+						.apply(job, [job.payload])
+						.then(response => {
+							if (!options.isQuiet) this.log("INFO", `Ran job ${job.name} (${job.toString()}) successfully`);
+							job.setStatus("FINISHED");
+							job.setResponse(response);
+							this.jobStatistics[job.name].successful += 1;
+							job.setResponseType("RESOLVE");
+							if (
+								config.debug &&
+								config.debug.stationIssue === true &&
+								config.debug.captureJobs &&
+								config.debug.captureJobs.indexOf(job.name) !== -1
+							) {
+								this.moduleManager.debugJobs.completed.push({
+									status: "success",
+									job,
+									priority: job.task.priority,
+									response
+								});
+							}
+						})
+						.catch(error => {
+							this.log("INFO", `Running job ${job.name} (${job.toString()}) failed`);
+							job.setStatus("FINISHED");
+							job.setResponse(error);
+							job.setResponseType("REJECT");
+							this.jobStatistics[job.name].failed += 1;
+							if (
+								config.debug &&
+								config.debug.stationIssue === true &&
+								config.debug.captureJobs &&
+								config.debug.captureJobs.indexOf(job.name) !== -1
+							) {
+								this.moduleManager.debugJobs.completed.push({
+									status: "error",
+									job,
+									error
+								});
 							}
-						} else if (
-							job.parentJob &&
-							job.parentJob.childJobs.find(childJob =>
-								childJob ? childJob.status !== "FINISHED" : true
-							) === undefined
-						) {
-							if (job.parentJob.status !== "WAITING_ON_CHILD_JOB") {
-								this.log(
-									"ERROR",
-									`Job ${
-										job.parentJob.name
-									} (${job.parentJob.toString()}) had a child job complete even though it is not waiting on a child job. This should never happen.`
-								);
-							} else {
-								job.parentJob.setStatus("REQUEUED");
-								job.parentJob.module.jobQueue.resumeRunningJob(job.parentJob);
+						})
+						.finally(() => {
+							const endTime = Date.now();
+							const executionTime = endTime - startTime;
+							this.jobStatistics[job.name].total += 1;
+							this.jobStatistics[job.name].averageTiming.update(executionTime);
+							this.moduleManager.jobManager.removeJob(job);
+							job.cleanup();
+
+							if (!job.parentJob) {
+								if (job.responseType === "RESOLVE") {
+									job.onFinish.resolve(job.response);
+									job.responseType = "RESOLVED";
+								} else if (job.responseType === "REJECT") {
+									job.onFinish.reject(job.response);
+									job.responseType = "REJECTED";
+								}
+							} else if (
+								job.parentJob &&
+								job.parentJob.childJobs.find(childJob =>
+									childJob ? childJob.status !== "FINISHED" : true
+								) === undefined
+							) {
+								if (job.parentJob.status !== "WAITING_ON_CHILD_JOB") {
+									this.log(
+										"ERROR",
+										`Job ${
+											job.parentJob.name
+										} (${job.parentJob.toString()}) had a child job complete even though it is not waiting on a child job. This should never happen.`
+									);
+								} else {
+									job.parentJob.setStatus("REQUEUED");
+									job.parentJob.module.jobQueue.resumeRunningJob(job.parentJob);
+								}
 							}
-						}
-						resolve();
-					});
+							resolve();
+						});
+				else
+					this.log("ERROR", `Job not found! ${job.name}`)
 			} else {
 				this.log(
 					"INFO",

+ 11 - 6
backend/logic/actions/playlists.js

@@ -244,9 +244,12 @@ CacheModule.runJob("SUB", {
 				const newPlaylist = {
 					...playlist._doc,
 					songsCount: playlist.songs.length,
-					songsLength: playlist.songs.reduce((previous, current) => ({
-						duration: previous.duration + current.duration
-					})).duration
+					songsLength: playlist.songs.reduce(
+						(previous, current) => ({
+							duration: previous.duration + current.duration
+						}),
+						{ duration: 0 }
+					).duration
 				};
 				delete newPlaylist.songs;
 				WSModule.runJob("EMIT_TO_ROOMS", {
@@ -1511,9 +1514,11 @@ export default {
 
 				// update cache representation of the playlist
 				(res, next) => {
-					PlaylistsModule.runJob("UPDATE_PLAYLIST", { playlistId }, this)
-						.then(playlist => next(null, playlist))
-						.catch(next);
+					if (res.modifiedCount === 1)
+						PlaylistsModule.runJob("UPDATE_PLAYLIST", { playlistId }, this)
+							.then(playlist => next(null, playlist))
+							.catch(next);
+					else next("Song wasn't in playlist.");
 				},
 
 				(playlist, next) => {

+ 7 - 8
backend/logic/actions/songs.js

@@ -1191,7 +1191,6 @@ export default {
 									else failed += 1;
 									if (res.message === "This song is already in the database.") alreadyInDatabase += 1;
 									if (res.song) songs[index] = res.song;
-									else songs[index] = null;
 								})
 								.catch(() => {
 									failed += 1;
@@ -1272,7 +1271,7 @@ export default {
 							this
 						)
 						.then(res => {
-							if (res.status === "error")
+							if (res.status === "error" && res.message !== "Song wasn't in playlist.")
 								return next("Unable to remove song from the 'Disliked Songs' playlist.");
 							return next(null, song, user.likedSongsPlaylist);
 						})
@@ -1390,7 +1389,7 @@ export default {
 							this
 						)
 						.then(res => {
-							if (res.status === "error")
+							if (res.status === "error" && res.message !== "Song wasn't in playlist.")
 								return next("Unable to remove song from the 'Liked Songs' playlist.");
 							return next(null, song, user.dislikedSongsPlaylist);
 						})
@@ -1528,7 +1527,7 @@ export default {
 							this
 						)
 						.then(res => {
-							if (res.status === "error")
+							if (res.status === "error" && res.message !== "Song wasn't in playlist.")
 								return next("Unable to remove song from the 'Liked Songs' playlist.");
 							return next(null, song);
 						})
@@ -1625,7 +1624,7 @@ export default {
 							this
 						)
 						.then(res => {
-							if (res.status === "error")
+							if (res.status === "error" && res.message !== "Song wasn't in playlist.")
 								return next("Unable to remove song from the 'Disliked Songs' playlist.");
 							return next(null, song, user.likedSongsPlaylist);
 						})
@@ -1893,7 +1892,7 @@ export default {
 				(songsFound, next) => {
 					const query = {};
 					if (method === "add") {
-						query.$push = { genres: { $each: genres } };
+						query.$addToSet = { genres: { $each: genres } };
 					} else if (method === "remove") {
 						query.$pullAll = { genres };
 					} else if (method === "replace") {
@@ -1991,7 +1990,7 @@ export default {
 				(songsFound, next) => {
 					const query = {};
 					if (method === "add") {
-						query.$push = { artists: { $each: artists } };
+						query.$addToSet = { artists: { $each: artists } };
 					} else if (method === "remove") {
 						query.$pullAll = { artists };
 					} else if (method === "replace") {
@@ -2089,7 +2088,7 @@ export default {
 				(songsFound, next) => {
 					const query = {};
 					if (method === "add") {
-						query.$push = { tags: { $each: tags } };
+						query.$addToSet = { tags: { $each: tags } };
 					} else if (method === "remove") {
 						query.$pullAll = { tags };
 					} else if (method === "replace") {

+ 0 - 1
backend/logic/stations.js

@@ -657,7 +657,6 @@ class _StationsModule extends CoreClass {
 					}
 				],
 				(err, song) => {
-					if (err) console.log(33333, err, payload);
 					if (err) reject(err);
 					else resolve({ song });
 				}

+ 11 - 3
frontend/src/components/AdvancedTable.vue

@@ -1535,7 +1535,9 @@ export default {
 			this.highlightRow(newItemIndex);
 		},
 		highlightRow(itemIndex) {
-			const rowElement = this.$refs[`row-${itemIndex}`];
+			const rowElement = this.$refs[`row-${itemIndex}`]
+				? this.$refs[`row-${itemIndex}`][0]
+				: null;
 			// Set the last clicked item to no longer be highlighted, if it exists
 			if (this.lastSelectedItemIndex >= 0)
 				this.rows[this.lastSelectedItemIndex].highlighted = false;
@@ -1547,7 +1549,9 @@ export default {
 			this.rows[itemIndex].highlighted = true;
 		},
 		unhighlightRow(itemIndex) {
-			const rowElement = this.$refs[`row-${itemIndex}`];
+			const rowElement = this.$refs[`row-${itemIndex}`]
+				? this.$refs[`row-${itemIndex}`][0]
+				: null;
 			if (rowElement)
 				this.$nextTick(() => {
 					rowElement.blur();
@@ -1900,7 +1904,11 @@ export default {
 				.map(key => `${key}=${queryObject[key]}`)
 				.join("&")}`;
 
-			window.history.replaceState(null, null, queryString);
+			window.history.replaceState(
+				window.history.state,
+				null,
+				queryString
+			);
 		},
 		setLocalStorage() {
 			localStorage.setItem(

+ 66 - 32
frontend/src/components/modals/EditSong/index.vue

@@ -1104,12 +1104,22 @@ export default {
 					if (currentTime !== undefined)
 						this.youtubeVideoCurrentTime = currentTime.toFixed(3);
 
-					if (this.youtubeVideoDuration === "0.000") {
+					if (this.youtubeVideoDuration.indexOf(".000") !== -1) {
 						const duration = this.video.player.getDuration();
 
 						if (duration !== undefined) {
+							if (
+								`${this.youtubeVideoDuration}` ===
+								`${Number(this.song.duration).toFixed(3)}`
+							)
+								this.song.duration = duration.toFixed(3);
+
 							this.youtubeVideoDuration = duration.toFixed(3);
-							this.youtubeVideoNote = "(~)";
+							if (
+								this.youtubeVideoDuration.indexOf(".000") !== -1
+							)
+								this.youtubeVideoNote = "(~)";
+							else this.youtubeVideoNote = "";
 
 							this.drawCanvas();
 						}
@@ -1164,39 +1174,56 @@ export default {
 								const newYoutubeVideoDuration =
 									youtubeDuration.toFixed(3);
 
-								const songDurationNumber = Number(
-									this.song.duration
-								);
-								const songDurationNumber2 =
-									Number(this.song.duration) + 1;
-								const songDurationNumber3 =
-									Number(this.song.duration) - 1;
-								const fixedSongDuration =
-									songDurationNumber.toFixed(3);
-								const fixedSongDuration2 =
-									songDurationNumber2.toFixed(3);
-								const fixedSongDuration3 =
-									songDurationNumber3.toFixed(3);
-
 								if (
-									this.youtubeVideoDuration !==
-										newYoutubeVideoDuration &&
-									(fixedSongDuration ===
-										this.youtubeVideoDuration ||
-										fixedSongDuration2 ===
+									this.youtubeVideoDuration.indexOf(
+										".000"
+									) !== -1 &&
+									`${this.youtubeVideoDuration}` !==
+										`${newYoutubeVideoDuration}`
+								) {
+									const songDurationNumber = Number(
+										this.song.duration
+									);
+									const songDurationNumber2 =
+										Number(this.song.duration) + 1;
+									const songDurationNumber3 =
+										Number(this.song.duration) - 1;
+									const fixedSongDuration =
+										songDurationNumber.toFixed(3);
+									const fixedSongDuration2 =
+										songDurationNumber2.toFixed(3);
+									const fixedSongDuration3 =
+										songDurationNumber3.toFixed(3);
+
+									if (
+										`${this.youtubeVideoDuration}` ===
+											`${Number(
+												this.song.duration
+											).toFixed(3)}` &&
+										(fixedSongDuration ===
 											this.youtubeVideoDuration ||
-										fixedSongDuration3 ===
-											this.youtubeVideoDuration)
-								)
-									this.song.duration =
+											fixedSongDuration2 ===
+												this.youtubeVideoDuration ||
+											fixedSongDuration3 ===
+												this.youtubeVideoDuration)
+									)
+										this.song.duration =
+											newYoutubeVideoDuration;
+
+									this.youtubeVideoDuration =
 										newYoutubeVideoDuration;
-
-								this.youtubeVideoDuration =
-									newYoutubeVideoDuration;
-								this.youtubeVideoNote = "";
+									if (
+										this.youtubeVideoDuration.indexOf(
+											".000"
+										) !== -1
+									)
+										this.youtubeVideoNote = "(~)";
+									else this.youtubeVideoNote = "";
+								}
 
 								if (this.song.duration === -1)
-									this.song.duration = youtubeDuration;
+									this.song.duration =
+										this.youtubeVideoDuration;
 
 								youtubeDuration -= this.song.skipDuration;
 								if (this.song.duration > youtubeDuration + 1) {
@@ -1269,6 +1296,7 @@ export default {
 			this.thumbnailHeight = null;
 			this.youtubeVideoCurrentTime = "0.000";
 			this.youtubeVideoDuration = "0.000";
+			this.youtubeVideoNote = "";
 			this.socket.dispatch("apis.leaveRoom", `edit-song.${songId}`);
 			if (this.$refs.saveButton) this.$refs.saveButton.status = "default";
 		},
@@ -1820,10 +1848,16 @@ export default {
 		},
 		onCloseModal() {
 			const songStringified = JSON.stringify({
-				...this.song
+				...this.song,
+				...{
+					duration: Number(this.song.duration).toFixed(3)
+				}
 			});
 			const originalSongStringified = JSON.stringify({
-				...this.originalSong
+				...this.originalSong,
+				...{
+					duration: Number(this.originalSong.duration).toFixed(3)
+				}
 			});
 			const unsavedChanges = songStringified !== originalSongStringified;
 

+ 26 - 18
frontend/src/components/modals/EditSongs.vue

@@ -1,8 +1,8 @@
 <template>
 	<div>
 		<edit-song
-			:modal-module-path="`modals/editSongs/${this.modalUuid}/editSong`"
-			:modal-uuid="this.modalUuid"
+			:modal-module-path="`modals/editSongs/${modalUuid}/editSong`"
+			:modal-uuid="modalUuid"
 			:bulk="true"
 			:flagged="currentSongFlagged"
 			v-if="currentSong"
@@ -258,23 +258,31 @@ export default {
 			} else this.editNextSong();
 		});
 
-		this.socket.on(`event:admin.song.updated`, res => {
-			const index = this.items
-				.map(item => item.song._id)
-				.indexOf(res.data.song._id);
-			this.items[index].song = {
-				...this.items[index].song,
-				...res.data.song,
-				updated: true
-			};
-		});
+		this.socket.on(
+			`event:admin.song.updated`,
+			res => {
+				const index = this.items
+					.map(item => item.song._id)
+					.indexOf(res.data.song._id);
+				this.items[index].song = {
+					...this.items[index].song,
+					...res.data.song,
+					updated: true
+				};
+			},
+			{ modalUuid: this.modalUuid }
+		);
 
-		this.socket.on(`event:admin.song.removed`, res => {
-			const index = this.items
-				.map(item => item.song._id)
-				.indexOf(res.data.songId);
-			this.items[index].song.removed = true;
-		});
+		this.socket.on(
+			`event:admin.song.removed`,
+			res => {
+				const index = this.items
+					.map(item => item.song._id)
+					.indexOf(res.data.songId);
+				this.items[index].song.removed = true;
+			},
+			{ modalUuid: this.modalUuid }
+		);
 	},
 	beforeUnmount() {
 		this.socket.dispatch("apis.leaveRoom", "edit-songs");

+ 1 - 1
frontend/src/pages/Admin/News.vue

@@ -21,7 +21,7 @@
 				:filters="filters"
 				data-action="news.getData"
 				name="admin-news"
-				max-width="1200"
+				:max-width="1200"
 				:events="events"
 			>
 				<template #column-options="slotProps">

+ 1 - 1
frontend/src/pages/Admin/Punishments.vue

@@ -8,7 +8,7 @@
 				:filters="filters"
 				data-action="punishments.getData"
 				name="admin-punishments"
-				max-width="1200"
+				:max-width="1200"
 			>
 				<template #column-options="slotProps">
 					<div class="row-options">

+ 1 - 1
frontend/src/pages/Admin/Reports.vue

@@ -8,7 +8,7 @@
 				:filters="filters"
 				data-action="reports.getData"
 				name="admin-reports"
-				max-width="1200"
+				:max-width="1200"
 				:events="events"
 			>
 				<template #column-options="slotProps">

+ 1 - 1
frontend/src/pages/Admin/Users/DataRequests.vue

@@ -8,7 +8,7 @@
 				:filters="filters"
 				data-action="dataRequests.getData"
 				name="admin-data-requests"
-				max-width="1200"
+				:max-width="1200"
 				:events="events"
 			>
 				<template #column-options="slotProps">

+ 1 - 1
frontend/src/pages/Admin/Users/index.vue

@@ -8,7 +8,7 @@
 				:filters="filters"
 				data-action="users.getData"
 				name="admin-users"
-				max-width="1200"
+				:max-width="1200"
 				:events="events"
 			>
 				<template #column-options="slotProps">

+ 2 - 0
frontend/src/pages/Station/index.vue

@@ -531,6 +531,7 @@
 								:class="{ 'no-currently-playing': noSong }"
 							>
 								<song-item
+									:key="`songItem-currentSong-${currentSong._id}`"
 									:song="currentSong"
 									:duration="false"
 									:requested-by="true"
@@ -543,6 +544,7 @@
 								class="quadrant"
 							>
 								<song-item
+									:key="`songItem-nextSong-${nextSong._id}`"
 									:song="nextSong"
 									:duration="false"
 									:requested-by="true"