Browse Source

Worked more on stations.

KrisVos130 8 years ago
parent
commit
9c2179a24f

+ 7 - 2
backend/index.js

@@ -7,6 +7,7 @@ const async = require('async');
 const db = require('./logic/db');
 const app = require('./logic/app');
 const io = require('./logic/io');
+const stations = require('./logic/stations');
 const cache = require('./logic/cache');
 const notifications = require('./logic/notifications');
 const config = require('config');
@@ -17,7 +18,7 @@ async.waterfall([
 	(next) => {
 		cache.init(config.get('redis').url, () => {
 			// load some test stations into the cache
-			async.waterfall([
+			/*async.waterfall([
 				(next) => cache.hset('stations', 'edm', cache.schemas.station({
 					name: 'edm',
 					genres: ['edm'],
@@ -49,7 +50,8 @@ async.waterfall([
 						'gCYcHz2k5x0'
 					]
 				}), next),
-			], next);
+			], next);*/
+			next();
 		});
 	},
 
@@ -65,6 +67,9 @@ async.waterfall([
 	// setup the notifications
 	(next) => notifications.init(config.get('redis').url, next),
 
+	// setup the stations
+	(next) => stations.init(next),
+
 	// setup the frontend for local setups
 	(next) => {
 		if (!config.get("isDocker")) {

+ 60 - 86
backend/logic/actions/stations.js

@@ -9,101 +9,37 @@ const db = require('../db');
 const cache = require('../cache');
 const notifications = require('../notifications');
 const utils = require('../utils');
+const stations = require('../stations');
+const defaultSong = {
+	_id: '60ItHLz5WEA',
+	title: 'Faded',
+	artists: ['Alan Walker'],
+	duration: 212,
+	skipDuration: 0,
+	thumbnail: 'https://i.scdn.co/image/2ddde58427f632037093857ebb71a67ddbdec34b'
+};
 
-notifications.subscribe('station.locked', function(stationName) {
+cache.sub('station.locked', (stationName) => {
 	io.to(`station.${stationName}`).emit("event:station.locked");
 });
 
-notifications.subscribe('station.unlocked', function(stationName) {
+cache.sub('station.unlocked', (stationName) => {
 	io.to(`station.${stationName}`).emit("event:station.unlocked");
 });
 
-notifications.subscribe('station.pause', function(stationName) {
+cache.sub('station.pause', (stationName) => {
 	io.to(`station.${stationName}`).emit("event:station.pause");
 });
 
-notifications.subscribe('station.resume', function(stationName) {
+cache.sub('station.resume', (stationName) => {
 	io.to(`station.${stationName}`).emit("event:station.resume");
 });
 
-/**
- * Loads a station into the cache, and sets up all the related logic
- *
- * @param {String} stationId - the id of the station
- * @param {Function} cb - gets called when this function completes
- */
-function initializeAndReturnStation (stationId, cb) {
-	async.waterfall([
-
-		// first check the cache for the station
-		(next) => cache.hget('stations', stationId, next),
-
-		// if the cached version exist
-		(station, next) => {
-			if (station) return next(true, station);
-			db.models.station.findOne({ id: stationId }, next);
-		},
-
-		// if the station exists in the DB, add it to the cache
-		(station, next) => {
-			if (!station) return cb('Station by that id does not exist');
-			station = cache.schemas.station(station);
-			cache.hset('stations', station.id, station, (err) => next(err, station));
-		}
-
-	], (err, station) => {
-		if (err && err !== true) return cb(err);
-
-		// get notified when the next song for this station should play, so that we can notify our sockets
-		let notification = notifications.subscribe(`stations.nextSong?id=${station.id}`, () => {
-			// get the station from the cache
-			console.log('NOTIFICATION');
-			cache.hget('stations', station.name, (err, station) => {
-				if (station) {
-					console.log(777);
-					// notify all the sockets on this station to go to the next song
-					io.to(`station.${stationId}`).emit("event:songs.next", {
-						currentSong: station.currentSong,
-						startedAt: station.startedAt,
-						paused: station.paused,
-						timePaused: 0
-					});
-					// schedule a notification to be dispatched when the next song ends
-					notifications.schedule(`stations.nextSong?id=${station.id}`, station.currentSong.duration * 1000);
-				}
-				// the station doesn't exist anymore, unsubscribe from it
-				else {
-					console.log(888);
-					notifications.remove(notification);
-				}
-			});
-		}, true);
-
-		if (!station.paused) {
-			console.log(station);
-			notifications.schedule(`stations.nextSong?id=${station.id}`, station.currentSong.duration * 1000);
-		}
-
-		return cb(null, station);
-
-		// will need to be added once station namespace thing is decided
-		// function generatePlaylist(arr) {
-		// 	station.playlist = [];
-		// 	return arr.reduce((promise, id) => {
-		// 		return promise.then(() => {
-		// 			return globals.db.models.song.findOne({ id }, (err, song) => {
-		// 				if (err) throw err;
-		// 				station.playlist.push(song);
-		// 			});
-		// 		});
-		// 	}, Promise.resolve());
-		// }
-
-		// generatePlaylist(station.playlist).then(() => {
-		// 	cb(null, station);
-		// });
+cache.sub('station.create', (stationId) => {
+	stations.initializeAndReturnStation(stationId, () => {
+		//TODO Emit to homepage and admin station page
 	});
-}
+});
 
 module.exports = {
 
@@ -145,7 +81,7 @@ module.exports = {
 	 */
 	join: (sessionId, stationId, cb) => {
 
-		initializeAndReturnStation(stationId, (err, station) => {
+		stations.initializeAndReturnStation(stationId, (err, station) => {
 
 			if (err && err !== true) {
 				return cb({ status: 'error', message: 'An error occurred while joining the station' });
@@ -180,7 +116,7 @@ module.exports = {
 
 		if (!session) return cb({ status: 'failure', message: 'You must be logged in to skip a song!' });
 
-		initializeAndReturnStation(stationId, (err, station) => {
+		stations.initializeAndReturnStation(stationId, (err, station) => {
 
 			if (err && err !== true) {
 				return cb({ status: 'error', message: 'An error occurred while skipping the station' });
@@ -213,7 +149,7 @@ module.exports = {
 	 */
 	leave: (session, cb) => {
 		let stationId = "edm";
-		initializeAndReturnStation(stationId, (err, station) => {
+		stations.initializeAndReturnStation(stationId, (err, station) => {
 
 			if (err && err !== true) {
 				return cb({ status: 'error', message: 'An error occurred while leaving the station' });
@@ -234,7 +170,7 @@ module.exports = {
 
 	lock: (session, stationId, cb) => {
 		//TODO Require admin
-		initializeAndReturnStation(stationId, (err, station) => {
+		stations.initializeAndReturnStation(stationId, (err, station) => {
 			if (err && err !== true) {
 				return cb({ status: 'error', message: 'An error occurred while locking the station' });
 			} else if (station) {
@@ -248,7 +184,7 @@ module.exports = {
 
 	unlock: (session, stationId, cb) => {
 		//TODO Require admin
-		initializeAndReturnStation(stationId, (err, station) => {
+		stations.initializeAndReturnStation(stationId, (err, station) => {
 			if (err && err !== true) {
 				return cb({ status: 'error', message: 'An error occurred while unlocking the station' });
 			} else if (station) {
@@ -260,4 +196,42 @@ module.exports = {
 		});
 	},
 
+	create: (sessionId, data, cb) => {
+		//TODO Require admin
+		async.waterfall([
+
+			(next) => {
+				return (data) ? next() : cb({ 'status': 'failure', 'message': 'Invalid data.' });
+			},
+
+			// check the cache for the station
+			(next) => cache.hget('stations', data.name, next),
+
+			// if the cached version exist
+			(station, next) => {
+				if (station) return next({ 'status': 'failure', 'message': 'A station with that name already exists.' });
+				db.models.station.findOne({ _id: data.name }, next);
+			},
+
+			(station, next) => {
+				if (station) return next({ 'status': 'failure', 'message': 'A station with that name already exists.' });
+				db.models.station.create({
+					_id: data.name,
+					displayName: data.displayName,
+					description: data.description,
+					type: "official",
+					playlist: [defaultSong._id],
+					locked: true,
+					currentSong: defaultSong
+				}, next);
+			}
+
+		], (err, station) => {
+			console.log(err, 123986);
+			if (err) return cb(err);
+			cache.pub('station.create', data.name);
+			return cb(null, { 'status': 'success', 'message': 'Successfully created station.' });
+		});
+	},
+
 };

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

@@ -143,7 +143,7 @@ const lib = {
 				if (parseJson) try { message = JSON.parse(message); } catch (e) {}
 				subs[channel].cbs.forEach((cb) => cb(message));
 			});
-			subs[channel].subscribe(channel);
+			subs[channel].client.subscribe(channel);
 		}
 
 		subs[channel].cbs.push(cb);

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

@@ -1,14 +1,15 @@
 module.exports = {
-	id: { type: String, lowercase: true, max: 16, min: 2, index: true, unique: true, required: true },
+	_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 },
 	description: { type: String, min: 2, max: 128, required: true },
 	paused: { type: Boolean, default: false, required: true },
 	currentSong: {
-		id: { type: String, unique: true, required: true },
+		_id: { type: String, unique: true, required: true },
 		title: { type: String, required: true },
 		artists: [{ type: String }],
-		duration: { type: String, required: true },
+		duration: { type: Number, required: true },
+		skipDuration: { type: Number, required: true },
 		thumbnail: { type: String, required: true }
 	},
 	currentSongIndex: { type: Number, default: 0, required: true },

+ 0 - 1
backend/logic/io.js

@@ -12,7 +12,6 @@ module.exports = {
 	io: null,
 
 	init: (cb) => {
-
 		//TODO Check every 30s/60s, for all sockets, if they are still allowed to be in the rooms they are in, and on socket at all (permission changing/banning)
 		this.io = require('socket.io')(app.server);
 

+ 103 - 0
backend/logic/stations.js

@@ -0,0 +1,103 @@
+'use strict';
+
+// This file contains all the logic for Socket.IO
+
+const cache = require('./cache');
+const db = require('./db');
+const utils = require('./utils');
+const notifications = require('./notifications');
+const async = require('async');
+
+module.exports = {
+
+	init: function(cb) {
+		let _this = this;
+		console.log("Init stations");
+		db.models.station.find({}, (err, stations) => {
+			if (!err) {
+				stations.forEach((station) => {
+					console.log("Initing " + station._id);
+					_this.initializeAndReturnStation(station._id, (err, station) => {
+						console.log(err, station, 123456789);
+						//TODO Emit to homepage and admin station list
+					});
+				});
+				cb();
+			}
+		});
+	},
+
+	initializeAndReturnStation: (stationId, cb) => {
+		async.waterfall([
+
+			// first check the cache for the station
+			(next) => cache.hget('stations', stationId, next),
+
+			// if the cached version exist
+			(station, next) => {
+				if (station) return next(true, station);
+				db.models.station.findOne({ _id: stationId }, next);
+			},
+
+			// if the station exists in the DB, add it to the cache
+			(station, next) => {
+				if (!station) return cb('Station by that id does not exist');
+				station = cache.schemas.station(station);
+				cache.hset('stations', station._id, station, (err) => next(err, station));
+			}
+
+		], (err, station) => {
+			if (err && err !== true) return cb(err);
+
+			// get notified when the next song for this station should play, so that we can notify our sockets
+			let notification = notifications.subscribe(`stations.nextSong?id=${station._id}`, () => {
+				// get the station from the cache
+				console.log('NOTIFICATION');
+				cache.hget('stations', station.name, (err, station) => {
+					if (station) {
+						console.log(777);
+						// notify all the sockets on this station to go to the next song
+						io.to(`station.${stationId}`).emit("event:songs.next", {
+							currentSong: station.currentSong,
+							startedAt: station.startedAt,
+							paused: station.paused,
+							timePaused: 0
+						});
+						// schedule a notification to be dispatched when the next song ends
+						notifications.schedule(`stations.nextSong?id=${station.id}`, station.currentSong.duration * 1000);
+					}
+					// the station doesn't exist anymore, unsubscribe from it
+					else {
+						console.log(888);
+						notifications.remove(notification);
+					}
+				});
+			}, true);
+
+			if (!station.paused) {
+				console.log(station);
+				notifications.schedule(`stations.nextSong?id=${station.id}`, station.currentSong.duration * 1000);
+			}
+
+			return cb(null, station);
+
+			// will need to be added once station namespace thing is decided
+			// function generatePlaylist(arr) {
+			// 	station.playlist = [];
+			// 	return arr.reduce((promise, id) => {
+			// 		return promise.then(() => {
+			// 			return globals.db.models.song.findOne({ id }, (err, song) => {
+			// 				if (err) throw err;
+			// 				station.playlist.push(song);
+			// 			});
+			// 		});
+			// 	}, Promise.resolve());
+			// }
+
+			// generatePlaylist(station.playlist).then(() => {
+			// 	cb(null, station);
+			// });
+		});
+	}
+
+};

+ 1 - 1
frontend/build/index.css

@@ -4,5 +4,5 @@ body {
 
 .card {
 	background-color: white;
-	padding: 20px;
+	/*padding: 20px;*/
 }

+ 50 - 2
frontend/components/Admin/Stations.vue

@@ -42,13 +42,53 @@
 			<a class="button is-success" @click="update()">Save Changes</a>
 		</div>
 	</div>
+	<div class="columns is-mobile">
+		<div class="column is-8-desktop is-offset-2-desktop is-12-mobile">
+			<div class="card is-fullwidth">
+				<header class="card-header">
+					<p class="card-header-title">Create official station</p>
+				</header>
+				<div class="card-content">
+					<div class="content">
+						<label class="label">Name</label>
+						<div class="control is-horizontal">
+							<div class="control is-grouped">
+								<p class="control is-expanded">
+									<input class="input" type="text" placeholder="Locale name" v-model="newStation.name">
+								</p>
+								<p class="control is-expanded">
+									<input class="input" type="text" placeholder="Display name" v-model="newStation.displayName">
+								</p>
+							</div>
+						</div>
+						<label class="label">Description</label>
+						<p class="control is-expanded">
+							<input class="input" type="text" placeholder="Short description" v-model="newStation.description">
+						</p>
+						<label class="label">Genres</label>
+						<p class="control has-addons">
+							<input class="input" type="text" placeholder="Genre" v-model="newStationGenre">
+							<a class="button is-info">Add genre</a>
+						</p>
+						<span class="tag is-info">Bar<button class="delete is-info"></button></span>
+						<span class="tag is-info">Bar<button class="delete is-info"></button></span>
+						<span class="tag is-info">Bar<button class="delete is-info"></button></span>
+					</div>
+				</div>
+				<footer class="card-footer">
+					<a class="card-footer-item" @click="createStation()">Create</a>
+				</footer>
+			</div>
+		</div>
+	</div>
 </template>
 
 <script>
 	export default {
 		data() {
 			return {
-				stations: []
+				stations: [],
+				newStation: {}
 			}
 		},
 		methods: {
@@ -72,7 +112,15 @@
 			// 	local.socket.emit("/songs/queue/updateSong/:id", songId, songObject, function(data) {
 			// 		console.log(data);
 			// 	});
-			// }
+			// },
+			createStation: function() {
+				let _this = this;
+				let { newStation: { name, displayName, description } } = this;
+				let data = { name, displayName, description, genres: ['edm'] };
+				_this.socket.emit('stations.create', data, result => {
+					console.log(result);
+				});
+			}
 		},
 		ready: function() {
 			let socket = this.socket = this.$parent.$parent.socket;