ソースを参照

Added buttons to clear and refill station and genre playlists

Kristian Vos 3 年 前
コミット
42245ab6b5

+ 234 - 0
backend/logic/actions/playlists.js

@@ -1627,5 +1627,239 @@ export default {
 				return cb({ status: "success", message: "Success" });
 			}
 		);
+	}),
+
+	/**
+	 * Clears and refills a station playlist
+	 *
+	 * @param {object} session - the session object automatically added by socket.io
+	 * @param {string} playlistId - the id of the playlist we are clearing and refilling
+	 * @param {Function} cb - gets called with the result
+	 */
+	clearAndRefillStationPlaylist: isAdminRequired(async function index(session, playlistId, cb) {
+		async.waterfall(
+			[
+				next => {
+					if (!playlistId) next("Please specify a playlist id");
+					else {
+						PlaylistsModule.runJob("CLEAR_AND_REFILL_STATION_PLAYLIST", { playlistId }, this)
+							.then(() => {
+								next();
+							})
+							.catch(err => {
+								next(err);
+							});
+					}
+				}
+			],
+			async err => {
+				if (err) {
+					err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
+
+					this.log(
+						"ERROR",
+						"PLAYLIST_CLEAR_AND_REFILL_STATION_PLAYLIST",
+						`Clearing and refilling station playlist "${playlistId}" failed for user "${session.userId}". "${err}"`
+					);
+
+					return cb({ status: "failure", message: err });
+				}
+
+				this.log(
+					"SUCCESS",
+					"PLAYLIST_CLEAR_AND_REFILL_STATION_PLAYLIST",
+					`Successfully cleared and refilled station playlist "${playlistId}" for user "${session.userId}".`
+				);
+
+				return cb({
+					status: "success",
+					message: "Playlist has been successfully cleared and refilled"
+				});
+			}
+		);
+	}),
+
+	/**
+	 * Clears and refills a genre playlist
+	 *
+	 * @param {object} session - the session object automatically added by socket.io
+	 * @param {string} playlistId - the id of the playlist we are clearing and refilling
+	 * @param {Function} cb - gets called with the result
+	 */
+	clearAndRefillGenrePlaylist: isAdminRequired(async function index(session, playlistId, cb) {
+		async.waterfall(
+			[
+				next => {
+					if (!playlistId) next("Please specify a playlist id");
+					else {
+						PlaylistsModule.runJob("CLEAR_AND_REFILL_GENRE_PLAYLIST", { playlistId }, this)
+							.then(() => {
+								next();
+							})
+							.catch(err => {
+								next(err);
+							});
+					}
+				}
+			],
+			async err => {
+				if (err) {
+					err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
+
+					this.log(
+						"ERROR",
+						"PLAYLIST_CLEAR_AND_REFILL_GENRE_PLAYLIST",
+						`Clearing and refilling genre playlist "${playlistId}" failed for user "${session.userId}". "${err}"`
+					);
+
+					return cb({ status: "failure", message: err });
+				}
+
+				this.log(
+					"SUCCESS",
+					"PLAYLIST_CLEAR_AND_REFILL_GENRE_PLAYLIST",
+					`Successfully cleared and refilled genre playlist "${playlistId}" for user "${session.userId}".`
+				);
+
+				return cb({
+					status: "success",
+					message: "Playlist has been successfully cleared and refilled"
+				});
+			}
+		);
+	}),
+
+	/**
+	 * Clears and refills all station playlists
+	 *
+	 * @param {object} session - the session object automatically added by socket.io
+	 * @param {Function} cb - gets called with the result
+	 */
+	clearAndRefillAllStationPlaylists: isAdminRequired(async function index(session, cb) {
+		async.waterfall(
+			[
+				next => {
+					PlaylistsModule.runJob("GET_ALL_STATION_PLAYLISTS", {}, this)
+						.then(response => {
+							next(null, response.playlists);
+						})
+						.catch(err => {
+							next(err);
+						});
+				},
+
+				(playlists, next) => {
+					async.eachLimit(
+						playlists,
+						1,
+						(playlist, next) => {
+							PlaylistsModule.runJob(
+								"CLEAR_AND_REFILL_STATION_PLAYLIST",
+								{ playlistId: playlist._id },
+								this
+							)
+								.then(() => {
+									next();
+								})
+								.catch(err => {
+									next(err);
+								});
+						},
+						next
+					);
+				}
+			],
+			async err => {
+				if (err) {
+					err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
+
+					this.log(
+						"ERROR",
+						"PLAYLIST_CLEAR_AND_REFILL_ALL_STATION_PLAYLISTS",
+						`Clearing and refilling all station playlists failed for user "${session.userId}". "${err}"`
+					);
+
+					return cb({ status: "failure", message: err });
+				}
+
+				this.log(
+					"SUCCESS",
+					"PLAYLIST_CLEAR_AND_REFILL_ALL_STATION_PLAYLISTS",
+					`Successfully cleared and refilled all station playlists for user "${session.userId}".`
+				);
+
+				return cb({
+					status: "success",
+					message: "Playlists have been successfully cleared and refilled"
+				});
+			}
+		);
+	}),
+
+	/**
+	 * Clears and refills all genre playlists
+	 *
+	 * @param {object} session - the session object automatically added by socket.io
+	 * @param {Function} cb - gets called with the result
+	 */
+	clearAndRefillAllGenrePlaylists: isAdminRequired(async function index(session, cb) {
+		async.waterfall(
+			[
+				next => {
+					PlaylistsModule.runJob("GET_ALL_GENRE_PLAYLISTS", {}, this)
+						.then(response => {
+							next(null, response.playlists);
+						})
+						.catch(err => {
+							next(err);
+						});
+				},
+
+				(playlists, next) => {
+					async.eachLimit(
+						playlists,
+						1,
+						(playlist, next) => {
+							PlaylistsModule.runJob(
+								"CLEAR_AND_REFILL_GENRE_PLAYLIST",
+								{ playlistId: playlist._id },
+								this
+							)
+								.then(() => {
+									next();
+								})
+								.catch(err => {
+									next(err);
+								});
+						},
+						next
+					);
+				}
+			],
+			async err => {
+				if (err) {
+					err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
+
+					this.log(
+						"ERROR",
+						"PLAYLIST_CLEAR_AND_REFILL_ALL_GENRE_PLAYLISTS",
+						`Clearing and refilling all genre playlists failed for user "${session.userId}". "${err}"`
+					);
+
+					return cb({ status: "failure", message: err });
+				}
+
+				this.log(
+					"SUCCESS",
+					"PLAYLIST_CLEAR_AND_REFILL_ALL_GENRE_PLAYLISTS",
+					`Successfully cleared and refilled all genre playlists for user "${session.userId}".`
+				);
+
+				return cb({
+					status: "success",
+					message: "Playlists have been successfully cleared and refilled"
+				});
+			}
+		);
 	})
 };

