Selaa lähdekoodia

Added skipvote, improved and fixed station creating and various other bugs.

KrisVos130 8 vuotta sitten
vanhempi
commit
37b4b038a3

+ 49 - 51
backend/logic/actions/stations.js

@@ -31,6 +31,10 @@ cache.sub('station.queueUpdate', stationId => {
 	});
 });
 
+cache.sub('station.voteSkipSong', stationId => {
+	io.io.to(`station.${stationId}`).emit("event:song.voteSkipSong");
+});
+
 cache.sub('station.create', stationId => {
 	stations.initializeStation(stationId, (err, station) => {
 		console.log("*************", err, station);
@@ -177,6 +181,7 @@ module.exports = {
 								station.currentSong.likes = -1;
 								station.currentSong.dislikes = -1;
 							}
+							station.currentSong.skipVotes = station.currentSong.skipVotes.length;
 							cb({
 								status: 'success',
 								data: {
@@ -223,35 +228,24 @@ module.exports = {
 	 * @param session
 	 * @param stationId - the station id
 	 * @param cb
-	 * @return {{ status: String, skipCount: Integer }}
 	 */
-	/*skip: (session, stationId, cb) => {
-
-		if (!session) return cb({ status: 'failure', message: 'You must be logged in to skip a song!' });
-
+	voteSkip: hooks.loginRequired((session, stationId, cb, userId) => {
 		stations.getStation(stationId, (err, station) => {
-			
-			if (err && err !== true) {
-				return cb({ status: 'error', message: 'An error occurred while skipping the station' });
-			}
-
-			if (station) {
-				cache.client.hincrby('station.skipCounts', session.stationId, 1, (err, skipCount) => {
-
-					session.skippedSong = true;
-
-					if (err) return cb({ status: 'error', message: 'An error occurred while skipping the station' });
-
-					cache.hget('station.userCounts', session.stationId, (err, userCount) => {
-						cb({ status: 'success', skipCount });
-					});
-				});
-			}
-			else {
-				cb({ status: 'failure', message: `That station doesn't exist` });
-			}
+			if (err) return cb({ status: 'failure', message: 'Something went wrong when saving the station.' });
+			if (!station.currentSong) return cb({ status: 'failure', message: 'There is currently no song to skip.' });
+			if (station.currentSong.skipVotes.indexOf(userId) !== -1) return cb({ status: 'failure', message: 'You have already voted to skip this song.' });
+			db.models.station.update({_id: stationId}, {$push: {"currentSong.skipVotes": userId}}, (err) => {
+				if (err) return cb({ status: 'failure', message: 'Something went wrong when saving the station.' });
+				stations.updateStation(stationId, (err, station) => {
+					cache.pub('station.voteSkipSong', stationId);
+					if (station.currentSong && station.currentSong.skipVotes.length >= 1) {
+						stations.skipStation(stationId)();
+					}
+					cb({ status: 'success', message: 'Successfully voted to skip the song.' });
+				})
+			});
 		});
-	},*/
+	}),
 
 	forceSkip: hooks.ownerRequired((session, stationId, cb) => {
 		stations.getStation(stationId, (err, station) => {
@@ -405,37 +399,38 @@ module.exports = {
 	}),
 
 	create: hooks.loginRequired((session, data, cb) => {
+		data._id = data._id.toLowerCase();
 		async.waterfall([
 
 			(next) => {
 				return (data) ? next() : cb({ 'status': 'failure', 'message': 'Invalid data' });
 			},
 
-			// check the cache for the station
-			(next) => 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);
+			(next) => {
+				db.models.station.findOne({ $or: [{_id: data._id}, {displayName: new RegExp(`^${data.displayName}$`, 'i')}] }, next);
 			},
 
 			(station, 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, genres, playlist, type } = data;
-				if (type == 'official') {
-					db.models.station.create({
-						_id,
-						displayName,
-						description,
-						type,
-						privacy: 'private',
-						playlist,
-						genres,
-						currentSong: {}
-					}, next);
-				} else if (type == 'community') {
-					cache.hget('sessions', session.sessionId, (err, session) => {
+				cache.hget('sessions', session.sessionId, (err, session) => {
+					if (type == 'official') {
+						db.models.user.findOne({_id: session.userId}, (err, user) => {
+							if (err) return next({ 'status': 'failure', 'message': 'Something went wrong when getting your user info.' });
+							if (!user) return next({ 'status': 'failure', 'message': 'User not found.' });
+							if (user.role !== 'admin') return next({ 'status': 'failure', 'message': 'Admin required.' });
+							db.models.station.create({
+								_id,
+								displayName,
+								description,
+								type,
+								privacy: 'private',
+								playlist,
+								genres,
+								currentSong: stations.defaultSong
+							}, next);
+						});
+					} else if (type == 'community') {
 						db.models.station.create({
 							_id,
 							displayName,
@@ -446,14 +441,17 @@ module.exports = {
 							queue: [],
 							currentSong: null
 						}, next);
-					});
-				}
+					}
+				});
 			}
 
 		], (err, station) => {
-			if (err) console.error(err); cb({ 'status': 'failure', 'message': 'Something went wrong.'});
-			cb(null, { 'status': 'success', 'message': 'Successfully created station.' });
+			if (err) {
+				console.error(err);
+				return cb({ 'status': 'failure', 'message': 'Something went wrong.'});
+			}
 			cache.pub('station.create', data._id);
+			cb({ 'status': 'success', 'message': 'Successfully created station.' });
 		});
 	}),
 

+ 4 - 0
backend/logic/db/index.js

@@ -26,6 +26,10 @@ let lib = {
 				reports: new mongoose.Schema(require(`./schemas/reports`))
 			};
 
+			lib.schemas.station.path('_id').validate((id) => {
+				return /^[a-z]+$/.test(id);
+			}, 'The id can only have the letters a-z.');
+
 			lib.models = {
 				song: mongoose.model('song', lib.schemas.song),
 				queueSong: mongoose.model('queueSong', lib.schemas.queueSong),

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

@@ -1,8 +1,8 @@
 module.exports = {
-	_id: { type: String, lowercase: true, max: 16, min: 2, index: true, unique: true, required: true },
+	_id: { type: String, lowercase: true, maxlength: 16, minlength: 2, index: true, unique: true, required: true },
 	type: { type: String, enum: ["official", "community"], required: true },
-	displayName: { type: String, min: 2, max: 32, required: true, unique: true },
-	description: { type: String, min: 2, max: 128, required: true },
+	displayName: { type: String, minlength: 2, maxlength: 32, required: true, unique: true },
+	description: { type: String, minlength: 2, maxlength: 128, required: true },
 	paused: { type: Boolean, default: false, required: true },
 	currentSong: {
 		_id: { type: String },
@@ -13,6 +13,7 @@ module.exports = {
 		thumbnail: { type: String },
 		likes: { type: Number, default: -1 },
 		dislikes: { type: Number, default: -1 },
+		skipVotes: [{ type: String }],
 	},
 	currentSongIndex: { type: Number, default: 0, required: true },
 	timePaused: { type: Number, default: 0, required: true },

+ 3 - 2
backend/logic/stations.js

@@ -386,6 +386,9 @@ module.exports = {
 					], (err, station) => {
 						console.log("##3", err);
 						if (!err) {
+							if (station.currentSong !== null && station.currentSong._id !== undefined) {
+								station.currentSong.skipVotes = 0;
+							}
 							io.io.to(`station.${station._id}`).emit("event:songs.next", {
 								currentSong: station.currentSong,
 								startedAt: station.startedAt,
@@ -408,8 +411,6 @@ module.exports = {
 				}
 				// the station doesn't exist anymore, unsubscribe from it
 				else {
-					console.log(112233445566, "REMOVE NOTIFICATION");
-					notifications.remove(notification);
 					cb("Station not found.");
 				}
 			});

+ 15 - 1
frontend/build/index.html

@@ -13,7 +13,21 @@
 	<script src="/vendor/jquery.min.js"></script>
 	<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.15.0/moment.min.js"></script>
 	<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/1.4.8/socket.io.min.js"></script>
-	<script src='https://cdn.rawgit.com/atjonathan/lofig/master/dist/lofig.min.js'></script>
+	<script>
+		var lofig = {
+			folder: 'config/default.json',
+			get: function(query, callback) {
+				$.getJSON(this.folder, function(json) {
+					callback(json[query]);
+				});
+			},
+			has: function(query, callback) {
+				$.getJSON(this.folder, function(json) {
+					callback(!!json[query]);
+				});
+			}
+		};
+	</script>
 </head>
 <body>
 	<script src="/bundle.js"></script>

+ 25 - 19
frontend/components/Station/CommunityHeader.vue

@@ -9,33 +9,16 @@
 					<i class='material-icons'>settings</i>
 				</span>
 			</a>
-		</div>
-
-		<!--<div class='nav-center'>
-			{{title}}
-		</div>-->
-
-		<span class="nav-toggle" :class="{ 'is-active': isMobile }" @click="isMobile = !isMobile">
-			<span></span>
-			<span></span>
-			<span></span>
-		</span>
-
-		<div class="nav-right nav-menu" :class="{ 'is-active': isMobile }">
-			<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>
-			</a>
 			<a v-if='isOwner()' class='nav-item' href='#' @click='$parent.skipStation()'>
 				<span class='icon'>
 					<i class='material-icons'>skip_next</i>
 				</span>
 			</a>
-			<a v-if='!isOwner() && $parent.$parent.loggedIn' class='nav-item' href='#' @click='$parent.voteSkipStation()'>
+			<a v-if='!isOwner() && $parent.$parent.loggedIn && $parent.currentSong' class='nav-item' href='#' @click='$parent.voteSkipStation()'>
 				<span class='icon'>
 					<i class='material-icons'>skip_next</i>
 				</span>
+				<span class="skip-votes">{{$parent.currentSong.skipVotes}}</span>
 			</a>
 			<a class='nav-item' href='#' v-if='isOwner() && $parent.paused' @click='$parent.resumeStation()'>
 				<span class='icon'>
@@ -47,6 +30,24 @@
 					<i class='material-icons'>pause</i>
 				</span>
 			</a>
+		</div>
+
+		<!--<div class='nav-center'>
+			{{title}}
+		</div>-->
+
+		<span class="nav-toggle" :class="{ 'is-active': isMobile }" @click="isMobile = !isMobile">
+			<span></span>
+			<span></span>
+			<span></span>
+		</span>
+
+		<div class="nav-right nav-menu" :class="{ 'is-active': isMobile }">
+			<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>
+			</a>
 			<!--<a class='nav-item' href='#'>
 				<span class='icon'>
 					<i class='material-icons'>chat</i>
@@ -107,6 +108,11 @@
 		}
 	}
 
+	.skip-votes {
+		position: relative;
+		left: 11px;
+	}
+
 	.nav-toggle {
 		height: 64px;
 	}

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

@@ -101,7 +101,8 @@
 				simpleSong: false,
 				queue: [],
 				timeBeforePause: 0,
-				station: {}
+				station: {},
+				skipVotes: 0
 			}
 		},
 		methods: {
@@ -239,6 +240,16 @@
 					}
 				});
 			},
+			voteSkipStation: function () {
+				let _this = this;
+				_this.socket.emit('stations.voteSkip', _this.stationId, data => {
+					if (data.status !== 'success') {
+						Toast.methods.addToast(`Error: ${data.message}`, 8000);
+					} else {
+						Toast.methods.addToast('Successfully voted to skip the current song.', 4000);
+					}
+				});
+			},
 			resumeStation: function () {
 				let _this = this;
 				_this.socket.emit('stations.resume', _this.stationId, data => {
@@ -418,6 +429,12 @@
 							this.queue = queue;
 						}
 					});
+
+					_this.socket.on('event:song.voteSkipSong', () => {
+						if (this.currentSong) {
+							this.currentSong.skipVotes++;
+						}
+					});
 					clearInterval(socketInterval);
 				}
 			}, 100);

+ 5 - 1
frontend/components/pages/Home.vue

@@ -27,7 +27,7 @@
 			</div>
 		</div>
 		<div class="group">
-			<div class="group-title">Community Stations <i class="material-icons ccs-button" @click="toggleModal('createCommunityStation')" v-if="$parent.loggedIn">add</i></div>
+			<div class="group-title">Community Stations <i class="material-icons ccs-button" @click="toggleModal('createCommunityStation')" v-if="$parent.loggedIn">add_circle_outline</i></div>
 			<div class="card" v-for="station in stations.community" v-link="{ path: '/community/' + station._id }" @click="this.$dispatch('joinStation', station._id)" :class="station.class">
 				<div class="card-image">
 					<figure class="image is-square">
@@ -154,6 +154,10 @@
 		}
 	}
 
+	.group {
+		min-height: 64px;
+	}
+
 	.ccs-button {
 		cursor: pointer;
 		transition: .25s ease color;