Browse Source

Merge branch 'polishing' of https://github.com/Musare/MusareNode into polishing

Kristian Vos 3 years ago
parent
commit
fc466a8338

+ 22 - 4
backend/index.js

@@ -225,20 +225,37 @@ if (config.debug && config.debug.traceUnhandledPromises === true) {
 // }
 
 class JobManager {
+	// eslint-disable-next-line require-jsdoc
 	constructor() {
 		this.runningJobs = {};
 	}
 
+	/**
+	 * Adds a job to the list of running jobs
+	 *
+	 * @param {object} job - the job object
+	 */
 	addJob(job) {
 		if (!this.runningJobs[job.module.name]) this.runningJobs[job.module.name] = {};
 		this.runningJobs[job.module.name][job.toString()] = job;
 	}
 
+	/**
+	 * Removes a job from the list of running jobs (after it's completed)
+	 *
+	 * @param {object} job - the job object
+	 */
 	removeJob(job) {
 		if (!this.runningJobs[job.module.name]) this.runningJobs[job.module.name] = {};
 		delete this.runningJobs[job.module.name][job.toString()];
 	}
 
+	/**
+	 * Returns detail about a job via a identifier
+	 *
+	 * @param {string} uuid - the job identifier
+	 * @returns {object} - the job object
+	 */
 	getJob(uuid) {
 		let job = null;
 		Object.keys(this.runningJobs).forEach(moduleName => {
@@ -343,7 +360,8 @@ class ModuleManager {
 
 			this.log(
 				"INFO",
-				`Initialized: ${Object.keys(this.modules).length - this.modulesNotInitialized.length}/${Object.keys(this.modules).length
+				`Initialized: ${Object.keys(this.modules).length - this.modulesNotInitialized.length}/${
+					Object.keys(this.modules).length
 				}.`
 			);
 
@@ -465,7 +483,8 @@ process.stdin.on("data", data => {
 			console.log(
 				`${moduleName.toUpperCase()}${Array(tabsNeeded).join(
 					"\t"
-				)}${module.getStatus()}. Jobs in queue: ${module.jobQueue.lengthQueue()}. Jobs in progress: ${module.jobQueue.lengthRunning()}. Jobs paused: ${module.jobQueue.lengthPaused()} Concurrency: ${module.jobQueue.concurrency
+				)}${module.getStatus()}. Jobs in queue: ${module.jobQueue.lengthQueue()}. Jobs in progress: ${module.jobQueue.lengthRunning()}. Jobs paused: ${module.jobQueue.lengthPaused()} Concurrency: ${
+					module.jobQueue.concurrency
 				}. Stage: ${module.getStage()}`
 			);
 		});
@@ -501,8 +520,7 @@ process.stdin.on("data", data => {
 		const parts = command.split(" ");
 
 		const uuid = parts[1];
-		let jobFound = moduleManager.jobManager.getJob(uuid);
-
+		const jobFound = moduleManager.jobManager.getJob(uuid);
 
 		if (jobFound) {
 			let topParent = jobFound;

+ 14 - 9
backend/logic/actions/songs.js

@@ -571,7 +571,6 @@ export default {
 	// 			},
 
 	// 			(res, next) => {
-	// 				// TODO Check if res gets returned from above
 	// 				CacheModule.runJob("HDEL", { table: "songs", key: songId }, this)
 	// 					.then(() => {
 	// 						next();
@@ -769,8 +768,8 @@ export default {
 				},
 
 				(song, next) => {
-					song.acceptedBy = session.userId;
-					song.acceptedAt = Date.now();
+					song.verifiedBy = session.userId;
+					song.verifiedAt = Date.now();
 					song.status = "verified";
 					song.save(err => {
 						next(err, song);
@@ -1001,8 +1000,8 @@ export default {
 
 	// 			next => {
 	// 				const newSong = new SongModel(song);
-	// 				newSong.acceptedBy = session.userId;
-	// 				newSong.acceptedAt = Date.now();
+	// 				newSong.verifiedBy = session.userId;
+	// 				newSong.verifiedAt = Date.now();
 	// 				newSong.save(next);
 	// 			},
 
@@ -1091,8 +1090,12 @@ export default {
 							this
 						)
 						.then(res => {
-							if (res.status === "error")
+							if (res.status === "error") {
+								if (res.message === "That song is already in the playlist")
+									return next("You have already liked this song.");
 								return next("Unable to add song to the 'Liked Songs' playlist.");
+							}
+
 							return next(null, song, user.dislikedSongsPlaylist);
 						})
 						.catch(err => next(err));
@@ -1165,8 +1168,6 @@ export default {
 		);
 	}),
 
-	// TODO: ALready liked/disliked etc.
-
 	/**
 	 * Dislikes a song
 	 *
@@ -1206,8 +1207,12 @@ export default {
 							this
 						)
 						.then(res => {
-							if (res.status === "error")
+							if (res.status === "error") {
+								if (res.message === "That song is already in the playlist")
+									return next("You have already disliked this song.");
 								return next("Unable to add song to the 'Disliked Songs' playlist.");
+							}
+
 							return next(null, song, user.likedSongsPlaylist);
 						})
 						.catch(err => next(err));

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

@@ -541,7 +541,6 @@ CacheModule.runJob("SUB", {
 				args: ["event:admin.station.created", { data: { station } }]
 			}).then(() => {});
 
-			// TODO If community, check if on whitelist
 			if (station.privacy === "public")
 				WSModule.runJob("EMIT_TO_ROOM", {
 					room: "home",

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

@@ -1339,14 +1339,13 @@ export default {
 			});
 	}),
 
-	// TODO Fix security issues
 	/**
 	 * Gets user info from session
 	 *
 	 * @param {object} session - the session object automatically added by the websocket
 	 * @param {Function} cb - gets called with the result
 	 */
-	async findBySession(session, cb) {
+	findBySession: isLoginRequired(async function findBySession(session, cb) {
 		const userModel = await DBModule.runJob("GET_MODEL", { modelName: "user" }, this);
 
 		async.waterfall(
@@ -1406,7 +1405,7 @@ export default {
 				});
 			}
 		);
-	},
+	}),
 
 	/**
 	 * Updates a user's username

+ 0 - 1
backend/logic/activities.js

@@ -34,7 +34,6 @@ class _ActivitiesModule extends CoreClass {
 		});
 	}
 
-	// TODO: Migrate
 	/**
 	 * Adds a new activity to the database
 	 *

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

@@ -12,7 +12,7 @@ const REQUIRED_DOCUMENT_VERSIONS = {
 	punishment: 1,
 	queueSong: 1,
 	report: 2,
-	song: 4,
+	song: 5,
 	station: 5,
 	user: 3
 };

+ 2 - 2
backend/logic/db/schemas/song.js

@@ -11,8 +11,8 @@ export default {
 	explicit: { type: Boolean },
 	requestedBy: { type: String },
 	requestedAt: { type: Date },
-	acceptedBy: { type: String }, // TODO Should be verifiedBy
-	acceptedAt: { type: Date }, // TODO Should be verifiedAt
+	verifiedBy: { type: String },
+	verifiedAt: { type: Date },
 	discogs: { type: Object },
 	status: { type: String, required: true, default: "hidden", enum: ["hidden", "unverified", "verified"] },
 	documentVersion: { type: Number, default: 5, required: true }

+ 3 - 9
backend/logic/migration/index.js

@@ -42,9 +42,7 @@ class _MigrationModule extends CoreClass {
 					useCreateIndex: true
 				})
 				.then(async () => {
-					mongoose.connection.on("error", err => {
-						this.log("ERROR", err);
-					});
+					mongoose.connection.on("error", err => this.log("ERROR", err));
 
 					mongoose.connection.on("disconnected", () => {
 						this.log("ERROR", "Disconnected, going to try to reconnect...");
@@ -83,12 +81,8 @@ class _MigrationModule extends CoreClass {
 						1,
 						(index, next) => {
 							MigrationModule.runJob("RUN_MIGRATION", { index: index + 1 }, null, -1)
-								.then(() => {
-									next();
-								})
-								.catch(err => {
-									next(err);
-								});
+								.then(() => next())
+								.catch(err => next(err));
 						},
 						err => {
 							if (err) console.log("Migration error", err);

+ 59 - 0
backend/logic/migration/migrations/migration11.js

@@ -0,0 +1,59 @@
+import async from "async";
+
+/**
+ * Migration 11
+ *
+ * Migration for changing language of verifying a song from 'accepted' to 'verified' for songs
+ *
+ * @param {object} MigrationModule - the MigrationModule
+ * @returns {Promise} - returns promise
+ */
+export default async function migrate(MigrationModule) {
+	const songModel = await MigrationModule.runJob("GET_MODEL", { modelName: "song" }, this);
+
+	return new Promise((resolve, reject) => {
+		async.waterfall(
+			[
+				next => {
+					this.log("INFO", `Migration 11. Finding songs with document version 4.`);
+					songModel.find({ documentVersion: 4 }, (err, songs) => {
+						if (err) next(err);
+						else {
+							async.eachLimit(
+								songs.map(songi => songi._doc),
+								1,
+								(songi, next) =>
+									songModel.updateOne(
+										{ _id: songi._id },
+										{
+											$set: {
+												verifiedBy: songi.acceptedBy,
+												verifiedAt: songi.acceptedAt,
+												documentVersion: 5
+											},
+											$unset: {
+												acceptedBy: "",
+												acceptedAt: ""
+											}
+										},
+										next
+									),
+								err => {
+									if (err) next(err);
+									else {
+										this.log("INFO", `Migration 11. Songs found: ${songs.length}.`);
+										next();
+									}
+								}
+							);
+						}
+					});
+				}
+			],
+			err => {
+				if (err) reject(new Error(err));
+				else resolve();
+			}
+		);
+	});
+}

+ 1 - 1
frontend/dist/index.tpl.html

@@ -36,7 +36,7 @@
 	<link rel='stylesheet' href='/index.css'>
 	<script src='https://www.youtube.com/iframe_api'></script>
 	<script type='text/javascript' src='/vendor/can-autoplay.min.js'></script>
-	<script type='text/javascript' src='/vendor/lofig.1.3.3.min.js'></script>
+	<script type='text/javascript' src='/vendor/lofig.1.3.4.min.js'></script>
 </head>
 <body>
 	<div id="root"></div>

+ 0 - 0
frontend/dist/vendor/lofig.1.3.3.min.js → frontend/dist/vendor/lofig.1.3.4.min.js


+ 3 - 3
frontend/package-lock.json

@@ -10547,9 +10547,9 @@
       }
     },
     "toasters": {
-      "version": "2.3.0",
-      "resolved": "https://registry.npmjs.org/toasters/-/toasters-2.3.0.tgz",
-      "integrity": "sha512-3BaJ9SYFOL9VNh9YXqUIpR5beX+qLit35xi7iSMLi6CH8GR/8RCEz5EaWeCcH+Z1VD6whv0amPLtBWWGgkoiSw=="
+      "version": "2.3.1",
+      "resolved": "https://registry.npmjs.org/toasters/-/toasters-2.3.1.tgz",
+      "integrity": "sha512-2u7xuSf0MDCt4SwDRWOe9gzOYLyB0s3hvS6FPLaxUXVmRrjLZOhOXGimHq6BxWpplBGo+xJA+Ifhcx+asSDKUA=="
     },
     "toidentifier": {
       "version": "1.0.0",

+ 1 - 1
frontend/package.json

@@ -49,7 +49,7 @@
     "eslint-config-airbnb-base": "^13.2.0",
     "html-webpack-plugin": "^5.3.1",
     "marked": "^2.0.3",
-    "toasters": "^2.3.0",
+    "toasters": "^2.3.1",
     "vue": "^2.6.12",
     "vue-content-loader": "^0.2.3",
     "vue-loader": "^15.9.6",

+ 48 - 46
frontend/src/App.vue

@@ -396,10 +396,6 @@ a {
 					p {
 						color: var(--white);
 					}
-
-					.checkbox-control label span {
-						background-color: var(--dark-grey-2);
-					}
 				}
 			}
 		}
@@ -537,7 +533,7 @@ a {
 	&.songActions-theme,
 	&.addToPlaylist-theme {
 		.tippy-arrow {
-			border-top-color: var(--light-grey-3);
+			border-top-color: var(--white);
 		}
 	}
 	&.confirm-theme .tippy-arrow {
@@ -548,7 +544,7 @@ a {
 	&.songActions-theme,
 	&.addToPlaylist-theme {
 		.tippy-arrow {
-			border-bottom-color: var(--light-grey-3);
+			border-bottom-color: var(--white);
 		}
 	}
 	&.confirm-theme .tippy-arrow {
@@ -559,7 +555,7 @@ a {
 	&.songActions-theme,
 	&.addToPlaylist-theme {
 		.tippy-arrow {
-			border-left-color: var(--light-grey-3);
+			border-left-color: var(--white);
 		}
 	}
 	&.confirm-theme .tippy-arrow {
@@ -570,7 +566,7 @@ a {
 	&.songActions-theme,
 	&.addToPlaylist-theme {
 		.tippy-arrow {
-			border-right-color: var(--light-grey-3);
+			border-right-color: var(--white);
 		}
 	}
 	&.confirm-theme .tippy-arrow {
@@ -600,55 +596,61 @@ a {
 
 			.checkbox-control {
 				display: flex;
+				flex-direction: row;
 				align-items: center;
-				margin-bottom: 0 !important;
-				width: inherit;
 
-				input {
-					margin-right: 5px;
+				p {
+					margin-left: 10px;
 				}
 
-				input[type="checkbox"] {
-					opacity: 0;
-					position: absolute;
+				.switch {
+					position: relative;
+					display: inline-block;
+					flex-shrink: 0;
+					width: 40px;
+					height: 24px;
 				}
 
-				label {
-					display: flex;
-					flex-direction: row;
-					align-items: center;
-					width: inherit;
-
-					span {
-						cursor: pointer;
-						min-width: 24px;
-						height: 24px;
-						background-color: var(--white);
-						display: inline-block;
-						border: 1px solid var(--dark-grey-2);
-						position: relative;
-						border-radius: 3px;
-					}
+				.switch input {
+					opacity: 0;
+					width: 0;
+					height: 0;
+				}
 
-					p {
-						margin-left: 10px;
-						cursor: pointer;
-						color: var(--black);
-						overflow: hidden;
-						text-overflow: ellipsis;
-						white-space: nowrap;
-					}
+				.slider {
+					position: absolute;
+					cursor: pointer;
+					top: 0;
+					left: 0;
+					right: 0;
+					bottom: 0;
+					background-color: #ccc;
+					transition: 0.2s;
+					border-radius: 34px;
 				}
 
-				input[type="checkbox"]:checked + label span::after {
+				.slider:before {
+					position: absolute;
 					content: "";
-					width: 18px;
-					height: 18px;
-					left: 2px;
-					top: 2px;
-					border-radius: 3px;
+					height: 16px;
+					width: 16px;
+					left: 4px;
+					bottom: 4px;
+					background-color: white;
+					transition: 0.2s;
+					border-radius: 50%;
+				}
+
+				input:checked + .slider {
 					background-color: var(--primary-color);
-					position: absolute;
+				}
+
+				input:focus + .slider {
+					box-shadow: 0 0 1px var(--primary-color);
+				}
+
+				input:checked + .slider:before {
+					transform: translateX(16px);
 				}
 			}
 

+ 17 - 6
frontend/src/components/AddToPlaylistDropdown.vue

@@ -1,6 +1,7 @@
 <template>
 	<tippy
 		class="addToPlaylistDropdown"
+		touch="true"
 		interactive="true"
 		:placement="placement"
 		theme="addToPlaylist"
@@ -20,6 +21,7 @@
 		<template #trigger>
 			<slot name="button" />
 		</template>
+
 		<div class="nav-dropdown-items" v-if="playlists.length > 0">
 			<button
 				class="nav-item"
@@ -30,12 +32,15 @@
 				:title="playlist.displayName"
 			>
 				<p class="control is-expanded checkbox-control">
-					<input
-						type="checkbox"
-						:id="index"
-						:checked="hasSong(playlist)"
-						@click="toggleSongInPlaylist(index)"
-					/>
+					<label class="switch">
+						<input
+							type="checkbox"
+							:id="index"
+							:checked="hasSong(playlist)"
+							@click="toggleSongInPlaylist(index)"
+						/>
+						<span class="slider round"></span>
+					</label>
 					<label :for="index">
 						<span></span>
 						<p>{{ playlist.displayName }}</p>
@@ -148,3 +153,9 @@ export default {
 	}
 };
 </script>
+
+<style lang="scss" scoped>
+.nav-dropdown-items button .control {
+	margin-bottom: 0 !important;
+}
+</style>

+ 1 - 0
frontend/src/components/Confirm.vue

@@ -1,6 +1,7 @@
 <template>
 	<tippy
 		interactive="true"
+		touch="true"
 		:placement="placement"
 		theme="confirm"
 		ref="confirm"

+ 1 - 0
frontend/src/components/SongItem.vue

@@ -72,6 +72,7 @@
 			>
 				<tippy
 					v-if="loggedIn"
+					touch="true"
 					interactive="true"
 					placement="left"
 					theme="songActions"

+ 4 - 0
frontend/src/components/modals/ManageStation/Tabs/Playlists.vue

@@ -4,6 +4,7 @@
 			<div class="tab-selection">
 				<button
 					class="button is-default"
+					ref="current-tab"
 					:class="{ selected: tab === 'current' }"
 					@click="showTab('current')"
 				>
@@ -11,6 +12,7 @@
 				</button>
 				<button
 					class="button is-default"
+					ref="search-tab"
 					:class="{ selected: tab === 'search' }"
 					@click="showTab('search')"
 				>
@@ -19,6 +21,7 @@
 				<button
 					v-if="station.type === 'community'"
 					class="button is-default"
+					ref="my-playlists-tab"
 					:class="{ selected: tab === 'my-playlists' }"
 					@click="showTab('my-playlists')"
 				>
@@ -396,6 +399,7 @@ export default {
 	},
 	methods: {
 		showTab(tab) {
+			this.$refs[`${tab}-tab`].scrollIntoView();
 			this.tab = tab;
 		},
 		isOwner() {

+ 5 - 0
frontend/src/components/modals/ManageStation/Tabs/Settings.vue

@@ -46,6 +46,7 @@
 					class="button-wrapper"
 					theme="addToPlaylist"
 					interactive="true"
+					touch="true"
 					placement="bottom"
 					trigger="click"
 					append-to="parent"
@@ -96,6 +97,7 @@
 					class="button-wrapper"
 					theme="addToPlaylist"
 					interactive="true"
+					touch="true"
 					placement="bottom"
 					trigger="click"
 					append-to="parent"
@@ -146,6 +148,7 @@
 					v-if="station.type === 'community'"
 					class="button-wrapper"
 					theme="addToPlaylist"
+					touch="true"
 					interactive="true"
 					placement="bottom"
 					trigger="click"
@@ -200,6 +203,7 @@
 					v-if="station.type === 'community'"
 					class="button-wrapper"
 					theme="addToPlaylist"
+					touch="true"
 					interactive="true"
 					placement="bottom"
 					trigger="click"
@@ -258,6 +262,7 @@
 					class="button-wrapper"
 					theme="addToPlaylist"
 					interactive="true"
+					touch="true"
 					placement="bottom"
 					trigger="click"
 					append-to="parent"

+ 1 - 0
frontend/src/main.js

@@ -17,6 +17,7 @@ const handleMetadata = attrs => {
 Vue.use(VueTippy, {
 	directive: "tippy", // => v-tippy
 	flipDuration: 0,
+	touch: false,
 	popperOptions: {
 		modifiers: {
 			preventOverflow: {

+ 98 - 52
frontend/src/pages/Settings/Tabs/Preferences.vue

@@ -7,51 +7,76 @@
 		<hr class="section-horizontal-rule" />
 
 		<p class="control is-expanded checkbox-control">
-			<input type="checkbox" id="nightmode" v-model="localNightmode" />
+			<label class="switch">
+				<input
+					type="checkbox"
+					id="nightmode"
+					v-model="localNightmode"
+				/>
+				<span class="slider round"></span>
+			</label>
+
 			<label for="nightmode">
-				<span></span>
 				<p>Use nightmode</p>
 			</label>
 		</p>
+
 		<p class="control is-expanded checkbox-control">
-			<input
-				type="checkbox"
-				id="autoSkipDisliked"
-				v-model="localAutoSkipDisliked"
-			/>
+			<label class="switch">
+				<input
+					type="checkbox"
+					id="autoSkipDisliked"
+					v-model="localAutoSkipDisliked"
+				/>
+				<span class="slider round"></span>
+			</label>
+
 			<label for="autoSkipDisliked">
-				<span></span>
 				<p>Automatically vote to skip disliked songs</p>
 			</label>
 		</p>
+
 		<p class="control is-expanded checkbox-control">
-			<input
-				type="checkbox"
-				id="activityLogPublic"
-				v-model="localActivityLogPublic"
-			/>
+			<label class="switch">
+				<input
+					type="checkbox"
+					id="activityLogPublic"
+					v-model="localActivityLogPublic"
+				/>
+				<span class="slider round"></span>
+			</label>
+
 			<label for="activityLogPublic">
-				<span></span>
 				<p>Allow my activity log to be viewed publicly</p>
 			</label>
 		</p>
+
 		<p class="control is-expanded checkbox-control">
-			<input
-				type="checkbox"
-				id="anonymousSongRequests"
-				v-model="localAnonymousSongRequests"
-			/>
+			<label class="switch">
+				<input
+					type="checkbox"
+					id="anonymousSongRequests"
+					v-model="localAnonymousSongRequests"
+				/>
+				<span class="slider round"></span>
+			</label>
+
 			<label for="anonymousSongRequests">
 				<span></span>
 				<p>Request songs anonymously</p>
 			</label>
 		</p>
+
 		<p class="control is-expanded checkbox-control">
-			<input
-				type="checkbox"
-				id="activityWatch"
-				v-model="localActivityWatch"
-			/>
+			<label class="switch">
+				<input
+					type="checkbox"
+					id="activityWatch"
+					v-model="localActivityWatch"
+				/>
+				<span class="slider round"></span>
+			</label>
+
 			<label for="activityWatch">
 				<span></span>
 				<p>Use ActivityWatch integration (requires extension)</p>
@@ -169,41 +194,62 @@ export default {
 
 <style lang="scss" scoped>
 .checkbox-control {
-	input[type="checkbox"] {
+	display: flex;
+	flex-direction: row;
+	align-items: center;
+
+	p {
+		margin-left: 10px;
+	}
+
+	.switch {
+		position: relative;
+		display: inline-block;
+		flex-shrink: 0;
+		width: 40px;
+		height: 24px;
+	}
+
+	.switch input {
 		opacity: 0;
-		position: absolute;
+		width: 0;
+		height: 0;
 	}
 
-	label {
-		display: flex;
-		flex-direction: row;
-		align-items: center;
-
-		span {
-			cursor: pointer;
-			width: 24px;
-			height: 24px;
-			background-color: var(--white);
-			display: inline-block;
-			border: 1px solid var(--dark-grey-2);
-			position: relative;
-			border-radius: 3px;
-		}
-
-		p {
-			margin-left: 10px;
-		}
+	.slider {
+		position: absolute;
+		cursor: pointer;
+		top: 0;
+		left: 0;
+		right: 0;
+		bottom: 0;
+		background-color: #ccc;
+		transition: 0.2s;
+		border-radius: 34px;
 	}
 
-	input[type="checkbox"]:checked + label span::after {
+	.slider:before {
+		position: absolute;
 		content: "";
-		width: 18px;
-		height: 18px;
-		left: 2px;
-		top: 2px;
-		border-radius: 3px;
+		height: 16px;
+		width: 16px;
+		left: 4px;
+		bottom: 4px;
+		background-color: white;
+		transition: 0.2s;
+		border-radius: 50%;
+	}
+
+	input:checked + .slider {
 		background-color: var(--primary-color);
-		position: absolute;
+	}
+
+	input:focus + .slider {
+		box-shadow: 0 0 1px var(--primary-color);
+	}
+
+	input:checked + .slider:before {
+		transform: translateX(16px);
 	}
 }
 </style>

+ 2 - 1
frontend/src/pages/Settings/index.vue

@@ -162,7 +162,7 @@ export default {
 		flex-direction: column;
 	}
 
-	@media only screen and (min-width: 900px) {
+	@media only screen and (min-width: 700px) {
 		#page-title {
 			margin: 0;
 			font-size: 40px;
@@ -170,6 +170,7 @@ export default {
 
 		#sidebar-with-content {
 			width: 962px;
+			max-width: 100%;
 			margin: 0 auto;
 			margin-top: 30px;
 			flex-direction: row;