Bladeren bron

Playlist privacy setting

Owen Diffey 4 jaren geleden
bovenliggende
commit
556a0b1e6f

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

@@ -105,6 +105,20 @@ CacheModule.runJob("SUB", {
 	}
 });
 
+CacheModule.runJob("SUB", {
+	channel: "playlist.updatePrivacy",
+	cb: res => {
+		IOModule.runJob("SOCKETS_FROM_USER", { userId: res.userId }, this).then(response => {
+			response.sockets.forEach(socket => {
+				socket.emit("event:playlist.updatePrivacy", {
+					playlistId: res.playlistId,
+					privacy: res.privacy
+				});
+			});
+		});
+	}
+});
+
 export default {
 	/**
 	 * Gets the first song from a private playlist
@@ -1174,5 +1188,70 @@ export default {
 				});
 			}
 		);
+	}),
+
+	/**
+	 * Updates the privacy of a private playlist
+	 *
+	 * @param {object} session - the session object automatically added by socket.io
+	 * @param {string} playlistId - the id of the playlist we are updating the displayName for
+	 * @param {Function} cb - gets called with the result
+	 */
+	updatePrivacy: isLoginRequired(async function updatePrivacy(session, playlistId, privacy, cb) {
+		const playlistModel = await DBModule.runJob(
+			"GET_MODEL",
+			{
+				modelName: "playlist"
+			},
+			this
+		);
+		async.waterfall(
+			[
+				next => {
+					playlistModel.updateOne(
+						{ _id: playlistId, createdBy: session.userId },
+						{ $set: { privacy } },
+						{ runValidators: true },
+						next
+					);
+				},
+
+				(res, next) => {
+					PlaylistsModule.runJob("UPDATE_PLAYLIST", { playlistId }, this)
+						.then(playlist => {
+							next(null, playlist);
+						})
+						.catch(next);
+				}
+			],
+			async err => {
+				if (err) {
+					err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
+					this.log(
+						"ERROR",
+						"PLAYLIST_UPDATE_PRIVACY",
+						`Updating privacy to "${privacy}" for private playlist "${playlistId}" failed for user "${session.userId}". "${err}"`
+					);
+					return cb({ status: "failure", message: err });
+				}
+				this.log(
+					"SUCCESS",
+					"PLAYLIST_UPDATE_PRIVACY",
+					`Successfully updated privacy to "${privacy}" for private playlist "${playlistId}" for user "${session.userId}".`
+				);
+				CacheModule.runJob("PUB", {
+					channel: "playlist.updatePrivacy",
+					value: {
+						playlistId,
+						privacy,
+						userId: session.userId
+					}
+				});
+				return cb({
+					status: "success",
+					message: "Playlist has been successfully updated"
+				});
+			}
+		);
 	})
 };

+ 2 - 1
backend/logic/db/schemas/playlist.js

@@ -2,5 +2,6 @@ export default {
 	displayName: { type: String, min: 2, max: 32, required: true },
 	songs: { type: Array },
 	createdBy: { type: String, required: true },
-	createdAt: { type: Date, default: Date.now, required: true }
+	createdAt: { type: Date, default: Date.now, required: true },
+	privacy: { type: String, enum: ["public", "private"], default: "private" }
 };

+ 1 - 1
frontend/src/App.vue

@@ -86,7 +86,7 @@ export default {
 		else this.disableNightMode();
 
 		const autoSkipDisliked =
-			false || JSON.parse(localStorage.getItem("autoSkipDisliked"));
+			true || JSON.parse(localStorage.getItem("autoSkipDisliked"));
 
 		this.changeAutoSkipDisliked(autoSkipDisliked);
 	},

+ 33 - 0
frontend/src/components/modals/EditPlaylist.vue

@@ -152,6 +152,19 @@
 					>
 				</p>
 			</div>
+			<div class="control is-grouped">
+				<div class="control select">
+					<select v-model="playlist.privacy">
+						<option value="private">Private</option>
+						<option value="public">Public</option>
+					</select>
+				</div>
+				<p class="control">
+					<a class="button is-info" @click="updatePrivacy()" href="#"
+						>Update Privacy</a
+					>
+				</p>
+			</div>
 		</div>
 		<div slot="footer">
 			<a class="button is-danger" @click="removePlaylist()" href="#"
@@ -387,6 +400,19 @@ export default {
 				}
 			);
 		},
+		updatePrivacy() {
+			const { privacy } = this.playlist;
+			if (privacy === "public" || privacy === "private") {
+				this.socket.emit(
+					"playlists.updatePrivacy",
+					this.playlist._id,
+					privacy,
+					res => {
+						new Toast({ content: res.message, timeout: 4000 });
+					}
+				);
+			}
+		},
 		...mapActions("modals", ["closeModal"])
 	}
 };
