Explorar el Código

Added ratings.

KrisVos130 hace 8 años
padre
commit
08fe061c42

+ 4 - 0
backend/index.js

@@ -8,6 +8,7 @@ const db = require('./logic/db');
 const app = require('./logic/app');
 const io = require('./logic/io');
 const stations = require('./logic/stations');
+const songs = require('./logic/songs');
 const cache = require('./logic/cache');
 const notifications = require('./logic/notifications');
 const config = require('config');
@@ -70,6 +71,9 @@ async.waterfall([
 	// setup the stations
 	(next) => stations.init(next),
 
+	// setup the songs
+	(next) => songs.init(next),
+
 	// setup the frontend for local setups
 	(next) => {
 		if (!config.get("isDocker")) {

+ 154 - 1
backend/logic/actions/songs.js

@@ -1,6 +1,45 @@
 'use strict';
 
 const db = require('../db');
+const io = require('../io');
+const cache = require('../cache');
+const utils = require('../utils');
+
+cache.sub('song.like', (data) => {
+	io.io.to(`song.${data.songId}`).emit('event:song.like', {songId: data.songId, undisliked: data.undisliked});
+	utils.socketsFromUser(data.userId, (sockets) => {
+		sockets.forEach((socket) => {
+			socket.emit('event:song.newRatings', {songId: data.songId, liked: true, disliked: false});
+		});
+	});
+});
+
+cache.sub('song.dislike', (data) => {
+	io.io.to(`song.${data.songId}`).emit('event:song.dislike', {songId: data.songId, unliked: data.unliked});
+	utils.socketsFromUser(data.userId, (sockets) => {
+		sockets.forEach((socket) => {
+			socket.emit('event:song.newRatings', {songId: data.songId, liked: false, disliked: true});
+		});
+	});
+});
+
+cache.sub('song.unlike', (data) => {
+	io.io.to(`song.${data.songId}`).emit('event:song.unlike', {songId: data.songId});
+	utils.socketsFromUser(data.userId, (sockets) => {
+		sockets.forEach((socket) => {
+			socket.emit('event:song.newRatings', {songId: data.songId, liked: false, disliked: false});
+		});
+	});
+});
+
+cache.sub('song.undislike', (data) => {
+	io.io.to(`song.${data.songId}`).emit('event:song.undislike', {songId: data.songId});
+	utils.socketsFromUser(data.userId, (sockets) => {
+		sockets.forEach((socket) => {
+			socket.emit('event:song.newRatings', {songId: data.songId, liked: false, disliked: false});
+		});
+	});
+});
 
 module.exports = {
 
@@ -36,6 +75,120 @@ module.exports = {
 		//TODO Check if video already in songs list
 		//TODO Check if video is in queue
 		//TODO Add the song to the appropriate stations
-	}
+	},
+
+	like: (sessionId, songId, cb) => {
+		cache.hget('sessions', sessionId, (err, session) => {
+			cache.hget('userSessions', session.userSessionId, (err, userSession) => {
+				db.models.user.findOne({_id: userSession.userId}, (err, user) => {
+					if (user.liked.indexOf(songId) !== -1) return cb({ status: 'failure', message: 'You have already liked this song.' });
+					let dislikes = 0;
+					if (user.disliked.indexOf(songId) !== -1) dislikes = -1;
+					db.models.song.update({_id: songId}, {$inc: {likes: 1, dislikes: dislikes}}, (err) => {
+						if (!err) {
+							db.models.user.update({_id: userSession.userId}, {$push: {liked: songId}, $pull: {disliked: songId}}, (err) => {
+								if (!err) {
+									console.log(JSON.stringify({songId, userId: userSession.userId}));
+									cache.pub('song.like', JSON.stringify({songId, userId: userSession.userId, undisliked: (dislikes === -1)}));
+									//TODO Update song object in Redis
+								} else db.models.song.update({_id: songId}, {$inc: {likes: -1, dislikes: -dislikes}}, (err) => {
+									return cb({ status: 'failure', message: 'Something went wrong while liking this song.' });
+								});
+							});
+						} else {
+							return cb({ status: 'failure', message: 'Something went wrong while liking this song.' });
+						}
+					});
+				});
+			});
+		});
+	},
+
+	dislike: (sessionId, songId, cb) => {
+		cache.hget('sessions', sessionId, (err, session) => {
+			cache.hget('userSessions', session.userSessionId, (err, userSession) => {
+				db.models.user.findOne({_id: userSession.userId}, (err, user) => {
+					if (user.disliked.indexOf(songId) !== -1) return cb({ status: 'failure', message: 'You have already disliked this song.' });
+					let likes = 0;
+					if (user.liked.indexOf(songId) !== -1) likes = -1;
+					db.models.song.update({_id: songId}, {$inc: {likes: likes, dislikes: 1}}, (err) => {
+						if (!err) {
+							db.models.user.update({_id: userSession.userId}, {$push: {disliked: songId}, $pull: {liked: songId}}, (err) => {
+								if (!err) {
+									cache.pub('song.dislike', JSON.stringify({songId, userId: userSession.userId, unliked: (likes === -1)}));
+									//TODO Update song object in Redis
+								} else db.models.song.update({_id: songId}, {$inc: {likes: -likes, dislikes: -1}}, (err) => {
+									return cb({ status: 'failure', message: 'Something went wrong while disliking this song.' });
+								});
+							});
+						} else {
+							return cb({ status: 'failure', message: 'Something went wrong while disliking this song.' });
+						}
+					});
+				});
+			});
+		});
+	},
+
+	undislike: (sessionId, songId, cb) => {
+		cache.hget('sessions', sessionId, (err, session) => {
+			cache.hget('userSessions', session.userSessionId, (err, userSession) => {
+				db.models.user.findOne({_id: userSession.userId}, (err, user) => {
+					if (user.disliked.indexOf(songId) === -1) return cb({ status: 'failure', message: 'You have not disliked this song.' });
+					db.models.song.update({_id: songId}, {$inc: {dislikes: -1}}, (err) => {
+						if (!err) {
+							db.models.user.update({_id: userSession.userId}, {$pull: {disliked: songId}}, (err) => {
+								if (!err) {
+									cache.pub('song.undislike', JSON.stringify({songId, userId: userSession.userId}));
+									//TODO Update song object in Redis
+								} else db.models.song.update({_id: songId}, {$inc: {dislikes: 1}}, (err) => {
+									return cb({ status: 'failure', message: 'Something went wrong while undisliking this song.' });
+								});
+							});
+						} else {
+							return cb({ status: 'failure', message: 'Something went wrong while undisliking this song.' });
+						}
+					});
+				});
+			});
+		});
+	},
+
+	unlike: (sessionId, songId, cb) => {
+		cache.hget('sessions', sessionId, (err, session) => {
+			cache.hget('userSessions', session.userSessionId, (err, userSession) => {
+				db.models.user.findOne({_id: userSession.userId}, (err, user) => {
+					if (user.liked.indexOf(songId) === -1) return cb({ status: 'failure', message: 'You have not liked this song.' });
+					db.models.song.update({_id: songId}, {$inc: {likes: -1}}, (err) => {
+						if (!err) {
+							db.models.user.update({_id: userSession.userId}, {$pull: {liked: songId}}, (err) => {
+								if (!err) {
+									cache.pub('song.unlike', JSON.stringify({songId, userId: userSession.userId}));
+									//TODO Update song object in Redis
+								} else db.models.song.update({_id: songId}, {$inc: {likes: 1}}, (err) => {
+									return cb({ status: 'failure', message: 'Something went wrong while unliking this song.' });
+								});
+							});
+						} else {
+							return cb({ status: 'failure', message: 'Something went wrong while unliking this song.' });
+						}
+					});
+				});
+			});
+		});
+	},
+
+	getOwnSongRatings: (sessionId, songId, cb) => {
+		cache.hget('sessions', sessionId, (err, session) => {
+			cache.hget('userSessions', session.userSessionId, (err, userSession) => {
+				db.models.user.findOne({_id: userSession.userId}, (err, user) => {
+					console.log({ status: 'success', songId: songId, liked: (user.liked.indexOf(songId) !== -1), disliked: (user.disliked.indexOf(songId) !== -1) })
+					console.log(user.liked)
+					console.log(user.disliked)
+					return cb({ status: 'success', songId: songId, liked: (user.liked.indexOf(songId) !== -1), disliked: (user.disliked.indexOf(songId) !== -1) });
+				});
+			});
+		});
+	},
 
 };

+ 1 - 0
backend/logic/actions/stations.js

@@ -91,6 +91,7 @@ module.exports = {
 				cache.client.hincrby('station.userCounts', stationId, 1, (err, userCount) => {
 					if (err) return cb({ status: 'error', message: 'An error occurred while joining the station' });
 					utils.socketJoinRoom(sessionId, `station.${stationId}`);
+					utils.socketJoinSongRoom(sessionId, `song.${station.currentSong._id}`);
 					//TODO Emit to cache, listen on cache
 					cb({ status: 'success', currentSong: station.currentSong, startedAt: station.startedAt, paused: station.paused, timePaused: station.timePaused });
 				});

+ 2 - 1
backend/logic/cache/index.js

@@ -13,7 +13,8 @@ const lib = {
 	schemas: {
 		session: require('./schemas/session'),
 		userSession: require('./schemas/userSession'),
-		station: require('./schemas/station')
+		station: require('./schemas/station'),
+		song: require('./schemas/song')
 	},
 
 	/**

+ 5 - 0
backend/logic/cache/schemas/song.js

@@ -0,0 +1,5 @@
+'use strict';
+
+module.exports = (song) => {
+	return song;
+};

+ 2 - 0
backend/logic/db/schemas/user.js

@@ -22,5 +22,7 @@ module.exports = {
 		songsDisliked: [{ type: String, default: '', required: true }],
 		songsLiked: [{ type: String, default: '', required: true }]
 	},
+	liked: [{ type: String }],
+	disliked: [{ type: String }],
 	createdAt: { type: Date, default: Date.now() }
 };

+ 7 - 4
backend/logic/io.js

@@ -32,7 +32,6 @@ module.exports = {
 		});
 
 		this.io.on('connection', socket => {
-			socket.join("SomeRoom");
 			console.info('User has connected');
 
 			// catch when the socket has been disconnected
@@ -97,9 +96,13 @@ module.exports = {
 							if (err && err !== true) socket.emit('ready', false);
 							else if (userSession) {
 								db.models.user.findOne({ _id: userSession.userId }, (err, user) => {
-									let role = 'default';
-									if (user) role = user.role;
-									socket.emit('ready', true, role);
+									let role = '';
+									let username = '';
+									if (user) {
+										role = user.role;
+										username = user.username;
+									}
+									socket.emit('ready', true, role, username);
 								});
 							} else socket.emit('ready', false);
 						});

+ 25 - 0
backend/logic/songs.js

@@ -0,0 +1,25 @@
+'use strict';
+
+const cache = require('./cache');
+const db = require('./db');
+const io = require('./io');
+const utils = require('./utils');
+const async = require('async');
+
+module.exports = {
+
+	init: function(cb) {
+		let _this = this;
+		db.models.song.find({}, (err, songs) => {
+			if (!err) {
+				songs.forEach((song) => {
+					cache.hset('songs', song._id, cache.schemas.song(song));
+				});
+				cb();
+			}
+		});
+	}
+
+	//TODO Add way to get song, which adds song to Redis if not in Redis and returns the song
+
+};

+ 2 - 0
backend/logic/stations.js

@@ -200,6 +200,8 @@ module.exports = {
 								paused: station.paused,
 								timePaused: 0
 							});
+							console.log(io.io.to(`station.${stationId}`).sockets);
+							utils.socketsJoinSongRoom(io.io.to(`station.${stationId}`).sockets, `song.${station.currentSong._id}`);
 							// schedule a notification to be dispatched when the next song ends
 							notifications.schedule(`stations.nextSong?id=${station.id}`, station.currentSong.duration * 1000);
 							skipTimeout = setTimeout(skipSongTemp, station.currentSong.duration * 1000);

+ 56 - 2
backend/logic/utils.js

@@ -1,7 +1,8 @@
 'use strict';
 
 const 	moment = require('moment'),
-		io = require('./io');
+		io = require('./io'),
+		cache = require('./cache');
 
 class Timer {
 	constructor(callback, delay, paused) {
@@ -138,7 +139,36 @@ module.exports = {
 			}
 		}
 	},
-	socketLeaveRooms: function(sessionId, room) {
+	socketsFromUser: function(userId, cb) {
+		let ns = io.io.of("/");
+		let sockets = [];
+		if (ns) {
+			let total = Object.keys(ns.connected).length;
+			let done = 0;
+			for (let id in ns.connected) {
+				let sessionId = ns.connected[id].sessionId;
+				if (sessionId) {
+					cache.hget('sessions', sessionId, (err, session) => {
+						if (!err && session && session.userSessionId) {
+							cache.hget('userSessions', session.userSessionId, (err, userSession) => {
+								if (!err && userSession && userSession.userId === userId) {
+									sockets.push(ns.connected[id]);
+								}
+								checkComplete();
+							})
+						} else checkComplete();
+					});
+				} else checkComplete();
+			}
+			function checkComplete() {
+				done++;
+				if (done === total) {
+					cb(sockets);
+				}
+			}
+		}
+	},
+	socketLeaveRooms: function(sessionId) {
 		let socket = this.socketFromSession(sessionId);
 		let rooms = socket.rooms;
 		for (let j = 0; j < rooms.length; j++) {
@@ -153,5 +183,29 @@ module.exports = {
 			socket.leave(rooms[j]);
 		}
 		socket.join(room);
+	},
+	socketJoinSongRoom: function(sessionId, room) {
+		let socket = this.socketFromSession(sessionId);
+		//console.log(io.io.sockets[socket.id]);
+		let rooms = socket.rooms;
+		for (let j = 0; j < rooms.length; j++) {
+			if (socket.indexOf('song.') !== -1) {
+				socket.leave(rooms[j]);
+			}
+		}
+		socket.join(room);
+	},
+	socketsJoinSongRoom: function(sockets, room) {
+		for (let id in sockets) {
+			let socket = sockets[id];
+			let rooms = socket.rooms;
+			for (let roomId in rooms) {
+				console.log(roomId);
+				if (roomId.indexOf('song.') !== -1) {
+					socket.leave(roomId);
+				}
+			}
+			socket.join(room);
+		}
 	}
 };

+ 3 - 1
frontend/App.vue

@@ -31,6 +31,7 @@
 				},
 				loggedIn: false,
 				role: '',
+				username: '',
 				isRegisterActive: false,
 				isLoginActive: false
 			}
@@ -49,10 +50,11 @@
 		},
 		ready() {
 			let _this = this;
-			auth.getStatus((authenticated, role) => {
+			auth.getStatus((authenticated, role, username) => {
 				_this.socket = window.socket;
 				_this.loggedIn = authenticated;
 				_this.role = role;
+				_this.username = username;
 			});
 		},
 		events: {

+ 5 - 3
frontend/auth.js

@@ -4,22 +4,24 @@ export default {
 
 	ready: false,
 	authenticated: false,
+	username: '',
 	role: 'default',
 
 	getStatus: function(cb) {
 		if (this.ready) {
-			cb(this.authenticated, this.role);
+			cb(this.authenticated, this.role, this.username);
 		} else {
 			callbacks.push(cb);
 		}
 	},
 
-	data: function(authenticated, role) {
+	data: function(authenticated, role, username) {
 		this.authenticated = authenticated;
 		this.role = role;
+		this.username = username;
 		this.ready = true;
 		callbacks.forEach((callback) => {
-			callback(authenticated, role);
+			callback(authenticated, role, username);
 		});
 		callbacks = [];
 	}

+ 3 - 0
frontend/components/MainHeader.vue

@@ -23,6 +23,9 @@
 				News
 			</a>
 			<span class="grouped" v-if="$parent.$parent.loggedIn">
+				<a class="nav-item is-tab" href="#" v-link="{ path: '/u/' + $parent.$parent.username }">
+					Profile
+				</a>
 				<a class="nav-item is-tab" href="#" v-link="{ path: '/settings' }">
 					Settings
 				</a>

+ 84 - 6
frontend/components/Station/Station.vue

@@ -27,9 +27,9 @@
 								</p>
 							</form>
 							<div class="column is-8-mobile is-5-desktop" style="float: right;">
-								<ul id="ratings" v-if="currentSong.likes > -1 && currentSong.dislikes > -1">
-									<li id="like" class="right"><span class="flow-text">{{currentSong.likes}} </span> <i id="thumbs_up" class="material-icons grey-text">thumb_up</i></li>
-									<li style="margin-right: 10px;" id="dislike" class="right"><span class="flow-text">{{currentSong.dislikes}} </span><i id="thumbs_down" class="material-icons grey-text">thumb_down</i></li>
+								<ul id="ratings" v-if="currentSong.likes !== -1 && currentSong.dislikes !== -1">
+									<li id="like" class="right" @click="toggleLike()"><span class="flow-text">{{currentSong.likes}} </span> <i id="thumbs_up" class="material-icons grey-text" v-bind:class="{liked: liked}">thumb_up</i></li>
+									<li style="margin-right: 10px;" id="dislike" class="right" @click="toggleDislike()"><span class="flow-text">{{currentSong.dislikes}} </span><i id="thumbs_down" class="material-icons grey-text" v-bind:class="{disliked: disliked}">thumb_down</i></li>
 								</ul>
 							</div>
 						</div>
@@ -107,7 +107,9 @@
 				queue: [],
 				slideout: {
 					playlist: false
-				}
+				},
+				liked: false,
+				disliked: false
 			}
 		},
 		methods: {
@@ -259,6 +261,30 @@
 						});
 					}
 				});
+			},
+			toggleLike: function() {
+				let _this = this;
+				if (_this.liked) {
+					_this.socket.emit('songs.unlike', _this.currentSong._id, (data) => {
+						console.log(data);
+					});
+				} else {
+					_this.socket.emit('songs.like', _this.currentSong._id, (data) => {
+						console.log(data);
+					});
+				}
+			},
+			toggleDislike: function() {
+				let _this = this;
+				if (_this.disliked) {
+					_this.socket.emit('songs.undislike', _this.currentSong._id, (data) => {
+						console.log(data);
+					});
+				} else {
+					_this.socket.emit('songs.dislike', _this.currentSong._id, (data) => {
+						console.log(data);
+					});
+				}
 			}
 		},
 		ready: function() {
@@ -277,6 +303,13 @@
 					_this.timePaused = data.timePaused;
 					_this.youtubeReady();
 					_this.playVideo();
+					_this.socket.emit('songs.getOwnSongRatings', data.currentSong._id, (data) => {
+						console.log(data);
+						if (_this.currentSong._id === data.songId) {
+							_this.liked = data.liked;
+							_this.disliked = data.disliked;
+						}
+					});
 				} else {
 					//TODO Handle error
 				}
@@ -288,6 +321,13 @@
 				_this.paused = data.paused;
 				_this.timePaused = data.timePaused;
 				_this.playVideo();
+				_this.socket.emit('songs.getOwnSongRatings', data.currentSong._id, (data) => {
+					console.log(data);
+					if (_this.currentSong._id === data.songId) {
+						_this.liked = data.liked;
+						_this.disliked = data.disliked;
+					}
+				});
 			});
 
 			_this.socket.on('event:stations.pause', data => {
@@ -299,6 +339,44 @@
 				_this.resumeLocalStation();
 			});
 
+			_this.socket.on('event:song.like', data => {
+				if (data.songId === _this.currentSong._id) {
+					_this.currentSong.likes++;
+					if (data.undisliked) {
+						_this.currentSong.dislikes--;
+					}
+				}
+			});
+
+			_this.socket.on('event:song.dislike', data => {
+				if (data.songId === _this.currentSong._id) {
+					_this.currentSong.dislikes++;
+					if (data.unliked) {
+						_this.currentSong.likes--;
+					}
+				}
+			});
+
+			_this.socket.on('event:song.unlike', data => {
+				if (data.songId === _this.currentSong._id) {
+					_this.currentSong.likes--;
+				}
+			});
+
+			_this.socket.on('event:song.undislike', data => {
+				if (data.songId === _this.currentSong._id) {
+					_this.currentSong.dislikes--;
+				}
+			});
+
+			_this.socket.on('event:song.newRatings', data => {
+				console.log(data, 1234);
+				if (data.songId === _this.currentSong._id) {
+					_this.liked = data.liked;
+					_this.disliked = data.disliked;
+				}
+			});
+
 			let volume = parseInt(localStorage.getItem("volume"));
 			volume = (typeof volume === "number") ? volume : 20;
 			$("#volumeSlider").val(volume);
@@ -484,11 +562,11 @@
 		float: right;
 	}
 
-	#thumbs_up:hover {
+	#thumbs_up:hover, #thumbs_up.liked {
 		color: #87D37C !important;
 	}
 
-	#thumbs_down:hover {
+	#thumbs_down:hover, #thumbs_down.disliked {
 		color: #EC644B !important;
 	}
 

+ 2 - 2
frontend/main.js

@@ -20,8 +20,8 @@ let _this = this;
 lofig.folder = '../config/default.json';
 lofig.get('socket.url', function(res) {
 	let socket = window.socket = io(window.location.protocol + '//' + res);
-	socket.on("ready", (status, role) => {
-		auth.data(status, role);
+	socket.on("ready", (status, role, username) => {
+		auth.data(status, role, username);
 	});
 });