Explorar o código

Added Admin Dashboard. Still to update the server side

theflametrooper %!s(int64=8) %!d(string=hai) anos
pai
achega
5f74d4282c

+ 2 - 3
backend/app.js

@@ -68,11 +68,10 @@ function setupExpress() {
 		key: 'connect.sid',
 		secret: config.get('secret'),
 		store: mongoStore,
-		success: function (data, accept) {
-			console.log('successful connection to socket.io');
+		success: (data, accept) => {
 			accept();
 		},
-		fail: function (data, message, error, accept) {
+		fail: (data, message, error, accept) => {
 			if (error) throw new Error(message);
 			accept();
 		}

+ 11 - 33
backend/logic/coreHandler.js

@@ -34,7 +34,7 @@ const edmStation = new stations.Station("edm", {
 		{
 			id: "GxBSyx85Kp8",
 			title: "Yeah!",
-			artists: ["Usher"],
+			artists: ["Usher", "test"],
 			duration: '00:00:08',
 			thumbnail: "https://yt3.ggpht.com/-CGlBu6kDEi8/AAAAAAAAAAI/AAAAAAAAAAA/Pi679mvyyyU/s88-c-k-no-mo-rj-c0xffffff/photo.jpg",
 			likes: 0,
@@ -187,40 +187,18 @@ module.exports = {
 		}
 	},
 
-	'/songs/queue/getSongs': (user, cb) => {
-		if (user !== null && user !== undefined && user.logged_in) {
-			global.db.queueSong.find({}, function(err, songs) {
-				if (err) throw err;
-				else cb({songs: songs});
+	'/songs': cb => {
+		let songs = [];
+		cb(stations.getStations().map(station => {
+			station.playlist.forEach(song => {
+				songs.push(song);
 			});
-		} else {
-			cb({err: "Not logged in."});
-		}
+			return songs;
+		}));
 	},
 
-	'/songs/queue/updateSong/:id': (user, id, object, cb) => {
-		if (user !== null && user !== undefined && user.logged_in) {
-			global.db.queueSong.findOne({_id: id}, function(err, song) {
-				if (err) throw err;
-				else {
-					if (song !== undefined && song !== null) {
-						if (typeof object === "object" && object !== null) {
-							delete object.requestedBy;
-							delete object.requestedAt;
-							global.db.queueSong.update({_id: id}, {$set: object}, function(err, song) {
-								if (err) throw err;
-								cb({success: true});
-							});
-						} else {
-							cb({err: "Invalid data."});
-						}
-					} else {
-						cb({err: "Song not found."});
-					}
-				}
-			});
-		} else {
-			cb({err: "Not logged in."});
-		}
+	'/songs/update': (songs, cb) => {
+		console.log(songs);
+		cb(stations.getStations());
 	}
 };

+ 0 - 6
backend/logic/expressHandler.js

@@ -19,10 +19,4 @@ module.exports = (core, app) => {
 		req.logout();
 		res.end();
 	});
-
-	app.post('/stations', (req, res) => {
-		core['/stations'](result => {
-			res.send(result);
-		});
-	});
 };

+ 1 - 2
backend/logic/global.js

@@ -91,8 +91,7 @@ function convertTime(duration) {
     let minutes = Math.floor(duration % 3600 / 60);
     let seconds = Math.floor(duration % 3600 % 60);
 
-	return (hours < 10 ? ("0" + hours + ":") : (hours + ":")) + (minutes < 10 ? ("0" + minutes + ":") : (minutes + ":")) + (seconds < 10 ? ("0" + seconds) : seconds); // 00:00:23
-    // return moment.duration(final, "hh:mm:ss").asSeconds(); // 23
+	return (hours < 10 ? ("0" + hours + ":") : (hours + ":")) + (minutes < 10 ? ("0" + minutes + ":") : (minutes + ":")) + (seconds < 10 ? ("0" + seconds) : seconds);
 }
 
 module.exports = {

+ 14 - 4
backend/logic/socketHandler.js

@@ -13,6 +13,10 @@ module.exports = (core, io) => {
 			console.log('User has disconnected');
 		});
 
+		socket.on('error', err => {
+			console.log(err);
+		});
+
 		socket.on('/stations/join/:id', (id, cb) => {
 			core['/stations/join/:id'](id, result => {
 				_currentStation = id;
@@ -39,14 +43,20 @@ module.exports = (core, io) => {
 			});
 		});
 
-		socket.on('/songs/queue/getSongs', (cb) => {
-			core['/songs/queue/getSongs'](_user, result => {
+		socket.on('/songs', (cb) => {
+			core['/songs'](result => {
+				cb(result[0]);
+			});
+		});
+
+		socket.on('/stations', (cb) => {
+			core['/stations'](result => {
 				cb(result);
 			});
 		});
 
-		socket.on('/songs/queue/updateSong/:id', (id, object, cb) => {
-			core['/songs/queue/updateSong/:id'](_user, id, object, result => {
+		socket.on('/songs/update', (songs, cb) => {
+			core['/songs/update'](songs, result => {
 				cb(result);
 			});
 		});

+ 0 - 2
backend/logic/stations.js

@@ -53,8 +53,6 @@ module.exports = {
 
 				this.currentSong = this.playlist[this.currentSongIndex];
 
-				// console.log(this.currentSong.duration);
-
 				let self = this;
 				this.timer = new global.Timer(() => {
 					self.nextSong();

+ 0 - 8
backend/schema.json

@@ -17,14 +17,6 @@
 	"currentSongIndex": 1,
 	"genres": ["edm"]
   },
-  // Song Item
-  {
-    "id": "dQw4w9WgXcQ",
-    "title": "Never Gonna Give You Up",
-    "artists": ["Rick Astley"],
-    "duration": 254.21,
-    "thumbnail": "https://upload.wikimedia.org/wikipedia/en/3/34/RickAstleyNeverGonnaGiveYouUp7InchSingleCover.jpg"
-  },
 
   // User
   {

+ 3 - 3
backend/schemas/station.js

@@ -9,10 +9,10 @@ module.exports = mongoose => {
 		description: { type: String, min: 2, max: 128, required: true },
 		paused: { type: Boolean, default: false, required: true },
 		currentSong: {
-			id: { type: String, length: 11, index: true, unique: true, required: true },
+			id: { type: String, unique: true, required: true },
 			title: { type: String, required: true },
-			artists: [{ type: String, min: 1 }],
-			duration: { type: Number, required: true },
+			artists: [{ type: String }],
+			duration: { type: String, required: true },
 			thumbnail: { type: String, required: true }
 		},
 		currentSongIndex: { type: Number, default: 0, required: true },

+ 2 - 47
frontend/App.vue

@@ -44,16 +44,8 @@
 				local.loggedIn = status;
 			});
 
-			$.ajax({
-				method: "POST",
-				url: "/stations",
-				contentType: "application/json; charset=utf-8",
-				success: stations => {
-					if (stations) this.stations = stations;
-				},
-				error: err => {
-					if (err) console.log(err);
-				}
+			local.socket.emit("/stations", function(data) {
+				local.stations = data;
 			});
 		},
 		events: {
@@ -122,40 +114,3 @@
 		}
 	}
 </script>
-
-<style lang="sass" scoped>
-	#toasts {
-		position: fixed;
-		z-index: 100000;
-		right: 5%;
-		top: 10%;
-		max-width: 90%;
-
-		.toast {
-			width: 100%;
-			height: auto;
-			padding: 10px 20px;
-			border-radius: 3px;
-			color: white;
-			background-color: #424242;
-			display: -webkit-flex;
-			display: -ms-flexbox;
-			display: flex;
-			margin-bottom: 10px;
-			font-size: 1.18em;
-			font-weight: 400;
-			box-shadow: 0 2px 5px 0 rgba(0,0,0,0.16), 0 2px 10px 0 rgba(0,0,0,0.12);
-			transition: all 0.25s ease;
-		}
-
-		.toast-remove {
-			opacity: 0;
-			margin-top: -50px;
-		}
-
-		.toast-add {
-			opacity: 0;
-			margin-top: 50px;
-		}
-	}
-</style>

+ 75 - 0
frontend/components/AdminSongs.vue

@@ -0,0 +1,75 @@
+<template>
+	<div class="columns is-mobile">
+		<div class="column is-8-desktop is-offset-2-desktop is-12-mobile">
+			<table class="table is-striped">
+				<thead>
+					<tr>
+						<td>YouTube ID</td>
+						<td>Title</td>
+						<td>Thumbnail</td>
+						<td>Artists</td>
+						<td>Options</td>
+					</tr>
+				</thead>
+				<tbody>
+					<tr v-for="(index, song) in songs" track-by="$index">
+						<td>
+							<p class="control">
+								<input class="input" type="text" :value="song.id" v-model="song.id">
+							</p>
+						</td>
+						<td>
+							<p class="control">
+								<input class="input" type="text" :value="song.title" v-model="song.title">
+							</p>
+						</td>
+						<td>
+							<p class="control">
+								<input class="input" type="text" :value="song.thumbnail" v-model="song.thumbnail">
+							</p>
+						</td>
+						<td>
+							<div class="control is-horizontal">
+								<input v-for="artist in song.artists" track-by="$index" class="input" type="text" :value="artist" v-model="artist">
+							</p>
+						</td>
+						<td>
+							<a class="button is-danger" @click="songs.splice(index, 1)">Remove</a>
+						</td>
+					</tr>
+				</tbody>
+			</table>
+			<a class="button is-success" @click="update()">Save Changes</a>
+		</div>
+	</div>
+</template>
+
+<script>
+	export default {
+		data() {
+			return {
+				songs: []
+			}
+		},
+		methods: {
+			update() {
+				this.socket.emit('/songs/update', this.songs, (result) => {
+					console.log(result);
+				});
+			}
+		},
+		ready: function() {
+			let local = this;
+			local.socket = local.$parent.$parent.socket;
+			local.socket.emit("/songs", function(data) {
+				local.songs = data;
+			});
+		}
+	}
+</script>
+
+<style lang="sass" scoped>
+	.is-success {
+		width: 100%;
+	}
+</style>

+ 91 - 0
frontend/components/AdminStations.vue

@@ -0,0 +1,91 @@
+<template>
+	<div class="columns is-mobile">
+		<div class="column is-8-desktop is-offset-2-desktop is-12-mobile">
+			<table class="table is-striped">
+				<thead>
+					<tr>
+						<td>ID</td>
+						<td>Display Name</td>
+						<td>Description</td>
+						<td>Playlist</td>
+						<td>Options</td>
+					</tr>
+				</thead>
+				<tbody>
+					<tr v-for="(index, station) in stations" track-by="$index">
+						<td>
+							<p class="control">
+								<input class="input" type="text" :value="station.id" v-model="station.id">
+							</p>
+						</td>
+						<td>
+							<p class="control">
+								<input class="input" type="text" :value="station.displayName" v-model="station.displayName">
+							</p>
+						</td>
+						<td>
+							<p class="control">
+								<input class="input" type="text" :value="station.description" v-model="station.description">
+							</p>
+						</td>
+						<td>
+							<div class="control is-horizontal">
+								<input v-for="song in station.playlist" track-by="$index" class="input" type="text" :value="song.id" v-model="song.id">
+							</p>
+						</td>
+						<td>
+							<a class="button is-danger" @click="stations.splice(index, 1)">Remove</a>
+						</td>
+					</tr>
+				</tbody>
+			</table>
+			<a class="button is-success" @click="update()">Save Changes</a>
+		</div>
+	</div>
+</template>
+
+<script>
+	export default {
+		data() {
+			return {
+				stations: []
+			}
+		},
+		methods: {
+			// saveQueueSongChanges: function() {
+			// 	let local = this;
+			// 	let songId = local.reviewSongId;
+			// 	let songObject = {};
+			// 	songObject._id = $("#reviewId").val();
+			// 	songObject.title = $("#reviewTitle").val();
+			// 	songObject.artists = $("#reviewArtists").val();
+			// 	songObject.genres = $("#reviewGenres").val();
+			// 	songObject.duration = $("#reviewDuration").val();
+			// 	songObject.skipDuration = $("#reviewSkipDuration").val();
+			// 	songObject.image = $("#reviewImage").val();
+			// 	if (typeof songObject.artists === "string") {
+			// 		songObject.artists = songObject.artists.split(", ");
+			// 	}
+			// 	if (typeof songObject.genres === "string") {
+			// 		songObject.genres = songObject.genres.split(", ");
+			// 	}
+			// 	local.socket.emit("/songs/queue/updateSong/:id", songId, songObject, function(data) {
+			// 		console.log(data);
+			// 	});
+			// }
+		},
+		ready: function() {
+			let local = this;
+			local.socket = local.$parent.$parent.socket;
+			local.socket.emit("/stations", function(data) {
+				local.stations = data;
+			});
+		}
+	}
+</script>
+
+<style lang="sass" scoped>
+	.is-success {
+		width: 100%;
+	}
+</style>

+ 2 - 2
frontend/components/MainHeader.vue

@@ -13,8 +13,8 @@
 		</span>-->
 
 		<div class="nav-right">
-			<a class="nav-item" href="#" v-link="{ path: '/admin/queue' }">
-				Admin Queue
+			<a class="nav-item" href="#" v-link="{ path: '/admin' }">
+				Admin
 			</a>
 			<a class="nav-item" href="#">
 				About

+ 47 - 0
frontend/components/pages/Admin.vue

@@ -0,0 +1,47 @@
+<template>
+	<div class="app">
+		<main-header></main-header>
+		<div class="tabs is-centered">
+			<ul>
+				<li :class="{ 'is-active': currentTab == 'songs' }" @click="showTab('songs')">
+					<a>
+						<span class="icon is-small"><i class="fa fa-music"></i></span>
+						<span>Songs</span>
+					</a>
+				</li>
+				<li :class="{ 'is-active': currentTab == 'stations' }" @click="showTab('stations')">
+					<a>
+						<span class="icon is-small"><i class="fa fa-headphones"></i></span>
+						<span>Stations</span>
+					</a>
+				</li>
+			</ul>
+		</div>
+		<admin-songs v-if="currentTab == 'songs'"></admin-songs>
+		<admin-stations v-if="currentTab == 'stations'"></admin-stations>
+	</div>
+</template>
+
+<script>
+	import MainHeader from '../MainHeader.vue'
+	import MainFooter from '../MainFooter.vue'
+
+	import AdminSongs from '../AdminSongs.vue'
+	import AdminStations from '../AdminStations.vue'
+
+	export default {
+		components: { MainHeader, MainFooter, AdminSongs, AdminStations },
+		data() {
+			return {
+				currentTab: 'songs'
+			}
+		},
+		methods: {
+			showTab: function(tab) {
+				this.currentTab = tab;
+			}
+		}
+	}
+</script>
+
+<style lang="sass" scoped></style>

+ 0 - 122
frontend/components/pages/AdminQueue.vue

@@ -1,122 +0,0 @@
-<template>
-	<div class="app">
-		<main-header></main-header>
-		<div class="row">
-			<div class="col-md-8 col-sm-10 col-xs-12 col-md-offset-2 col-sm-offset-1 card">
-				<table class="table table-striped">
-					<thead>
-					<tr>
-						<td>Title</td>
-						<td>Artists</td>
-						<td>Genre's</td>
-						<td>Controls</td>
-					</tr>
-					</thead>
-					<tbody>
-					<tr v-for="song in songs">
-						<td>{{song.title}}</td>
-						<td>{{song.artists}}</td>
-						<td>{{song.genres}}</td>
-						<td><button @click="reviewSong(song._id)">Review</button></td>
-					</tr>
-					</tbody>
-				</table>
-			</div>
-		</div>
-
-		<main-footer></main-footer>
-		<div class="modal fade" id="reviewModal" tabindex="-1" role="dialog" aria-labelledby="review-modal">
-			<div class="modal-dialog modal-large" role="document">
-				<div class="modal-content">
-					<div class="modal-header">
-						<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
-						<h5 class="modal-title">Review</h5>
-					</div>
-					<div class="modal-body">
-						<label for="reviewId">ID</label>
-						<input type="text" v-bind:value="reviewSongObject._id" id="reviewId"/><br>
-						<label for="reviewTitle">Title</label>
-						<input type="text" v-bind:value="reviewSongObject.title" id="reviewTitle"/><br>
-						<label for="reviewArtists">Artists</label>
-						<input type="text" v-bind:value="reviewSongObject.artists" id="reviewArtists"/><br>
-						<label for="reviewGenres">Genres</label>
-						<input type="text" v-bind:value="reviewSongObject.genres" id="reviewGenres"/><br>
-						<label for="reviewDuration">Duration</label>
-						<input type="number" v-bind:value="reviewSongObject.duration" id="reviewDuration"/><br>
-						<label for="reviewSkipDuration">Skip Duration</label>
-						<input type="number" v-bind:value="reviewSongObject.skipDuration" id="reviewSkipDuration"/><br>
-						<label for="reviewImage">Image</label>
-						<input type="text" v-bind:value="reviewSongObject.image" id="reviewImage"/>
-					</div>
-					<div class="modal-footer">
-						<button type="button" class="btn btn-primary left" @click="saveQueueSongChanges()">Save</button>
-					</div>
-				</div>
-			</div>
-		</div>
-	</div>
-</template>
-
-<script>
-	import MainHeader from '../MainHeader.vue'
-	import MainFooter from '../MainFooter.vue'
-
-	export default {
-		components: { MainHeader, MainFooter },
-		data() {
-			return {
-				songs: [],
-				reviewSongObject: {},
-				reviewSongId: ""
-			}
-		},
-		methods: {
-			reviewSong: function(id) {
-				let local = this;
-				local.reviewSongObject = {};
-				local.reviewSongId = "";
-				local.songs.forEach(function(song) {
-					if (song._id === id) {
-						for (var prop in song) {
-							local.reviewSongObject[prop] = song[prop];
-						}
-						local.reviewSongId = id;
-						$('#reviewModal').modal('show');
-					}
-				});
-			},
-			saveQueueSongChanges: function() {
-				let local = this;
-				let songId = local.reviewSongId;
-				let songObject = {};
-				songObject._id = $("#reviewId").val();
-				songObject.title = $("#reviewTitle").val();
-				songObject.artists = $("#reviewArtists").val();
-				songObject.genres = $("#reviewGenres").val();
-				songObject.duration = $("#reviewDuration").val();
-				songObject.skipDuration = $("#reviewSkipDuration").val();
-				songObject.image = $("#reviewImage").val();
-				if (typeof songObject.artists === "string") {
-					songObject.artists = songObject.artists.split(", ");
-				}
-				if (typeof songObject.genres === "string") {
-					songObject.genres = songObject.genres.split(", ");
-				}
-				local.socket.emit("/songs/queue/updateSong/:id", songId, songObject, function(data) {
-					console.log(data);
-				});
-			}
-		},
-		ready: function() {
-			let local = this;
-			local.socket = this.$parent.socket;
-			local.socket.emit("/songs/queue/getSongs", function(data) {
-				local.songs = data.songs;
-			});
-		}
-	}
-</script>
-
-<style lang="sass">
-
-</style>

+ 0 - 2
frontend/components/pages/Station.vue

@@ -175,13 +175,11 @@
 
 				let duration = (Date.now() - local.currentSong.startedAt - local.timePaused) / 1000;
 				let songDuration = moment.duration(local.currentSong.duration, "hh:mm:ss").asSeconds();
-
 				if (songDuration <= duration) {
 					local.player.pauseVideo();
 				}
 
 				let d = moment.duration(duration, 'seconds');
-
 				if ((!local.paused || local.timeElapsed === "0:00") && duration <= songDuration) {
 					local.timeElapsed = (d.hours() < 10 ? ("0" + d.hours() + ":") : (d.hours() + ":")) + (d.minutes() < 10 ? ("0" + d.minutes() + ":") : (d.minutes() + ":")) + (d.seconds() < 10 ? ("0" + d.seconds()) : d.seconds());
 				}

+ 3 - 3
frontend/main.js

@@ -3,7 +3,7 @@ import VueRouter from 'vue-router';
 import App from './App.vue';
 import Home from './components/pages/Home.vue';
 import Station from './components/pages/Station.vue';
-import AdminQueue from './components/pages/AdminQueue.vue';
+import Admin from './components/pages/Admin.vue';
 
 Vue.use(VueRouter);
 let router = new VueRouter({ history: true });
@@ -15,8 +15,8 @@ router.map({
 	'/station/:id': {
 		component: Station
 	},
-	'/admin/queue': {
-		component: AdminQueue
+	'/admin': {
+		component: Admin
 	}
 });