+ 179 - 46
backend/logic/playlists.js

@@ -204,6 +204,23 @@ class _PlaylistsModule extends CoreClass {
 		});
 	}
 
+	/**
+	 * Gets all station playlists
+	 *
+	 * @param {object} payload - object that contains the payload
+	 * @param {string} payload.includeSongs - include the songs
+	 * @returns {Promise} - returns promise (reject, resolve)
+	 */
+	GET_ALL_STATION_PLAYLISTS(payload) {
+		return new Promise((resolve, reject) => {
+			const includeObject = payload.includeSongs ? null : { songs: false };
+			PlaylistsModule.playlistModel.find({ type: "station" }, includeObject, (err, playlists) => {
+				if (err) reject(new Error(err));
+				else resolve({ playlists });
+			});
+		});
+	}
+
 	/**
 	 * Gets a genre playlist
 	 *
@@ -410,13 +427,13 @@ class _PlaylistsModule extends CoreClass {
 							this
 						)
 							.then(response => {
-								next(null, { playlist: response.playlist });
+								next(null, response.playlist._id);
 							})
 							.catch(err => {
 								if (err.message === "Playlist not found") {
 									PlaylistsModule.runJob("CREATE_GENRE_PLAYLIST", { genre: payload.genre }, this)
 										.then(playlistId => {
-											next(null, { playlist: { _id: playlistId, songs: [] } });
+											next(null, playlistId);
 										})
 										.catch(err => {
 											next(err);
@@ -425,11 +442,10 @@ class _PlaylistsModule extends CoreClass {
 							});
 					},
 
-					(data, next) => {
+					(playlistId, next) => {
 						SongsModule.runJob("GET_ALL_SONGS_WITH_GENRE", { genre: payload.genre }, this)
 							.then(response => {
-								data.songs = response.songs;
-								next(null, data);
+								next(null, playlistId, response.songs);
 							})
 							.catch(err => {
 								console.log(err);
@@ -437,56 +453,83 @@ class _PlaylistsModule extends CoreClass {
 							});
 					},
 
-					(data, next) => {
-						data.songsToDelete = [];
-						data.songsToAdd = [];
-
-						data.playlist.songs.forEach(playlistSong => {
-							const found = data.songs.find(song => playlistSong.songId === song.songId);
-							if (!found) data.songsToDelete.push(playlistSong);
+					(playlistId, _songs, next) => {
+						const songs = _songs.map(song => {
+							const { _id, songId, title, artists, thumbnail, duration, status } = song;
+							return {
+								_id,
+								songId,
+								title,
+								artists,
+								thumbnail,
+								duration,
+								status
+							};
 						});
 
-						data.songs.forEach(song => {
-							const found = data.playlist.songs.find(playlistSong => song.songId === playlistSong.songId);
-							if (!found) data.songsToAdd.push(song);
+						PlaylistsModule.playlistModel.updateOne({ _id: playlistId }, { $set: { songs } }, err => {
+							next(err, playlistId);
 						});
-
-						next(null, data);
 					},
 
-					(data, next) => {
-						const promises = [];
-						data.songsToAdd.forEach(song => {
-							promises.push(
-								PlaylistsModule.runJob(
-									"ADD_SONG_TO_PLAYLIST",
-									{ playlistId: data.playlist._id, song },
-									this
-								)
-							);
-						});
-						data.songsToDelete.forEach(song => {
-							promises.push(
-								PlaylistsModule.runJob(
-									"DELETE_SONG_FROM_PLAYLIST_BY_SONGID",
-									{
-										playlistId: data.playlist._id,
-										songId: song.songId
-									},
-									this
-								)
-							);
-						});
-
-						Promise.allSettled(promises)
+					(playlistId, next) => {
+						PlaylistsModule.runJob("UPDATE_PLAYLIST", { playlistId }, this)
 							.then(() => {
-								next(null, data.playlist._id);
+								next(null, playlistId);
 							})
-							.catch(err => {
-								next(err);
-							});
+							.catch(next);
 					},
 
+					// (data, next) => {
+					// 	data.songsToDelete = [];
+					// 	data.songsToAdd = [];
+
+					// 	data.playlist.songs.forEach(playlistSong => {
+					// 		const found = data.songs.find(song => playlistSong.songId === song.songId);
+					// 		if (!found) data.songsToDelete.push(playlistSong);
+					// 	});
+
+					// 	data.songs.forEach(song => {
+					// 		const found = data.playlist.songs.find(playlistSong => song.songId === playlistSong.songId);
+					// 		if (!found) data.songsToAdd.push(song);
+					// 	});
+
+					// 	next(null, data);
+					// },
+
+					// (data, next) => {
+					// 	const promises = [];
+					// 	data.songsToAdd.forEach(song => {
+					// 		promises.push(
+					// 			PlaylistsModule.runJob(
+					// 				"ADD_SONG_TO_PLAYLIST",
+					// 				{ playlistId: data.playlist._id, song },
+					// 				this
+					// 			)
+					// 		);
+					// 	});
+					// 	data.songsToDelete.forEach(song => {
+					// 		promises.push(
+					// 			PlaylistsModule.runJob(
+					// 				"DELETE_SONG_FROM_PLAYLIST_BY_SONGID",
+					// 				{
+					// 					playlistId: data.playlist._id,
+					// 					songId: song.songId
+					// 				},
+					// 				this
+					// 			)
+					// 		);
+					// 	});
+
+					// 	Promise.allSettled(promises)
+					// 		.then(() => {
+					// 			next(null, data.playlist._id);
+					// 		})
+					// 		.catch(err => {
+					// 			next(err);
+					// 		});
+					// },
+
 					(playlistId, next) => {
 						StationsModule.runJob("GET_STATIONS_THAT_INCLUDE_OR_EXCLUDE_PLAYLIST", { playlistId })
 							.then(response => {
@@ -935,6 +978,96 @@ class _PlaylistsModule extends CoreClass {
 			)
 		);
 	}
+
+	/**
+	 * Clears and refills a station playlist
+	 *
+	 * @param {object} payload - object that contains the payload
+	 * @param {string} payload.playlistId - the id of the playlist we are trying to clear and refill
+	 * @returns {Promise} - returns promise (reject, resolve)
+	 */
+	CLEAR_AND_REFILL_STATION_PLAYLIST(payload) {
+		return new Promise((resolve, reject) => {
+			const { playlistId } = payload;
+			async.waterfall(
+				[
+					next => {
+						PlaylistsModule.runJob("GET_PLAYLIST", { playlistId }, this)
+							.then(playlist => {
+								next(null, playlist);
+							})
+							.catch(err => {
+								next(err);
+							});
+					},
+
+					(playlist, next) => {
+						if (playlist.type !== "station") next("This playlist is not a station playlist.");
+						else next(null, playlist.createdFor);
+					},
+
+					(stationId, next) => {
+						PlaylistsModule.runJob("AUTOFILL_STATION_PLAYLIST", { stationId }, this)
+							.then(() => {
+								next();
+							})
+							.catch(err => {
+								next(err);
+							});
+					}
+				],
+				err => {
+					if (err && err !== true) return reject(new Error(err));
+					return resolve();
+				}
+			);
+		});
+	}
+
+	/**
+	 * Clears and refills a genre playlist
+	 *
+	 * @param {object} payload - object that contains the payload
+	 * @param {string} payload.playlistId - the id of the playlist we are trying to clear and refill
+	 * @returns {Promise} - returns promise (reject, resolve)
+	 */
+	CLEAR_AND_REFILL_GENRE_PLAYLIST(payload) {
+		return new Promise((resolve, reject) => {
+			const { playlistId } = payload;
+			async.waterfall(
+				[
+					next => {
+						PlaylistsModule.runJob("GET_PLAYLIST", { playlistId }, this)
+							.then(playlist => {
+								next(null, playlist);
+							})
+							.catch(err => {
+								next(err);
+							});
+					},
+
+					(playlist, next) => {
+						if (playlist.type !== "genre") next("This playlist is not a genre playlist.");
+						else next(null, playlist.createdFor);
+					},
+
+					(genre, next) => {
+						PlaylistsModule.runJob("AUTOFILL_GENRE_PLAYLIST", { genre }, this)
+							.then(() => {
+								next();
+							})
+							.catch(err => {
+								next(err);
+							});
+					}
+				],
+				err => {
+					if (err && err !== true) return reject(new Error(err));
+					return resolve();
+				}
+			);
+		});
+	}
 }
 
 export default new _PlaylistsModule();

