Переглянути джерело

Merge remote-tracking branch 'origin/polishing' into owen

Owen Diffey 3 роки тому
батько
коміт
4e21cdbed0

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

@@ -1353,10 +1353,10 @@ export default {
 	}),
 
 	/**
-	 * Removes a private playlist
+	 * Removes a user's own modifiable user playlist
 	 *
 	 * @param {object} session - the session object automatically added by the websocket
-	 * @param {string} playlistId - the id of the playlist we are moving the song to the top from
+	 * @param {string} playlistId - the id of the playlist we are removing
 	 * @param {Function} cb - gets called with the result
 	 */
 	remove: isLoginRequired(async function remove(session, playlistId, cb) {
@@ -1371,6 +1371,7 @@ export default {
 				},
 
 				(playlist, next) => {
+					if (playlist.createdBy !== session.userId) return next("You do not own this playlist."); 
 					if (!playlist.isUserModifiable) return next("Playlist cannot be removed.");
 					return next(null, playlist);
 				},
@@ -1432,6 +1433,78 @@ export default {
 		);
 	}),
 
+	/**
+	 * Removes a user's modifiable user playlist as an admin
+	 *
+	 * @param {object} session - the session object automatically added by the websocket
+	 * @param {string} playlistId - the id of the playlist we are removing
+	 * @param {Function} cb - gets called with the result
+	 */
+	 removeAdmin: isAdminRequired(async function removeAdmin(session, playlistId, cb) {
+		const userModel = await DBModule.runJob("GET_MODEL", { modelName: "user" }, this);
+
+		async.waterfall(
+			[
+				next => {
+					PlaylistsModule.runJob("GET_PLAYLIST", { playlistId }, this)
+						.then(playlist => next(null, playlist))
+						.catch(next);
+				},
+
+				(playlist, next) => {
+					if (!playlist.isUserModifiable) return next("Playlist cannot be removed.");
+					return next(null, playlist);
+				},
+
+				(playlist, next) => {
+					userModel.updateOne(
+						{ _id: playlist.createdBy },
+						{ $pull: { "preferences.orderOfPlaylists": playlist._id } },
+						err => next(err, playlist)
+					);
+				},
+
+				(playlist, next) => {
+					PlaylistsModule.runJob("DELETE_PLAYLIST", { playlistId }, this)
+						.then(() => next(null, playlist))
+						.catch(next);
+				}
+			],
+			async err => {
+				if (err) {
+					err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
+					this.log(
+						"ERROR",
+						"PLAYLIST_REMOVE_ADMIN",
+						`Removing private playlist "${playlistId}" failed for user "${session.userId}". "${err}"`
+					);
+					return cb({ status: "error", message: err });
+				}
+
+				this.log(
+					"SUCCESS",
+					"PLAYLIST_REMOVE_ADMIN",
+					`Successfully removed private playlist "${playlistId}" for user "${session.userId}".`
+				);
+
+				CacheModule.runJob("PUB", {
+					channel: "playlist.delete",
+					value: {
+						userId: session.userId,
+						playlistId
+					}
+				});
+
+				ActivitiesModule.runJob("REMOVE_ACTIVITY_REFERENCES", { type: "playlistId", playlistId });
+
+				return cb({
+					status: "success",
+					message: "Playlist successfully removed"
+				});
+			}
+		)
+	 }),
+
 	/**
 	 * Updates the privacy of a private playlist
 	 *

+ 11 - 0
backend/logic/playlists.js

@@ -1276,6 +1276,17 @@ class _PlaylistsModule extends CoreClass {
 						)
 							.then(() => next())
 							.catch(next);
+					},
+
+					next => {
+						StationsModule.runJob(
+							"REMOVE_INCLUDED_OR_EXCLUDED_PLAYLIST_FROM_STATIONS",
+							{ playlistId: payload.playlistId },
+							this
+						).then(() => {
+							next();
+						})
+						.catch(err => next(err));
 					}
 				],
 				err => {

+ 46 - 1
backend/logic/stations.js

@@ -1439,7 +1439,6 @@ class _StationsModule extends CoreClass {
 	 */
 	REMOVE_EXCLUDED_PLAYLIST(payload) {
 		return new Promise((resolve, reject) => {
-			console.log(112, payload);
 			async.waterfall(
 				[
 					next => {
@@ -1504,6 +1503,52 @@ class _StationsModule extends CoreClass {
 		});
 	}
 
+	/**
+	 * Removes included or excluded playlist from a station
+	 *
+	 * @param {object} payload - object that contains the payload
+	 * @param {string} payload.playlistId - the playlist id
+	 * @returns {Promise} - returns promise (reject, resolve)
+	 */
+	REMOVE_INCLUDED_OR_EXCLUDED_PLAYLIST_FROM_STATIONS(payload) {
+		return new Promise((resolve, reject) => {
+			async.waterfall(
+				[
+					next => {
+						if (!payload.playlistId) next("Please specify a playlist id");
+						else next();
+					},
+
+					next => {
+						StationsModule.stationModel.updateMany(
+							{
+								$or: [{ includedPlaylists: payload.playlistId }, { excludedPlaylists: payload.playlistId }]
+							},
+							{
+								$pull: {
+									includedPlaylists: payload.playlistId,
+									excludedPlaylists: payload.playlistId
+								}
+							},
+							(err) => {
+								if (err) next(err);
+								else next();
+							}
+						);
+					}
+				],
+				async err => {
+					if (err && err !== true) {
+						err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
+						return reject(new Error(err));
+					}
+
+					return resolve();
+				}
+			);
+		});
+	}
+
 	/**
 	 * Gets stations that include or exclude a specific playlist
 	 *

+ 1 - 1
backend/logic/ws.js

@@ -294,7 +294,7 @@ class _WSModule extends CoreClass {
 		return new Promise(resolve => {
 			// create room if it doesn't exist, and add socketId to array
 			if (WSModule.rooms[room]) {
-				if (!(socketId in WSModule.rooms[room])) WSModule.rooms[room].push(socketId);
+				if (WSModule.rooms[room].indexOf(socketId) === -1) WSModule.rooms[room].push(socketId);
 			} else WSModule.rooms[room] = [socketId];
 
 			return resolve();

+ 49 - 139
fallback.html

@@ -1,148 +1,58 @@
 <!DOCTYPE html>
 <html>
-    <head>
-        <title>Musare</title>
 
-        <link href="https://fonts.googleapis.com/css?family=Lato:100" rel="stylesheet" type="text/css">
-        <link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet" integrity="sha384-wvfXpqpZZVQGK6TAh5PVlGOfQNHSoD2xbE+QkPxCAFlNEevoEH3Sl0sibVcOQVnN" crossorigin="anonymous">
-        <style media="screen"></style>
-        <style>
-            html, body {
-                height: 100%;
-            }
-            body {
-                margin: 0;
-                padding: 0;
-                width: 100%;
-                display: table;
-                font-weight: 100;
-                font-family: 'Lato';
-            }
-            .container {
-                text-align: center;
-                display: table-cell;
-                vertical-align: middle;
-            }
-            .content {
-                text-align: center;
-                display: inline-block;
-                padding-left: 15px;
-                padding-right: 15px;
-            }
-            .title {
-                font-size: 96px;
-            }
-            p {
-              font-size: 28px;
-              font-weight: 700;
-            }
-            .social {
-                width: 100%;
-                text-align:center;
-                display: block;
-            }
-            .social #discord {
-                display: inline-block;
-                vertical-align: middle;
-                color: #ff4545;
-                font-size: 22px;
-                background-color: transparent;
-                height: 40px;
-                line-height: 42px;
-                width: 40px;
-                margin: 10px 5px;
-                transition: all ease-in-out 0.5s
-            }
-            .social #discord .st0 {
-                fill:#ff4545;
-                transition: all ease-in-out 0.5s
-            }
-            .social #discord:hover .st0 {
-                fill:#0279b1;
-                transition: all ease-in-out 0.5s
-            }
-            .social .fa {
-                display: inline-block;
-                vertical-align: middle;
-                color: #ff4545;
-                font-size: 28px;
-                background-color: transparent;
-                height: 40px;
-                line-height: 42px;
-                width: 40px;
-                margin: 10px 5px;
-                transition: all ease-in-out 0.5s
-            }
-            .social .fa:hover {
-                color: #0279b1;
-            }
+<head>
+    <title>Musare</title>
+    <link href="https://fonts.googleapis.com/css?family=Lato:100" rel="stylesheet" type="text/css">
+    <link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet"
+        integrity="sha384-wvfXpqpZZVQGK6TAh5PVlGOfQNHSoD2xbE+QkPxCAFlNEevoEH3Sl0sibVcOQVnN" crossorigin="anonymous">
+    <style>
+        html,
+        body {
+            height: 100%;
+        }
 
-            .social .social-icon .fa {
-                font-size:20px;
-            	position:absolute;
-            	left:9px;
-            	top:10px;
-            }
+        body {
+            margin: 0;
+            padding: 0;
+            width: 100%;
+            display: table;
+            font-weight: 100;
+            font-family: 'Lato';
+        }
 
-            .socialIcon {
-                position: relative;
-            }
+        .container {
+            text-align: center;
+            display: table-cell;
+            vertical-align: middle;
+        }
 
-            .socialIcon .icon-purpose {
-        		visibility: hidden;
-        		width: 120px;
-        		font-size: 18px;
-        		background-color: rgba(255, 69, 69, 0.8);
-        		color: #fff;
-        		text-align: center;
-        		border-radius: 6px;
-        		padding: 5px;
-        		position: absolute;
-        		z-index: 1;
-                bottom: 150%;
-                left: 50%;
-                margin-left: -65px;
-        		opacity: 0;
-                margin-bottom: 10px;
-            	transition: opacity 0.5s;
-        		display: none;
-        	}
+        .content {
+            text-align: center;
+            display: inline-block;
+            padding-left: 15px;
+            padding-right: 15px;
+        }
 
-        	.socialIcon .icon-purpose::after {
-        		content: "";
-        	    position: absolute;
-                top: 100%;
-                left: 50%;
-                margin-left: -5px;
-        	    border-width: 5px;
-        	    border-style: solid;
-        	    border-color: rgba(255, 69, 69, 0.8) transparent transparent transparent;
-        	}
+        .title {
+            font-size: 96px;
+        }
 
-        	.socialIcon:hover .icon-purpose {
-        		visibility: visible;
-        		opacity: 1;
-        		display: block;
-        	}
-        </style>
-    </head>
-    <body>
-        <div class="container">
-            <div class="content">
-                <img src="https://preview.ibb.co/eAo1y5/logo.png" alt="Logo" style="width: 80%;" />
-                <div class="title">We are offline!</div>
-                <p>Visit Twitter or Discord via the links below to check when we are back online.</p>
-                <span class="social">
-                    <a class="socialIcon" href="https://twitter.com/MusareApp" target="_blank">
-                        <i class="fa fa-twitter" aria-hidden="true"></i>
-                        <span class="icon-purpose">Twitter</span>
-                    </a>
-                    <a class="socialIcon" href="https://discord.gg/Y5NxYGP" target="_blank">
-                        <svg id="discord" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 245 240"><path class="st0" d="M104.4 103.9c-5.7 0-10.2 5-10.2 11.1s4.6 11.1 10.2 11.1c5.7 0 10.2-5 10.2-11.1.1-6.1-4.5-11.1-10.2-11.1zM140.9 103.9c-5.7 0-10.2 5-10.2 11.1s4.6 11.1 10.2 11.1c5.7 0 10.2-5 10.2-11.1s-4.5-11.1-10.2-11.1z"/><path class="st0" d="M189.5 20h-134C44.2 20 35 29.2 35 40.6v135.2c0 11.4 9.2 20.6 20.5 20.6h113.4l-5.3-18.5 12.8 11.9 12.1 11.2 21.5 19V40.6c0-11.4-9.2-20.6-20.5-20.6zm-38.6 130.6s-3.6-4.3-6.6-8.1c13.1-3.7 18.1-11.9 18.1-11.9-4.1 2.7-8 4.6-11.5 5.9-5 2.1-9.8 3.5-14.5 4.3-9.6 1.8-18.4 1.3-25.9-.1-5.7-1.1-10.6-2.7-14.7-4.3-2.3-.9-4.8-2-7.3-3.4-.3-.2-.6-.3-.9-.5-.2-.1-.3-.2-.4-.3-1.8-1-2.8-1.7-2.8-1.7s4.8 8 17.5 11.8c-3 3.8-6.7 8.3-6.7 8.3-22.1-.7-30.5-15.2-30.5-15.2 0-32.2 14.4-58.3 14.4-58.3 14.4-10.8 28.1-10.5 28.1-10.5l1 1.2c-18 5.2-26.3 13.1-26.3 13.1s2.2-1.2 5.9-2.9c10.7-4.7 19.2-6 22.7-6.3.6-.1 1.1-.2 1.7-.2 6.1-.8 13-1 20.2-.2 9.5 1.1 19.7 3.9 30.1 9.6 0 0-7.9-7.5-24.9-12.7l1.4-1.6s13.7-.3 28.1 10.5c0 0 14.4 26.1 14.4 58.3 0 0-8.5 14.5-30.6 15.2z"/></svg>
-                        <span class="icon-purpose">Discord</span>
-                    </a>
-                </span>
-            </div>
+        p {
+            font-size: 28px;
+            font-weight: 700;
+        }
+    </style>
+</head>
+
+<body>
+    <div class="container">
+        <div class="content">
+            <img src="https://preview.ibb.co/eAo1y5/logo.png" alt="Logo" style="width: 80%;" />
+            <div class="title">We are offline!</div>
+            <p>Please check back later.</p>
         </div>
-    </body>
-</html>
+    </div>
+</body>
+
+</html>

+ 24 - 4
frontend/src/components/modals/EditPlaylist/index.vue

@@ -405,6 +405,9 @@ export default {
 		isAdmin() {
 			return this.userRole === "admin";
 		},
+		isOwner() {
+			return this.loggedIn && this.userId === this.playlist.createdBy;
+		},
 		repositionSong({ moved }) {
 			if (!moved) return; // we only need to update when song is moved
 
@@ -492,10 +495,27 @@ export default {
 			);
 		},
 		removePlaylist() {
-			this.socket.dispatch("playlists.remove", this.playlist._id, res => {
-				new Toast(res.message);
-				if (res.status === "success") this.closeModal("editPlaylist");
-			});
+			if (this.isOwner()) {
+				this.socket.dispatch(
+					"playlists.remove",
+					this.playlist._id,
+					res => {
+						new Toast(res.message);
+						if (res.status === "success")
+							this.closeModal("editPlaylist");
+					}
+				);
+			} else if (this.isAdmin()) {
+				this.socket.dispatch(
+					"playlists.removeAdmin",
+					this.playlist._id,
+					res => {
+						new Toast(res.message);
+						if (res.status === "success")
+							this.closeModal("editPlaylist");
+					}
+				);
+			}
 		},
 		async downloadPlaylist() {
 			if (this.apiDomain === "")

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

@@ -72,6 +72,14 @@
 							<div id="station-info">
 								<div class="row" id="station-name">
 									<h1>{{ station.displayName }}</h1>
+									<i
+										v-if="station.type === 'official'"
+										class="material-icons verified-station"
+										content="Verified Station"
+										v-tippy
+									>
+										check_circle
+									</i>
 									<a href="#">
 										<!-- Favorite Station Button -->
 										<i
@@ -2470,6 +2478,10 @@ export default {
 							color: var(--primary-color);
 						}
 					}
+
+					.verified-station {
+						color: var(--primary-color);
+					}
 				}
 
 				p {