Browse Source

refactor: Put station history, soundcloud and spotify behind experimental config

Owen Diffey 1 year ago
parent
commit
4f46ec6c25

+ 0 - 2
.github/workflows/automated-tests.yml

@@ -31,8 +31,6 @@ jobs:
             - name: Build Musare
               run: |
                   cp .env.example .env
-                  cp backend/config/template.json backend/config/default.json
-                  cp frontend/dist/config/template.json frontend/dist/config/default.json
                   ./musare.sh build
             - name: Start Musare
               run: ./musare.sh start

+ 0 - 2
.github/workflows/build-lint.yml

@@ -31,8 +31,6 @@ jobs:
             - name: Build Musare
               run: |
                   cp .env.example .env
-                  cp backend/config/template.json backend/config/default.json
-                  cp frontend/dist/config/template.json frontend/dist/config/default.json
                   ./musare.sh build
             - name: Start Musare
               run: ./musare.sh start

+ 11 - 3
backend/config/default.json

@@ -6,7 +6,7 @@
 		"host": "localhost",
 		"secure": false
 	},
-	"cookie": "musareSID",
+	"cookie": "SID",
 	"sitename": "Musare",
 	"apis": {
 		"youtube": {
@@ -91,7 +91,12 @@
 		"accountRemoval": "Your account will be deactivated instantly and your data will shortly be deleted by an admin."
 	},
 	"christmas": false,
-	"footerLinks": {},
+	"footerLinks": {
+		"about": true,
+		"team": true,
+		"news": true,
+		"GitHub": "https://github.com/Musare/Musare"
+	},
 	"shortcutOverrides": {},
 	"registrationDisabled": false,
 	"sendDataRequestEmails": true,
@@ -135,7 +140,10 @@
 		"disable_youtube_search": false,
 		"registration_email_whitelist": false,
 		"changable_listen_mode": [],
-		"media_session": false
+		"media_session": false,
+		"station_history": false,
+		"soundcloud": false,
+		"spotify": false
 	},
 	"configVersion": 12
 }

+ 6 - 0
backend/logic/actions/playlists.js