+ 50 - 2
frontend/src/components/modals/EditPlaylist.vue

@@ -349,6 +349,22 @@
 			>
 				Remove Playlist
 			</a>
+			<a
+				class="button is-danger"
+				@click="clearAndRefillStationPlaylist()"
+				href="#"
+				v-if="playlist.type === 'station'"
+			>
+				Clear and refill station playlist
+			</a>
+			<a
+				class="button is-danger"
+				@click="clearAndRefillGenrePlaylist()"
+				href="#"
+				v-if="playlist.type === 'genre'"
+			>
+				Clear and refill genre playlist
+			</a>
 		</div>
 	</modal>
 </template>
@@ -702,8 +718,40 @@ export default {
 				songId,
 				data => {
 					if (data.status !== "success")
-						new Toast(`Error: ${data.message}`);
-					else new Toast(data.message);
+						new Toast({
+							content: `Error: ${data.message}`,
+							timeout: 8000
+						});
+					else new Toast({ content: data.message, timeout: 4000 });
+				}
+			);
+		},
+		clearAndRefillStationPlaylist() {
+			this.socket.dispatch(
+				"playlists.clearAndRefillStationPlaylist",
+				this.playlist._id,
+				data => {
+					console.log(data.message);
+					if (data.status !== "success")
+						new Toast({
+							content: `Error: ${data.message}`,
+							timeout: 8000
+						});
+					else new Toast({ content: data.message, timeout: 4000 });
+				}
+			);
+		},
+		clearAndRefillGenrePlaylist() {
+			this.socket.dispatch(
+				"playlists.clearAndRefillGenrePlaylist",
+				this.playlist._id,
+				data => {
+					if (data.status !== "success")
+						new Toast({
+							content: `Error: ${data.message}`,
+							timeout: 8000
+						});
+					else new Toast({ content: data.message, timeout: 4000 });
 				}
 			);
 		},