@@ -429,4 +455,11 @@ li a {
 h5 {
 	padding: 20px 0;
 }
+
+.control.select {
+	flex-grow: 1;
+	select {
+		width: 100%;
+	}
+}
 </style>

+ 14 - 1
frontend/src/components/ui/PlaylistItem.vue

@@ -1,7 +1,15 @@
 <template>
 	<div class="playlist">
 		<div class="left-part">
-			<p class="top-text">{{ playlist.displayName }}</p>
+			<p class="top-text">
+				{{ playlist.displayName }}
+				<i
+					v-if="playlist.privacy === 'private'"
+					class="privateIcon material-icons"
+					title="This playlist is not visible to other users."
+					>lock</i
+				>
+			</p>
 			<p class="bottom-text">
 				{{ totalLength(playlist) }} •
 				{{ playlist.songs.length }}
@@ -59,6 +67,11 @@ export default {
 		font-size: 20px;
 		line-height: 23px;
 		margin-bottom: 0;
+
+		.privateIcon {
+			color: $dark-pink;
+			font-size: 18px;
+		}
 	}
 
 	.bottom-text {

+ 5 - 1
frontend/src/pages/Admin/tabs/Users.vue

@@ -34,7 +34,11 @@
 						<td v-else>Not Linked</td>
 						<td v-if="user.hasPassword">Yes</td>
 						<td v-else>Not Linked</td>
-						<td>{{ user.username }}</td>
+						<td>
+							<a :href="'/u/' + user.username" target="_blank">{{
+								user.username
+							}}</a>
+						</td>
 						<td>{{ user.role }}</td>
 						<td>{{ user.email.address }}</td>
 						<td>{{ user.email.verified }}</td>

+ 58 - 29
frontend/src/pages/Profile.vue

@@ -76,7 +76,6 @@
 					<button
 						:class="{ active: activeTab === 'playlists' }"
 						@click="switchTab('playlists')"
-						v-if="user._id === userId"
 					>
 						Playlists
 					</button>
@@ -131,35 +130,49 @@
 					class="content playlists-tab"
 					v-if="activeTab === 'playlists'"
 				>
-					<div
-						class="item playlist"
-						v-for="playlist in playlists"
-						:key="playlist._id"
-					>
-						<playlist-item :playlist="playlist">
-							<div slot="actions">
-								<button
-									class="button is-primary"
-									@click="editPlaylistClick(playlist._id)"
-								>
-									<i class="material-icons icon-with-button"
-										>create</i
-									>Edit
-								</button>
-							</div>
-						</playlist-item>
+					<div v-if="playlists.length > 0">
+						<div
+							class="item playlist"
+							v-for="playlist in playlists"
+							:key="playlist._id"
+						>
+							<playlist-item
+								v-if="
+									playlist.privacy === 'public' ||
+										(playlist.privacy === 'private' &&
+											playlist.createdBy === userId)
+								"
+								:playlist="playlist"
+							>
+								<div v-if="user._id === userId" slot="actions">
+									<button
+										class="button is-primary"
+										@click="editPlaylistClick(playlist._id)"
+									>
+										<i
+											class="material-icons icon-with-button"
+											>create</i
+										>Edit
+									</button>
+								</div>
+							</playlist-item>
+						</div>
+						<button
+							v-if="user._id === userId"
+							class="button is-primary"
+							@click="
+								openModal({
+									sector: 'station',
+									modal: 'createPlaylist'
+								})
+							"
+						>
+							Create new playlist
+						</button>
+					</div>
+					<div v-else>
+						<h2>No playlists here.</h2>
 					</div>
-					<button
-						class="button is-primary"
-						@click="
-							openModal({
-								sector: 'station',
-								modal: 'createPlaylist'
-							})
-						"
-					>
-						Create new playlist
-					</button>
 				</div>
 			</div>
 		</div>
@@ -350,6 +363,22 @@ export default {
 									);
 								}
 							);
+
+							this.socket.on(
+								"event:playlist.updatePrivacy",
+								data => {
+									this.playlists.forEach(
+										(playlist, index) => {
+											if (
+												playlist._id === data.playlistId
+											) {
+												this.playlists[index].privacy =
+													data.privacy;
+											}
+										}
+									);
+								}
+							);
 						}
 					}
 				}

+ 8 - 0
frontend/src/pages/Station/components/Sidebar/MyPlaylists.vue

@@ -117,6 +117,14 @@ export default {
 					}
 				});
 			});
+
+			this.socket.on("event:playlist.updatePrivacy", data => {
+				this.playlists.forEach((playlist, index) => {
+					if (playlist._id === data.playlistId) {
+						this.playlists[index].privacy = data.privacy;
+					}
+				});
+			});
 		});
 	},
 	methods: {