@@ -2124,6 +2124,9 @@ export default {
 		playlistId,
 		cb
 	) {
+		if (!(config.has("experimental.soundcloud") && !!config.get("experimental.soundcloud")))
+			return cb({ status: "error", message: "SoundCloud is not enabled" });
+
 		let songsInPlaylistTotal = 0;
 		let addSongsStats = null;
 
@@ -2312,6 +2315,9 @@ export default {
 	 * @param {Function} cb - gets called with the result
 	 */
 	addSpotifySetToPlaylist: isLoginRequired(async function addSpotifySetToPlaylist(session, url, playlistId, cb) {
+		if (!(config.has("experimental.spotify") && !!config.get("experimental.spotify")))
+			return cb({ status: "error", message: "Spotify is not enabled" });
+
 		let songsInPlaylistTotal = 0;
 		let addSongsStats = null;
 

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

@@ -1006,6 +1006,12 @@ export default {
 	getHistory(session, stationId, cb) {
 		async.waterfall(
 			[
+				next => {
+					if (!(config.has("experimental.station_history") && !!config.get("experimental.station_history")))
+						return next("Station history is not enabled");
+					return next();
+				},
+
 				next => {
 					StationsModule.runJob("GET_STATION", { stationId }, this)
 						.then(station => {

+ 9 - 3
backend/logic/hooks/hasPermission.js

@@ -1,4 +1,5 @@
 import async from "async";
+import config from "config";
 
 // eslint-disable-next-line
 import moduleManager from "../../index";
@@ -37,7 +38,6 @@ permissions.moderator = {
 	"admin.view.users": true,
 	"admin.view.youtubeVideos": true,
 	"admin.view.youtubeChannels": true,
-	"admin.view.soundcloudTracks": true,
 	"apis.searchDiscogs": true,
 	"news.create": true,
 	"news.update": true,
@@ -73,8 +73,6 @@ permissions.admin = {
 	"admin.view.dataRequests": true,
 	"admin.view.statistics": true,
 	"admin.view.youtube": true,
-	"admin.view.soundcloud": true,
-	"admin.view.spotify": true,
 	"dataRequests.resolve": true,
 	"media.recalculateAllRatings": true,
 	"media.removeImportJobs": true,
@@ -101,6 +99,14 @@ permissions.admin = {
 	"youtube.removeVideos": true
 };
 
+if (config.has("experimental.soundcloud") && !!config.get("experimental.soundcloud")) {
+	permissions.moderator["admin.view.soundcloudTracks"] = true;
+	permissions.admin["admin.view.soundcloudTracks"] = true;
+	permissions.admin["admin.view.soundcloud"] = true;
+}
+if (config.has("experimental.spotify") && !!config.get("experimental.spotify"))
+	permissions.admin["admin.view.spotify"] = true;
+
 export const hasPermission = async (permission, session, stationId) => {
 	const CacheModule = moduleManager.modules.cache;
 	const DBModule = moduleManager.modules.db;

+ 15 - 19
backend/logic/spotify.js

@@ -20,22 +20,20 @@ const youtubeVideoUrlRegex =
 	/^(https?:\/\/)?(www\.)?(m\.)?(music\.)?(youtube\.com|youtu\.be)\/(watch\?v=)?(?<youtubeId>[\w-]{11})((&([A-Za-z0-9]+)?)*)?$/;
 const youtubeVideoIdRegex = /^([\w-]{11})$/;
 
-const spotifyTrackObjectToMusareTrackObject = spotifyTrackObject => {
-	return {
-		trackId: spotifyTrackObject.id,
-		name: spotifyTrackObject.name,
-		albumId: spotifyTrackObject.album.id,
-		albumTitle: spotifyTrackObject.album.title,
-		albumImageUrl: spotifyTrackObject.album.images[0].url,
-		artists: spotifyTrackObject.artists.map(artist => artist.name),
-		artistIds: spotifyTrackObject.artists.map(artist => artist.id),
-		duration: spotifyTrackObject.duration_ms / 1000,
-		explicit: spotifyTrackObject.explicit,
-		externalIds: spotifyTrackObject.external_ids,
-		popularity: spotifyTrackObject.popularity,
-		isLocal: spotifyTrackObject.is_local
-	};
-};
+const spotifyTrackObjectToMusareTrackObject = spotifyTrackObject => ({
+	trackId: spotifyTrackObject.id,
+	name: spotifyTrackObject.name,
+	albumId: spotifyTrackObject.album.id,
+	albumTitle: spotifyTrackObject.album.title,
+	albumImageUrl: spotifyTrackObject.album.images[0].url,
+	artists: spotifyTrackObject.artists.map(artist => artist.name),
+	artistIds: spotifyTrackObject.artists.map(artist => artist.id),
+	duration: spotifyTrackObject.duration_ms / 1000,
+	explicit: spotifyTrackObject.explicit,
+	externalIds: spotifyTrackObject.external_ids,
+	popularity: spotifyTrackObject.popularity,
+	isLocal: spotifyTrackObject.is_local
+});
 
 class RateLimitter {
 	/**
@@ -1316,9 +1314,7 @@ class _SpotifyModule extends CoreClass {
 
 						jobsToRun.push(promise);
 
-						//WikiDataModule.runJob("API_GET_DATA_FROM_MUSICBRAINZ_WORK", { workId: relation.work.id }, this));
-
-						return;
+						// WikiDataModule.runJob("API_GET_DATA_FROM_MUSICBRAINZ_WORK", { workId: relation.work.id }, this));
 					}
 				});
 			});

+ 11 - 0
backend/logic/stations.js

@@ -956,6 +956,9 @@ class _StationsModule extends CoreClass {
 	 * @param {*} payload
 	 */
 	async ADD_STATION_HISTORY_ITEM(payload) {
+		if (!(config.has("experimental.station_history") && !!config.get("experimental.station_history")))
+			throw new Error("Station history is not enabled");
+
 		const { stationId, currentSong, skipReason, skippedAt } = payload;
 
 		let document = await StationsModule.stationHistoryModel.create({
@@ -1030,6 +1033,14 @@ class _StationsModule extends CoreClass {
 					(station, next) => {
 						if (!station) return next("Station not found.");
 
+						if (
+							!(
+								config.has("experimental.station_history") &&
+								!!config.get("experimental.station_history")
+							)
+						)
+							return next(null, station);
+
 						const { currentSong } = station;
 						if (!currentSong || !currentSong.mediaSource) return next(null, station);
 

+ 4 - 1
backend/logic/ws.js

@@ -603,7 +603,10 @@ class _WSModule extends CoreClass {
 					experimental: {
 						changable_listen_mode: config.get("experimental.changable_listen_mode"),
 						media_session: config.get("experimental.media_session"),
-						disable_youtube_search: config.get("experimental.disable_youtube_search")
+						disable_youtube_search: config.get("experimental.disable_youtube_search"),
+						station_history: config.get("experimental.station_history"),
+						soundcloud: config.get("experimental.soundcloud"),
+						spotify: config.get("experimental.spotify")
 					}
 				},
 				user: { loggedIn: false }

+ 1 - 1
docker-compose.yml

@@ -37,7 +37,6 @@ services:
       - "${FRONTEND_HOST:-0.0.0.0}:${FRONTEND_PORT:-80}:80"
     volumes:
       - ./.git:/opt/app/.parent_git:ro
-      - ./frontend/dist/config:/opt/app/dist/config
       - ./types:/opt/types
     environment:
       - CONTAINER_MODE=${CONTAINER_MODE:-production}
@@ -45,6 +44,7 @@ services:
       - FRONTEND_PORT=${FRONTEND_PORT:-80}
       - FRONTEND_CLIENT_PORT=${FRONTEND_CLIENT_PORT:-80}
       - FRONTEND_DEV_PORT=${FRONTEND_DEV_PORT:-81}
+      - FRONTEND_PROD_DEVTOOLS=${FRONTEND_PROD_DEVTOOLS:-false}
       - MUSARE_SITENAME=${MUSARE_SITENAME:-Musare}
       - MUSARE_DEBUG_VERSION=${MUSARE_DEBUG_VERSION:-true}
       - MUSARE_DEBUG_GIT_REMOTE=${MUSARE_DEBUG_GIT_REMOTE:-false}

+ 7 - 20
frontend/src/components/MainFooter.vue

@@ -1,36 +1,23 @@
 <script setup lang="ts">
-import { ref, computed, onMounted } from "vue";
+import { computed } from "vue";
 import { useConfigStore } from "@/stores/config";
 
-const footerLinks = ref({});
-
 const configStore = useConfigStore();
 
 const filteredFooterLinks = computed(() =>
 	Object.fromEntries(
-		Object.entries(footerLinks.value).filter(
-			([title, url]) =>
-				!(
-					["about", "team", "news"].includes(title.toLowerCase()) &&
-					typeof url === "boolean"
-				)
+		Object.entries(configStore.get("footerLinks")).filter(
+			url => !(typeof url[1] === "boolean")
 		)
 	)
 );
 
 const getLink = title =>
-	footerLinks.value[
-		Object.keys(footerLinks.value).find(key => key.toLowerCase() === title)
+	configStore.get("footerLinks")[
+		Object.keys(configStore.get("footerLinks")).find(
+			key => key.toLowerCase() === title
+		)
 	];
-
-onMounted(() => {
-	footerLinks.value = {
-		about: true,
-		team: true,
-		news: true,
-		...configStore.get("footerLinks")
-	};
-});
 </script>
 
 <template>

+ 4 - 1
frontend/src/components/Request.vue

@@ -362,7 +362,10 @@ onMounted(async () => {
 					</div>
 				</div>
 
-				<div class="soundcloud-direct">
+				<div
+					v-if="configStore.get('experimental.soundcloud')"
+					class="soundcloud-direct"
+				>
 					<label class="label">
 						Add a SoundCloud song from a URL
 					</label>

+ 44 - 38
frontend/src/components/modals/EditPlaylist/Tabs/AddSongs.vue

@@ -279,45 +279,51 @@ watch(
 			</div>
 		</div>
 
-		<label class="label"> Add a SoundCloud song from a URL </label>
-		<div class="control is-grouped input-with-button">
-			<p class="control is-expanded">
-				<input
-					class="input"
-					type="text"
-					placeholder="Enter your SoundCloud song URL here..."
-					v-model="soundcloudDirect"
-					@keyup.enter="soundcloudAddToPlaylist(playlist._id)"
-				/>
-			</p>
-			<p class="control">
-				<a
-					class="button is-info"
-					@click="soundcloudAddToPlaylist(playlist._id)"
-					><i class="material-icons icon-with-button">add</i>Add</a
-				>
-			</p>
-		</div>
+		<template v-if="configStore.get('experimental.soundcloud')">
+			<label class="label"> Add a SoundCloud song from a URL </label>
+			<div class="control is-grouped input-with-button">
+				<p class="control is-expanded">
+					<input
+						class="input"
+						type="text"
+						placeholder="Enter your SoundCloud song URL here..."
+						v-model="soundcloudDirect"
+						@keyup.enter="soundcloudAddToPlaylist(playlist._id)"
+					/>
+				</p>
+				<p class="control">
+					<a
+						class="button is-info"
+						@click="soundcloudAddToPlaylist(playlist._id)"
+						><i class="material-icons icon-with-button">add</i
+						>Add</a
+					>
+				</p>
+			</div>
+		</template>
 
-		<label class="label"> Add a Spotify song from a URL </label>
-		<div class="control is-grouped input-with-button">
-			<p class="control is-expanded">
-				<input
-					class="input"
-					type="text"
-					placeholder="Enter your Spotify song URL here..."
-					v-model="spotifyDirect"
-					@keyup.enter="spotifyAddToPlaylist(playlist._id)"
-				/>
-			</p>
-			<p class="control">
-				<a
-					class="button is-info"
-					@click="spotifyAddToPlaylist(playlist._id)"
-					><i class="material-icons icon-with-button">add</i>Add</a
-				>
-			</p>
-		</div>
+		<template v-if="configStore.get('experimental.spotify')">
+			<label class="label"> Add a Spotify song from a URL </label>
+			<div class="control is-grouped input-with-button">
+				<p class="control is-expanded">
+					<input
+						class="input"
+						type="text"
+						placeholder="Enter your Spotify song URL here..."
+						v-model="spotifyDirect"
+						@keyup.enter="spotifyAddToPlaylist(playlist._id)"
+					/>
+				</p>
+				<p class="control">
+					<a
+						class="button is-info"
+						@click="spotifyAddToPlaylist(playlist._id)"
+						><i class="material-icons icon-with-button">add</i
+						>Add</a
+					>
+				</p>
+			</div>
+		</template>
 	</div>
 </template>
 

+ 49 - 41
frontend/src/components/modals/EditPlaylist/Tabs/ImportPlaylists.vue

@@ -6,6 +6,7 @@ import { useSearchYoutube } from "@/composables/useSearchYoutube";
 import { useSearchSoundcloud } from "@/composables/useSearchSoundcloud";
 import { useSearchSpotify } from "@/composables/useSearchSpotify";
 import { useWebsocketsStore } from "@/stores/websockets";
+import { useConfigStore } from "@/stores/config";
 import { useLongJobsStore } from "@/stores/longJobs";
 import { useEditPlaylistStore } from "@/stores/editPlaylist";
 
@@ -15,6 +16,7 @@ const props = defineProps({
 
 const { socket } = useWebsocketsStore();
 
+const configStore = useConfigStore();
 const editPlaylistStore = useEditPlaylistStore({ modalUuid: props.modalUuid });
 const { playlist } = storeToRefs(editPlaylistStore);
 
@@ -242,47 +244,53 @@ const importMusarePlaylistFile = () => {
 			</p>
 		</div>
 
-		<label class="label"> Import songs from SoundCloud playlist </label>
-		<div class="control is-grouped input-with-button">
-			<p class="control is-expanded">
-				<input
-					class="input"
-					type="text"
-					placeholder="Enter SoundCloud Playlist URL here..."
-					v-model="soundcloudSearch.playlist.query"
-					@keyup.enter="importSoundcloudPlaylist()"
-				/>
-			</p>
-			<p class="control has-addons">
-				<button
-					class="button is-info"
-					@click.prevent="importSoundcloudPlaylist()"
-				>
-					<i class="material-icons icon-with-button">publish</i>Import
-				</button>
-			</p>
-		</div>
-
-		<label class="label"> Import songs from Spotify playlist </label>
-		<div class="control is-grouped input-with-button">
-			<p class="control is-expanded">
-				<input
-					class="input"
-					type="text"
-					placeholder="Enter Spotify Playlist URL here..."
-					v-model="spotifySearch.playlist.query"
-					@keyup.enter="importSpotifyPlaylist()"
-				/>
-			</p>
-			<p class="control has-addons">
-				<button
-					class="button is-info"
-					@click.prevent="importSpotifyPlaylist()"
-				>
-					<i class="material-icons icon-with-button">publish</i>Import
-				</button>
-			</p>
-		</div>
+		<template v-if="configStore.get('experimental.soundcloud')">
+			<label class="label"> Import songs from SoundCloud playlist </label>
+			<div class="control is-grouped input-with-button">
+				<p class="control is-expanded">
+					<input
+						class="input"
+						type="text"
+						placeholder="Enter SoundCloud Playlist URL here..."
+						v-model="soundcloudSearch.playlist.query"
+						@keyup.enter="importSoundcloudPlaylist()"
+					/>
+				</p>
+				<p class="control has-addons">
+					<button
+						class="button is-info"
+						@click.prevent="importSoundcloudPlaylist()"
+					>
+						<i class="material-icons icon-with-button">publish</i
+						>Import
+					</button>
+				</p>
+			</div>
+		</template>
+
+		<template v-if="configStore.get('experimental.spotify')">
+			<label class="label"> Import songs from Spotify playlist </label>
+			<div class="control is-grouped input-with-button">
+				<p class="control is-expanded">
+					<input
+						class="input"
+						type="text"
+						placeholder="Enter Spotify Playlist URL here..."
+						v-model="spotifySearch.playlist.query"
+						@keyup.enter="importSpotifyPlaylist()"
+					/>
+				</p>
+				<p class="control has-addons">
+					<button
+						class="button is-info"
+						@click.prevent="importSpotifyPlaylist()"
+					>
+						<i class="material-icons icon-with-button">publish</i
+						>Import
+					</button>
+				</p>
+			</div>
+		</template>
 
 		<label class="label"> Import songs from a Musare playlist file </label>
 		<div class="control is-grouped input-with-button">

+ 6 - 2
frontend/src/pages/Admin/Songs/index.vue

@@ -3,6 +3,7 @@ import { defineAsyncComponent, ref, onMounted } from "vue";
 import { useRoute } from "vue-router";
 import Toast from "toasters";
 import { useWebsocketsStore } from "@/stores/websockets";
+import { useConfigStore } from "@/stores/config";
 import { useLongJobsStore } from "@/stores/longJobs";
 import { useModalsStore } from "@/stores/modals";
 import { useUserAuthStore } from "@/stores/userAuth";
@@ -31,6 +32,8 @@ const { setJob } = useLongJobsStore();
 
 const { socket } = useWebsocketsStore();
 
+const configStore = useConfigStore();
+
 const { hasPermission } = useUserAuthStore();
 
 const columnDefault = ref<TableColumn>({
@@ -548,8 +551,9 @@ onMounted(() => {
 				</button>
 				<button
 					v-if="
-						hasPermission('songs.create') ||
-						hasPermission('songs.update')
+						configStore.get('experimental.spotify') &&
+						(hasPermission('songs.create') ||
+							hasPermission('songs.update'))
 					"
 					class="button is-primary"
 					@click="openModal('importArtist')"

+ 8 - 1
frontend/src/pages/Station/Sidebar/index.vue

@@ -2,6 +2,7 @@
 import { useRoute } from "vue-router";
 import { defineAsyncComponent, watch, onMounted } from "vue";
 import { storeToRefs } from "pinia";
+import { useConfigStore } from "@/stores/config";
 import { useUserAuthStore } from "@/stores/userAuth";
 import { useStationStore } from "@/stores/station";
 import { useTabQueryHandler } from "@/composables/useTabQueryHandler";
@@ -16,6 +17,7 @@ const History = defineAsyncComponent(
 );
 
 const route = useRoute();
+const configStore = useConfigStore();
 const userAuthStore = useUserAuthStore();
 const stationStore = useStationStore();
 
@@ -86,6 +88,7 @@ onMounted(() => {
 				Request
 			</button>
 			<button
+				v-if="configStore.get('experimental.station_history')"
 				class="button is-default"
 				:class="{ selected: tab === 'history' }"
 				@click="showTab('history')"
@@ -101,7 +104,11 @@ onMounted(() => {
 			class="tab requests-tab"
 			sector="station"
 		/>
-		<History class="tab" v-show="tab === 'history'" />
+		<History
+			v-if="configStore.get('experimental.station_history')"
+			class="tab"
+			v-show="tab === 'history'"
+		/>
 	</div>
 </template>
 

+ 7 - 6
frontend/src/pages/Station/index.vue

@@ -1348,12 +1348,13 @@ onMounted(async () => {
 					}
 				});
 
-				socket.dispatch("stations.getHistory", _id, res => {
-					if (res.status === "success") {
-						const { history } = res.data;
-						setHistory(history);
-					}
-				});
+				if (configStore.get("experimental.station_history"))
+					socket.dispatch("stations.getHistory", _id, res => {
+						if (res.status === "success") {
+							const { history } = res.data;
+							setHistory(history);
+						}
+					});
 
 				if (hasPermission("stations.playback.toggle"))
 					keyboardShortcuts.registerShortcut("station.pauseResume", {

+ 7 - 1
frontend/src/stores/config.ts

@@ -19,6 +19,9 @@ export const useConfigStore = defineStore("config", {
 				changable_listen_mode: string[];
 				media_session: boolean;
 				disable_youtube_search: boolean;
+				station_history: boolean;
+				soundcloud: boolean;
+				spotify: boolean;
 			};
 		};
 	} => ({
@@ -41,7 +44,10 @@ export const useConfigStore = defineStore("config", {
 			experimental: {
 				changable_listen_mode: [],
 				media_session: false,
-				disable_youtube_search: false
+				disable_youtube_search: false,
+				station_history: false,
+				soundcloud: false,
+				spotify: false
 			}
 		}
 	}),