+ 42 - 0
frontend/src/pages/Admin/tabs/Playlists.vue

@@ -20,6 +20,18 @@
 			>
 				Request orphaned playlist songs
 			</button>
+			<button
+				class="button is-primary"
+				@click="clearAndRefillAllStationPlaylists()"
+			>
+				Clear and refill all station playlists
+			</button>
+			<button
+				class="button is-primary"
+				@click="clearAndRefillAllGenrePlaylists()"
+			>
+				Clear and refill all genre playlists
+			</button>
 			<br />
 			<br />
 			<table class="table is-striped">
@@ -181,6 +193,36 @@ export default {
 				}
 			);
 		},
+		clearAndRefillAllStationPlaylists() {
+			this.socket.dispatch(
+				"playlists.clearAndRefillAllStationPlaylists",
+				res => {
+					if (res.status === "success") {
+						new Toast({ content: res.message, timeout: 4000 });
+					} else {
+						new Toast({
+							content: `Error: ${res.message}`,
+							timeout: 4000
+						});
+					}
+				}
+			);
+		},
+		clearAndRefillAllGenrePlaylists() {
+			this.socket.dispatch(
+				"playlists.clearAndRefillAllGenrePlaylists",
+				res => {
+					if (res.status === "success") {
+						new Toast({ content: res.message, timeout: 4000 });
+					} else {
+						new Toast({
+							content: `Error: ${res.message}`,
+							timeout: 4000
+						});
+					}
+				}
+			);
+		},
 		...mapActions("modalVisibility", ["openModal"]),
 		...mapActions("user/playlists", ["editPlaylist"])
 	}