Browse Source

Worked more on song merging

Kristian Vos 4 years ago
parent
commit
5624ec3e87

+ 10 - 15
backend/logic/actions/playlists.js

@@ -875,25 +875,20 @@ export default {
 						.catch(next);
 				},
 				(position, next) => {
-					SongsModule.runJob("GET_SONG_FROM_ID", { songId }, this)
-						.then(res => {
-							const { song } = res;
-
+					SongsModule.runJob("ENSURE_SONG_EXISTS_BY_SONG_ID", { songId }, this)
+						.then(response => {
+							const { song } = response;
+							const { _id, title, thumbnail, duration, verified } = song;
 							next(null, {
-								_id: song._id,
+								_id,
 								songId,
-								title: song.title,
-								duration: song.duration,
-								thumbnail: song.thumbnail,
-								artists: song.artists,
-								position
+								title,
+								thumbnail,
+								duration,
+								verified
 							});
 						})
-						.catch(() => {
-							YouTubeModule.runJob("GET_SONG", { songId }, this)
-								.then(response => next(null, { ...response.song, position }))
-								.catch(next);
-						});
+						.catch(next);
 				},
 				(newSong, next) => {
 					playlistModel.updateOne(

+ 17 - 24
backend/logic/actions/stations.js

@@ -2816,36 +2816,29 @@ export default {
 				},
 
 				(station, next) => {
-					SongsModule.runJob("GET_SONG_FROM_ID", { songId }, this)
-						.then(res => {
-							if (res.song) return next(null, res.song, station);
-
-							return YouTubeModule.runJob("GET_SONG", { songId }, this)
-								.then(response => {
-									const { song } = response;
-									song.artists = [];
-									song.skipDuration = 0;
-									song.likes = -1;
-									song.dislikes = -1;
-									song.thumbnail = "empty";
-									song.explicit = false;
-
-									return next(null, song, station);
-								})
-								.catch(err => {
-									next(err);
-								});
+					SongsModule.runJob("ENSURE_SONG_EXISTS_BY_SONG_ID", { songId }, this)
+						.then(response => {
+							const { song } = response;
+							const { _id, title, thumbnail, duration, verified } = song;
+							next(
+								null,
+								{
+									_id,
+									songId,
+									title,
+									thumbnail,
+									duration,
+									verified
+								},
+								station
+							);
 						})
-						.catch(err => {
-							next(err);
-						});
+						.catch(next);
 				},
 
 				(song, station, next) => {
 					song.requestedBy = session.userId;
 					song.requestedAt = Date.now();
-					song._id = null;
-
 					let totalDuration = 0;
 					station.queue.forEach(song => {
 						totalDuration += song.duration;

+ 2 - 11
backend/logic/db/index.js

@@ -207,30 +207,22 @@ class _DBModule extends CoreClass {
 					// Song
 					const songTitle = title => isLength(title, 1, 100);
 					this.schemas.song.path("title").validate(songTitle, "Invalid title.");
-					this.schemas.queueSong.path("title").validate(songTitle, "Invalid title.");
 
-					this.schemas.song
-						.path("artists")
-						.validate(artists => !(artists.length < 1 || artists.length > 10), "Invalid artists.");
-					this.schemas.queueSong
-						.path("artists")
-						.validate(artists => !(artists.length < 0 || artists.length > 10), "Invalid artists.");
+					this.schemas.song.path("artists").validate(artists => artists.length <= 10, "Invalid artists.");
 
 					const songArtists = artists =>
 						artists.filter(artist => isLength(artist, 1, 64) && artist !== "NONE").length ===
 						artists.length;
 					this.schemas.song.path("artists").validate(songArtists, "Invalid artists.");
-					this.schemas.queueSong.path("artists").validate(songArtists, "Invalid artists.");
 
 					const songGenres = genres => {
-						if (genres.length < 1 || genres.length > 16) return false;
+						if (genres.length > 16) return false;
 						return (
 							genres.filter(genre => isLength(genre, 1, 32) && regex.ascii.test(genre)).length ===
 							genres.length
 						);
 					};
 					this.schemas.song.path("genres").validate(songGenres, "Invalid genres.");
-					this.schemas.queueSong.path("genres").validate(songGenres, "Invalid genres.");
 
 					const songThumbnail = thumbnail => {
 						if (!isLength(thumbnail, 1, 256)) return false;
@@ -238,7 +230,6 @@ class _DBModule extends CoreClass {
 						return thumbnail.startsWith("http://") || thumbnail.startsWith("https://");
 					};
 					this.schemas.song.path("thumbnail").validate(songThumbnail, "Invalid thumbnail.");
-					this.schemas.queueSong.path("thumbnail").validate(songThumbnail, "Invalid thumbnail.");
 
 					// Playlist
 					this.schemas.playlist

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

@@ -1,19 +1,19 @@
 export default {
 	songId: { type: String, min: 11, max: 11, required: true, index: true },
 	title: { type: String, required: true },
-	artists: [{ type: String }],
-	genres: [{ type: String }],
+	artists: [{ type: String, default: [] }],
+	genres: [{ type: String, default: [] }],
 	duration: { type: Number, min: 1, required: true },
-	skipDuration: { type: Number, required: true },
-	thumbnail: { type: String, required: true },
+	skipDuration: { type: Number, required: true, default: 0 },
+	thumbnail: { type: String },
 	likes: { type: Number, default: 0, required: true },
 	dislikes: { type: Number, default: 0, required: true },
-	explicit: { type: Boolean, default: false, required: true },
-	requestedBy: { type: String, required: true },
-	requestedAt: { type: Date, required: true },
-	acceptedBy: { type: String, required: true }, // TODO Should be verifiedBy
-	acceptedAt: { type: Date, default: Date.now, required: true }, // TODO Should be verifiedAt
+	explicit: { type: Boolean },
+	requestedBy: { type: String },
+	requestedAt: { type: Date },
+	acceptedBy: { type: String }, // TODO Should be verifiedBy
+	acceptedAt: { type: Date }, // TODO Should be verifiedAt
 	discogs: { type: Object },
-	verified: false,
+	verified: { type: Boolean, required: true, default: false },
 	documentVersion: { type: Number, default: 1, required: true }
 };

+ 114 - 12
backend/logic/songs.js

@@ -6,6 +6,9 @@ let SongsModule;
 let CacheModule;
 let DBModule;
 let UtilsModule;
+let YouTubeModule;
+let StationModule;
+let PlaylistModule;
 
 class _SongsModule extends CoreClass {
 	// eslint-disable-next-line require-jsdoc
@@ -26,9 +29,12 @@ class _SongsModule extends CoreClass {
 		CacheModule = this.moduleManager.modules.cache;
 		DBModule = this.moduleManager.modules.db;
 		UtilsModule = this.moduleManager.modules.utils;
+		YouTubeModule = this.moduleManager.modules.youtube;
+		StationModule = this.moduleManager.modules.stations;
+		PlaylistModule = this.moduleManager.modules.playlists;
 
-		this.songModel = await DBModule.runJob("GET_MODEL", { modelName: "song" });
-		this.songSchemaCache = await CacheModule.runJob("GET_SCHEMA", { schemaName: "song" });
+		this.SongModel = await DBModule.runJob("GET_MODEL", { modelName: "song" });
+		this.SongSchemaCache = await CacheModule.runJob("GET_SCHEMA", { schemaName: "song" });
 
 		this.setStage(2);
 
@@ -54,7 +60,7 @@ class _SongsModule extends CoreClass {
 						return async.each(
 							songIds,
 							(songId, next) => {
-								SongsModule.songModel.findOne({ songId }, (err, song) => {
+								SongsModule.SongModel.findOne({ songId }, (err, song) => {
 									if (err) next(err);
 									else if (!song)
 										CacheModule.runJob("HDEL", {
@@ -72,7 +78,7 @@ class _SongsModule extends CoreClass {
 
 					next => {
 						this.setStage(4);
-						SongsModule.songModel.find({}, next);
+						SongsModule.SongModel.find({}, next);
 					},
 
 					(songs, next) => {
@@ -83,7 +89,7 @@ class _SongsModule extends CoreClass {
 								CacheModule.runJob("HSET", {
 									table: "songs",
 									key: song.songId,
-									value: SongsModule.songSchemaCache(song)
+									value: SongsModule.SongSchemaCache(song)
 								})
 									.then(() => next())
 									.catch(next);
@@ -122,7 +128,7 @@ class _SongsModule extends CoreClass {
 
 					(song, next) => {
 						if (song) return next(true, song);
-						return SongsModule.songModel.findOne({ _id: payload.id }, next);
+						return SongsModule.SongModel.findOne({ _id: payload.id }, next);
 					},
 
 					(song, next) => {
@@ -147,6 +153,46 @@ class _SongsModule extends CoreClass {
 		);
 	}
 
+	/**
+	 * Makes sure that if a song is not currently in the songs db, to add it
+	 *
+	 * @param {object} payload - an object containing the payload
+	 * @param {string} payload.songId - the youtube song id of the song we are trying to ensure is in the songs db
+	 * @returns {Promise} - returns a promise (resolve, reject)
+	 */
+	ENSURE_SONG_EXISTS_BY_SONG_ID(payload) {
+		return new Promise((resolve, reject) =>
+			async.waterfall(
+				[
+					next => {
+						SongsModule.SongModel.findOne({ songId: payload.songId }, next);
+					},
+
+					(song, next) => {
+						if (song) next(true, song);
+						else {
+							YouTubeModule.runJob("GET_SONG", { songId: payload.songId }, this)
+								.then(response => next(null, { ...response.song }))
+								.catch(next);
+						}
+					},
+
+					(_song, next) => {
+						const song = new SongsModule.SongModel({ ..._song });
+						song.save({ validateBeforeSave: true }, err => {
+							if (err) return next(err, song);
+							return next(null, song);
+						});
+					}
+				],
+				(err, song) => {
+					if (err && err !== true) return reject(new Error(err));
+					return resolve({ song });
+				}
+			)
+		);
+	}
+
 	/**
 	 * Gets a song by song id from the cache or Mongo, and if it isn't in the cache yet, adds it the cache
 	 *
@@ -159,7 +205,7 @@ class _SongsModule extends CoreClass {
 			async.waterfall(
 				[
 					next => {
-						SongsModule.songModel.findOne({ songId: payload.songId }, next);
+						SongsModule.SongModel.findOne({ songId: payload.songId }, next);
 					}
 				],
 				(err, song) => {
@@ -182,7 +228,7 @@ class _SongsModule extends CoreClass {
 			async.waterfall(
 				[
 					next => {
-						SongsModule.songModel.findOne({ _id: payload.songId }, next);
+						SongsModule.SongModel.findOne({ _id: payload.songId }, next);
 					},
 
 					(song, next) => {
@@ -207,6 +253,62 @@ class _SongsModule extends CoreClass {
 								next(null, song);
 							})
 							.catch(next);
+					},
+
+					(song, next) => {
+						next(null, song);
+						const { _id, songId, title, artists, thumbnail, duration, verified } = song;
+						const trimmedSong = {
+							_id,
+							songId,
+							title,
+							artists,
+							thumbnail,
+							duration,
+							verified
+						};
+						this.log("INFO", `Going to update playlists and stations now for song ${_id}`);
+						DBModule.runJob("GET_MODEL", { modelName: "playlist" }).then(playlistModel => {
+							playlistModel.updateMany(
+								{ "songs._id": song._id },
+								{ $set: { "songs.$": trimmedSong } },
+								err => {
+									if (err) this.log("ERROR", err);
+									else
+										playlistModel.find({ "songs._id": song._id }, (err, playlists) => {
+											playlists.forEach(playlist => {
+												PlaylistModule.runJob("UPDATE_PLAYLIST", {
+													playlistId: playlist._id
+												});
+											});
+										});
+								}
+							);
+						});
+						DBModule.runJob("GET_MODEL", { modelName: "station" }).then(stationModel => {
+							stationModel.updateMany(
+								{ "queue._id": song._id },
+								{
+									$set: {
+										"queue.$.songId": songId,
+										"queue.$.title": title,
+										"queue.$.artists": artists,
+										"queue.$.thumbnail": thumbnail,
+										"queue.$.duration": duration,
+										"queue.$.verified": verified
+									}
+								},
+								err => {
+									if (err) this.log("ERROR", err);
+									else
+										stationModel.find({ "queue._id": song._id }, (err, stations) => {
+											stations.forEach(station => {
+												StationModule.runJob("UPDATE_STATION", { stationId: station._id });
+											});
+										});
+								}
+							);
+						});
 					}
 				],
 				(err, song) => {
@@ -229,7 +331,7 @@ class _SongsModule extends CoreClass {
 			async.waterfall(
 				[
 					next => {
-						SongsModule.songModel.deleteOne({ songId: payload.songId }, next);
+						SongsModule.SongModel.deleteOne({ songId: payload.songId }, next);
 					},
 
 					next => {
@@ -288,7 +390,7 @@ class _SongsModule extends CoreClass {
 					},
 
 					({ likes, dislikes }, next) => {
-						SongsModule.songModel.updateOne(
+						SongsModule.SongModel.updateOne(
 							{ _id: payload.songId },
 							{
 								$set: {
@@ -318,7 +420,7 @@ class _SongsModule extends CoreClass {
 			async.waterfall(
 				[
 					next => {
-						SongsModule.songModel.find({ verified: true }, { genres: 1, _id: false }, next);
+						SongsModule.SongModel.find({ verified: true }, { genres: 1, _id: false }, next);
 					},
 
 					(songs, next) => {
@@ -355,7 +457,7 @@ class _SongsModule extends CoreClass {
 			async.waterfall(
 				[
 					next => {
-						SongsModule.songModel.find(
+						SongsModule.SongModel.find(
 							{ verified: true, genres: { $regex: new RegExp(`^${payload.genre.toLowerCase()}$`, "i") } },
 							next
 						);