Browse Source

Implemented private playlists in stations, and fixed a lot of issues with them.

KrisVos130 8 years ago
parent
commit
695f81f10a

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

@@ -7,6 +7,7 @@ const utils = require('../utils');
 const hooks = require('./hooks');
 const async = require('async');
 const playlists = require('../playlists');
+const songs = require('../songs');
 
 let lib = {
 
@@ -27,20 +28,9 @@ let lib = {
 				return (data) ? next() : cb({ 'status': 'failure', 'message': 'Invalid data' });
 			},
 
-			// check the cache for the playlist
-			(next) => cache.hget('playlists', data._id, next),
-
-			// if the cached version exist
-			(playlist, next) => {
-				if (playlist) return next({ 'status': 'failure', 'message': 'A playlist with that id already exists' });
-				db.models.playlist.findOne({ _id: data._id }, next);
-			},
-
-			(playlist, next) => {
-				if (playlist) return next({ 'status': 'failure', 'message': 'A playlist with that id already exists' });
-				const { _id, displayName, songs, createdBy } = data;
+			(next) => {
+				const { name, displayName, songs, createdBy } = data;
 				db.models.playlist.create({
-					_id,
 					displayName,
 					songs,
 					createdBy,
@@ -74,20 +64,14 @@ let lib = {
 	addSongToPlaylist: (session, songId, playlistId, cb) => {
 		async.waterfall([
 			(next) => {
-				utils.getSongFromYouTube(songId, (song) => {
-					song.artists = [];
-					song.genres = [];
-					song.skipDuration = 0;
-					song.thumbnail = 'empty';
-					song.explicit = false;
-					song.requestedBy = 'temp';
-					song.requestedAt = Date.now();
-					next(null, song);
-				});
-			},
-			(newSong, next) => {
-				utils.getSongFromSpotify(newSong, (song) => {
-					next(null, song);
+				songs.getSong(songId, (err, song) => {
+					if (err) {
+						utils.getSongFromYouTube(songId, (song) => {
+							next(null, song);
+						});
+					} else {
+						next(null, {_id: songId, title: song.title, duration: song.duration});
+					}
 				});
 			},
 			(newSong, next) => {
@@ -169,23 +153,6 @@ let lib = {
 		});
 	},
 
-	updatePlaylistId: (session, oldId, newId, cb) => {
-		db.models.playlist.findOne({ _id: oldId }).exec((err, doc) => {
-			if (err) throw err;
-			doc._id = newId;
-			let newPlaylist = new db.models.playlist(doc);
-			newPlaylist.isNew = true;
-			newPlaylist.save(err => {
-				if (err) console.error(err);
-			});
-			db.models.playlist.remove({ _id: oldId });
-			cache.hdel('playlists', oldId, () => {
-				cache.hset('playlists', newId, doc);
-				return cb({ status: 'success', data: doc });
-			});
-		});
-	},
-
 	promoteSong: (session, playlistId, fromIndex, cb) => {
 		db.models.playlist.findOne({ _id: playlistId }, (err, playlist) => {
 			if (err) throw err;

+ 63 - 14
backend/logic/actions/stations.js

@@ -33,19 +33,26 @@ cache.sub('station.queueUpdate', stationId => {
 
 cache.sub('station.create', stationId => {
 	stations.initializeStation(stationId, (err, station) => {
+		console.log("*************", err, station);
 		//TODO Emit to admin station page
 
 		// TODO If community, check if on whitelist
+		console.log("*************", station.privacy);
 		if (station.privacy === 'public') io.io.to('home').emit("event:stations.created", station);
 		else {
 			let sockets = io.io.to('home').sockets;
+			console.log("*************", sockets.length);
 			for (let socketId in sockets) {
 				let socket = sockets[socketId];
 				let session = sockets[socketId].session;
+				console.log("*************", session);
 				if (session.sessionId) {
 					cache.hget('sessions', session.sessionId, (err, session) => {
+						console.log("*************", err, session);
 						if (!err && session) {
+							console.log("*************");
 							db.models.user.findOne({_id: session.userId}, (err, user) => {
+								console.log("*************", err, user.role, station.type, station.owner, session.userId);
 								if (user.role === 'admin') socket.emit("event:stations.created", station);
 								else if (station.type === "community" && station.owner === session.userId) socket.emit("event:stations.created", station);
 							});
@@ -54,10 +61,6 @@ cache.sub('station.create', stationId => {
 				}
 			}
 		}
-
-		function func() {
-			io.io.to('home').emit("event:stations.created", station);
-		}
 	});
 });
 
@@ -187,7 +190,9 @@ module.exports = {
 									description: station.description,
 									displayName: station.displayName,
 									privacy: station.privacy,
-									owner: station.owner
+									partyMode: station.partyMode,
+									owner: station.owner,
+									privatePlaylist: station.privatePlaylist
 								}
 							});
 						});
@@ -326,6 +331,21 @@ module.exports = {
 		});
 	}),
 
+	updatePartyMode: hooks.csOwnerRequired((session, stationId, newPartyMode, cb) => {
+		stations.getStation(stationId, (err, station) => {
+			if (err) return cb({ status: 'failure', message: err });
+			if (station.partyMode === newPartyMode) return cb({ status: 'failure', message: 'The party mode was already ' + ((newPartyMode) ? 'enabled.' : 'disabled.') });
+			db.models.station.update({_id: stationId}, {$set: {partyMode: newPartyMode}}, (err) => {
+				if (err) return cb({ status: 'failure', message: 'Something went wrong when saving the station.' });
+				stations.updateStation(stationId, () => {
+					//TODO Pub/sub for privacy change
+					stations.skipStation(stationId)();
+					cb({ status: 'success', message: 'Successfully updated the party mode.' });
+				})
+			});
+		});
+	}),
+
 	pause: hooks.csOwnerRequired((session, stationId, cb) => {
 		stations.getStation(stationId, (err, station) => {
 			if (err && err !== true) {
@@ -377,9 +397,12 @@ module.exports = {
 	}),
 
 	remove: hooks.csOwnerRequired((session, stationId, cb) => {
-		db.models.station.remove({ _id: stationId });
-		cache.hdel('stations', stationId, () => {
-			return cb({ status: 'success', message: 'Station successfully removed' });
+		db.models.station.remove({ _id: stationId }, (err) => {
+			console.log(err, stationId);
+			if (err) return cb({status: 'failure', message: 'Something went wrong when deleting that station.'});
+			cache.hdel('stations', stationId, () => {
+				return cb({ status: 'success', message: 'Station successfully removed' });
+			});
 		});
 	}),
 
@@ -410,7 +433,8 @@ module.exports = {
 					privacy: 'private',
 					playlist,
 					genres,
-					currentSong: stations.defaultSong
+					currentSong: stations.defaultSong,
+					partyMode: true
 				}, next);
 			}
 
@@ -429,12 +453,15 @@ module.exports = {
 			},
 
 			// check the cache for the station
-			(next) => cache.hget('stations', data._id, next),
+			(next) => {
+				data._id = data._id.toLowerCase();
+				cache.hget('stations', data._id, next)
+			},
 
 			// if the cached version exist
 			(station, next) => {
-				if (station) return next({ 'status': 'failure', 'message': 'A station with that id already exists' });
-				db.models.station.findOne({ _id: data._id }, next);
+				if (station) return next({ 'status': 'failure', 'message': 'A station with that name already exists' });
+				db.models.station.findOne({$or: [{_id: data._id}, {displayName: new RegExp(`^${data.displayName}$`, 'i')}] }, next);
 			},
 
 			(station, next) => {
@@ -444,7 +471,7 @@ module.exports = {
 			},
 
 			(station, userId, next) => {
-				if (station) return next({ 'status': 'failure', 'message': 'A station with that id already exists' });
+				if (station) return next({ 'status': 'failure', 'message': 'A station with that name or display name already exists' });
 				const { _id, displayName, description } = data;
 				db.models.station.create({
 					_id,
@@ -459,7 +486,7 @@ module.exports = {
 			}
 
 		], (err, station) => {
-			if (err) console.error(err);
+			if (err) return cb(err);
 			console.log(err, station);
 			cache.pub('station.create', data._id);
 			return cb({ 'status': 'success', 'message': 'Successfully created station.' });
@@ -539,4 +566,26 @@ module.exports = {
 		});
 	}),
 
+	selectPrivatePlaylist: hooks.csOwnerRequired((session, stationId, playlistId, cb, userId) => {
+		stations.getStation(stationId, (err, station) => {
+			if (err) return cb(err);
+			if (station.type === 'community') {
+				if (station.privatePlaylist === playlistId) return cb({'status': 'failure', 'message': 'That playlist is already selected.'});
+				db.models.playlist.findOne({_id: playlistId}, (err, playlist) => {
+					if (err) return cb(err);
+					if (playlist) {
+						db.models.station.update({_id: stationId}, {$set: {privatePlaylist: playlistId, currentSongIndex: 0}}, (err) => {
+							if (err) return cb(err);
+							stations.updateStation(stationId, (err, station) => {
+								if (err) return cb(err);
+								stations.skipStation(stationId)();
+								cb({'status': 'success', 'message': 'Playlist selected.'});
+							});
+						});
+					} else cb({'status': 'failure', 'message': 'Playlist not found.'});
+				});
+			} else cb({'status': 'failure', 'message': 'That station is not a community station.'});
+		});
+	}),
+
 };

+ 2 - 1
backend/logic/actions/users.js

@@ -14,11 +14,12 @@ module.exports = {
 
 	login: (session, identifier, password, cb) => {
 
+		identifier = identifier.toLowerCase();
+
 		async.waterfall([
 
 			// check if a user with the requested identifier exists
 			(next) => db.models.user.findOne({
-				//TODO Handle lowercase
 				$or: [{ 'username': identifier }, { 'email.address': identifier }]
 			}, next),
 

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

@@ -1,5 +1,5 @@
 module.exports = {
-	_id: { type: String, lowercase: true, max: 16, min: 2, index: true, unique: true, required: true },
+	name: { type: String, lowercase: true, max: 16, min: 2 },
 	displayName: { type: String, min: 2, max: 32, required: true },
 	songs: { type: Array },
 	createdBy: { type: String, required: true },

+ 4 - 2
backend/logic/db/schemas/station.js

@@ -1,7 +1,7 @@
 module.exports = {
 	_id: { type: String, lowercase: true, max: 16, min: 2, index: true, unique: true, required: true },
 	type: { type: String, enum: ["official", "community"], required: true },
-	displayName: { type: String, min: 2, max: 32, required: true },
+	displayName: { type: String, min: 2, max: 32, required: true, unique: true },
 	description: { type: String, min: 2, max: 128, required: true },
 	paused: { type: Boolean, default: false, required: true },
 	currentSong: {
@@ -33,5 +33,7 @@ module.exports = {
 		dislikes: { type: Number, default: -1 },
 		requestedBy: { type: String, required: true }
 	}],
-	owner: { type: String }
+	owner: { type: String },
+	privatePlaylist: { type: String },
+	partyMode: { type: Boolean }
 };

+ 60 - 17
backend/logic/stations.js

@@ -303,23 +303,67 @@ module.exports = {
 									});
 								}
 							} else {
-								if (station.queue.length > 0) {
-									console.log("##");
-									db.models.station.update({_id: stationId}, {$pull: {queue: {songId: station.queue[0]._id}}}, (err) => {
-										console.log("##1", err);
-										if (err) return next(err);
-										let $set = {};
-										$set.currentSong = station.queue[0];
-										$set.startedAt = Date.now();
-										$set.timePaused = 0;
-										if (station.paused) {
-											$set.pausedAt = Date.now();
+								if (station.partyMode === true) {
+									if (station.queue.length > 0) {
+										console.log("##");
+										db.models.station.update({_id: stationId}, {$pull: {queue: {songId: station.queue[0]._id}}}, (err) => {
+											console.log("##1", err);
+											if (err) return next(err);
+											let $set = {};
+											$set.currentSong = station.queue[0];
+											$set.startedAt = Date.now();
+											$set.timePaused = 0;
+											if (station.paused) {
+												$set.pausedAt = Date.now();
+											}
+											next(null, $set);
+										});
+									} else {
+										console.log("##2");
+										next(null, {currentSong: null});
+									}
+								} else {
+									db.models.playlist.findOne({_id: station.privatePlaylist}, (err, playlist) => {
+										console.log(station.privatePlaylist, err, playlist);
+										if (err || !playlist) return next(null, {currentSong: null});
+										playlist = playlist.songs;
+										if (playlist.length > 0) {
+											let $set = {};
+											if (station.currentSongIndex < playlist.length - 1) {
+												$set.currentSongIndex = station.currentSongIndex + 1;
+											} else {
+												$set.currentSongIndex = 0;
+											}
+											songs.getSong(playlist[$set.currentSongIndex]._id, (err, song) => {
+												if (!err && song) {
+													$set.currentSong = {
+														_id: song._id,
+														title: song.title,
+														artists: song.artists,
+														duration: song.duration,
+														likes: song.likes,
+														dislikes: song.dislikes,
+														skipDuration: song.skipDuration,
+														thumbnail: song.thumbnail
+													};
+												} else {
+													let song = playlist[$set.currentSongIndex];
+													$set.currentSong = {
+														_id: song._id,
+														title: song.title,
+														duration: song.duration,
+														likes: -1,
+														dislikes: -1
+													};
+												}
+												$set.startedAt = Date.now();
+												$set.timePaused = 0;
+												next(null, $set);
+											});
+										} else {
+											next(null, {currentSong: null});
 										}
-										next(null, $set);
 									});
-								} else {
-									console.log("##2");
-									next(null, {currentSong: null});
 								}
 							}
 						},
@@ -330,7 +374,7 @@ module.exports = {
 								console.log("##2.5", err);
 								_this.updateStation(station._id, (err, station) => {
 									console.log("##2.6", err);
-									if (station.type === 'community') {
+									if (station.type === 'community' && station.partyMode === true) {
 										cache.pub('station.queueUpdate', stationId);
 									}
 									next(null, station);
@@ -358,7 +402,6 @@ module.exports = {
 								console.log("22", !!(station.currentSong));
 								utils.socketsLeaveSongRooms(io.io.to(`station.${station._id}`).sockets, `song.${station.currentSong._id}`);
 							}
-							console.log(33, null, station, cb);
 							cb(null, station);
 						} else cb(err);
 					});

+ 17 - 0
frontend/components/Modals/EditStation.vue

@@ -40,6 +40,17 @@
 						<a class='button is-info' @click='updatePrivacy()'>Update</a>
 					</p>
 				</div>
+				<div class='control is-grouped' v-if="$parent.type === 'community'">
+					<p class="control is-expanded">
+						<label class="checkbox">
+							<input type="checkbox" v-model="$parent.station.partyMode">
+							Party mode
+						</label>
+					</p>
+					<p class='control'>
+						<a class='button is-info' @click='updatePartyMode()'>Update</a>
+					</p>
+				</div>
 			</section>
 		</div>
 	</div>
@@ -67,6 +78,12 @@
 					if (res.status == 'success') return Toast.methods.addToast(res.message, 4000);
 					Toast.methods.addToast(res.message, 8000);
 				});
+			},
+			updatePartyMode: function () {
+				this.socket.emit('stations.updatePartyMode', this.$parent.stationId, this.$parent.station.partyMode, res => {
+					if (res.status == 'success') return Toast.methods.addToast(res.message, 4000);
+					Toast.methods.addToast(res.message, 8000);
+				});
 			}
 		},
 		ready: function () {

+ 0 - 4
frontend/components/Modals/Playlists/Create.vue

@@ -7,9 +7,6 @@
 				<button class='delete' @click='$parent.toggleModal("createPlaylist")'></button>
 			</header>
 			<section class='modal-card-body'>
-				<p class='control is-expanded'>
-					<input class='input' type='text' placeholder='Playlist ID' v-model='playlist._id'>
-				</p>
 				<p class='control is-expanded'>
 					<input class='input' type='text' placeholder='Playlist Display Name' v-model='playlist.displayName'>
 				</p>
@@ -28,7 +25,6 @@
 		data() {
 			return {
 				playlist: {
-					_id: null,
 					displayName: null,
 					songs: [],
 					createdBy: this.$parent.$parent.username,

+ 2 - 16
frontend/components/Modals/Playlists/Edit.vue

@@ -14,11 +14,11 @@
 							<div class='controls'>
 								<a href='#'>
 									<i class='material-icons' v-if='playlist.songs[0] !== song' @click='promoteSong($index)'>keyboard_arrow_up</i>
-									<i class='material-icons' v-else>error</i>
+									<i class='material-icons' style='opacity: 0' v-else>error</i>
 								</a>
 								<a href='#' @click=''>
 									<i class='material-icons' v-if='playlist.songs.length - 1 !== $index' @click='demoteSong($index)'>keyboard_arrow_down</i>
-									<i class='material-icons' v-else>error</i>
+									<i class='material-icons' style='opacity: 0' v-else>error</i>
 								</a>
 								<a href='#' @click='removeSongFromPlaylist(song._id)'><i class='material-icons'>delete</i></a>
 							</div>
@@ -66,14 +66,6 @@
 						<a class='button is-info' @click='renamePlaylist()'>Rename</a>
 					</p>
 				</div>
-				<div class='control is-grouped'>
-					<p class='control is-expanded'>
-						<input class='input' type='text' placeholder='Playlist ID' v-model='playlist._id'>
-					</p>
-					<p class='control'>
-						<a class='button is-info' @click='renamePlaylistId()'>Rename</a>
-					</p>
-				</div>
 			</section>
 			<footer class='modal-card-foot'>
 				<a class='button is-danger' @click='removePlaylist()'>Remove Playlist</a>
@@ -138,12 +130,6 @@
 					if (res.status == 'success') Toast.methods.addToast(res.message, 3000);
 				});
 			},
-			renamePlaylistId: function () {
-				let _this = this;
-				_this.socket.emit('playlists.updatePlaylistId', _this.playlist.oldId, _this.playlist._id, res => {
-					if (res.status == 'success') _this.playlist = res.data;
-				});
-			},
 			removePlaylist: function () {
 				let _this = this;
 				_this.socket.emit('playlists.remove', _this.playlist._id, res => {

+ 10 - 2
frontend/components/Sidebars/Playlist.vue

@@ -9,7 +9,7 @@
 						<a href='#'>{{ playlist.displayName }}</a>
 						<!--Will play playlist in community station Kris-->
 						<div class='icons-group'>
-							<a href='#' @click=''>
+							<a href='#' @click='selectPlaylist(playlist._id)' v-if="$parent.station && !$parent.station.privatePlaylist === playlist._id">
 								<i class='material-icons'>play_arrow</i>
 							</a>
 							<a href='#' @click='editPlaylist(playlist._id)'>
@@ -28,6 +28,8 @@
 </template>
 
 <script>
+	import { Toast } from 'vue-roaster';
+
 	export default {
 		data() {
 			return {
@@ -35,8 +37,14 @@
 			}
 		},
 		methods: {
-			editPlaylist: function (id) {
+			editPlaylist: function(id) {
 				this.$parent.editPlaylist(id);
+			},
+			selectPlaylist: function(id) {
+				this.socket.emit('stations.selectPrivatePlaylist', this.$parent.stationId, id, (res) => {
+					if (res.status === 'failure') return Toast.methods.addToast(res.message, 8000);
+					Toast.methods.addToast(res.message, 4000);
+				});
 			}
 		},
 		ready: function () {

+ 1 - 1
frontend/components/Station/CommunityHeader.vue

@@ -42,7 +42,7 @@
 		</span>-->
 
 		<div class='nav-right'>
-			<a class='nav-item' href='#' @click='$parent.sidebars.queue = !$parent.sidebars.queue'>
+			<a class='nav-item' href='#' @click='$parent.sidebars.queue = !$parent.sidebars.queue' v-if='$parent.station.partyMode === true'>
 				<span class='icon'>
 					<i class='material-icons'>queue_music</i>
 				</span>

+ 3 - 1
frontend/components/Station/Station.vue

@@ -302,7 +302,9 @@
 								displayName: res.data.displayName,
 								description: res.data.description,
 								privacy: res.data.privacy,
-								owner: res.data.owner
+								partyMode: res.data.partyMode,
+								owner: res.data.owner,
+								privatePlaylist: res.data.privatePlaylist
 							};
 							_this.currentSong = (res.data.currentSong) ? res.data.currentSong : {};
 							_this.type = res.data.type;

+ 14 - 12
frontend/components/pages/Home.vue

@@ -64,21 +64,23 @@
 				_this.socket = _this.$parent.socket;
 				_this.socket.emit("stations.index", data => {
 					if (data.status === "success")  data.stations.forEach(station => {
-					if (!station.currentSong) station.currentSong = { thumbnail: '/assets/notes.png' };
-			console.log(station.privacy);
-					if (station.privacy !== 'public') {
-						console.log(123);
-						station.class = {'station-red': true}
-					} else if (station.type === 'community') {
-						if (station.owner === userId) {
-							station.class = {'station-blue': true}
+						if (!station.currentSong) station.currentSong = { thumbnail: '/assets/notes.png' };
+						console.log(station.privacy);
+						if (station.privacy !== 'public') {
+							console.log(123);
+							station.class = {'station-red': true}
+						} else if (station.type === 'community') {
+							if (station.owner === userId) {
+								station.class = {'station-blue': true}
+							}
 						}
-					}
-					if (station.type == 'official') _this.stations.official.push(station);
-					else _this.stations.community.push(station);
+						if (station.type == 'official') _this.stations.official.push(station);
+						else _this.stations.community.push(station);
+					});
 				});
-				_this.socket.emit("apis.joinRoom", 'home', () => {});
+				_this.socket.emit("apis.joinRoom", 'home', () => {
 					_this.socket.on('event:stations.created', station => {
+						console.log("CREATED!!!", station);
 						if (!station.currentSong) station.currentSong = {thumbnail: '/assets/notes.png'};
 						if (station.privacy !== 'public') {
 							station.class = {'station-red': true}