Bladeren bron

Merge remote-tracking branch 'origin/jonathan-activities' into polishing

Owen Diffey 4 jaren geleden
bovenliggende
commit
bf0354aa85

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

@@ -48,6 +48,7 @@ export default {
 									if (user.preferences.activityLogPublic) return next();
 									return next("User's activity log isn't public.");
 								}
+
 								return next("User does not exist.");
 							})
 							.catch(next);

+ 114 - 43
backend/logic/actions/playlists.js

@@ -435,9 +435,12 @@ export default {
 				});
 
 				ActivitiesModule.runJob("ADD_ACTIVITY", {
-					userId: session.userId,
-					activityType: "created_playlist",
-					payload: [playlist._id]
+					userId: playlist.createdBy,
+					type: "playlist__create",
+					payload: {
+						message: `Created playlist <playlistId>${playlist.displayName}</playlistId>`,
+						playlistId: playlist._id
+					}
 				});
 
 				this.log(
@@ -595,11 +598,13 @@ export default {
 					);
 					return cb({ status: "failure", message: err });
 				}
+
 				this.log(
 					"SUCCESS",
 					"PLAYLIST_UPDATE",
 					`Successfully updated private playlist "${playlistId}" for user "${session.userId}".`
 				);
+
 				return cb({
 					status: "success",
 					data: playlist
@@ -906,17 +911,29 @@ export default {
 					);
 					return cb({ status: "failure", message: err });
 				}
+
 				this.log(
 					"SUCCESS",
 					"PLAYLIST_ADD_SONG",
 					`Successfully added song "${songId}" to private playlist "${playlistId}" for user "${session.userId}".`
 				);
-				if (!isSet)
+
+				if (!isSet && playlist.displayName !== "Liked Songs" && playlist.displayName !== "Disliked Songs") {
+					const songName = newSong.artists
+						? `${newSong.title} by ${newSong.artists.join(", ")}`
+						: newSong.title;
+
 					ActivitiesModule.runJob("ADD_ACTIVITY", {
 						userId: session.userId,
-						activityType: "added_song_to_playlist",
-						payload: [{ songId, playlistId }]
+						type: "playlist__add_song",
+						payload: {
+							message: `Added <songId>${songName}</songId> to playlist <playlistId>${playlist.displayName}</playlistId>`,
+							thumbnail: newSong.thumbnail,
+							playlistId,
+							songId
+						}
 					});
+				}
 
 				CacheModule.runJob("PUB", {
 					channel: "playlist.addSong",
@@ -927,6 +944,7 @@ export default {
 						privacy: playlist.privacy
 					}
 				});
+
 				return cb({
 					status: "success",
 					message: "Song has been successfully added to the playlist",
@@ -1003,9 +1021,7 @@ export default {
 								.catch(() => {
 									failed += 1;
 								})
-								.finally(() => {
-									next();
-								});
+								.finally(() => next());
 						},
 						() => {
 							addSongsStats = { successful, failed, alreadyInPlaylist };
@@ -1016,9 +1032,7 @@ export default {
 
 				next => {
 					PlaylistsModule.runJob("GET_PLAYLIST", { playlistId }, this)
-						.then(playlist => {
-							next(null, playlist);
-						})
+						.then(playlist => next(null, playlist))
 						.catch(next);
 				},
 
@@ -1039,16 +1053,22 @@ export default {
 					);
 					return cb({ status: "failure", message: err });
 				}
+
 				ActivitiesModule.runJob("ADD_ACTIVITY", {
 					userId: session.userId,
-					activityType: "added_songs_to_playlist",
-					payload: addedSongs
+					type: "playlist__import_playlist",
+					payload: {
+						message: `Imported ${addSongsStats.successful} songs to playlist <playlistId>${playlist.displayName}</playlistId>`,
+						playlistId
+					}
 				});
+
 				this.log(
 					"SUCCESS",
 					"PLAYLIST_IMPORT",
 					`Successfully imported a YouTube playlist to private playlist "${playlistId}" for user "${session.userId}". Videos in playlist: ${videosInPlaylistTotal}, songs in playlist: ${songsInPlaylistTotal}, songs successfully added: ${addSongsStats.successful}, songs failed: ${addSongsStats.failed}, already in playlist: ${addSongsStats.alreadyInPlaylist}.`
 				);
+
 				return cb({
 					status: "success",
 					message: `Playlist has been imported. ${addSongsStats.successful} were added successfully, ${addSongsStats.failed} failed (${addSongsStats.alreadyInPlaylist} were already in the playlist)`,
@@ -1134,6 +1154,41 @@ export default {
 					PlaylistsModule.runJob("UPDATE_PLAYLIST", { playlistId }, this)
 						.then(playlist => next(null, playlist))
 						.catch(next);
+				},
+
+				(playlist, next) => {
+					SongsModule.runJob("GET_SONG_FROM_ID", { songId }, this)
+						.then(res =>
+							next(null, playlist, {
+								title: res.song.title,
+								thumbnail: res.song.thumbnail,
+								artists: res.song.artists
+							})
+						)
+						.catch(() => {
+							YouTubeModule.runJob("GET_SONG", { songId }, this)
+								.then(response => next(null, playlist, response.song))
+								.catch(next);
+						});
+				},
+
+				(playlist, song, next) => {
+					const songName = song.artists ? `${song.title} by ${song.artists.join(", ")}` : song.title;
+
+					if (playlist.displayName !== "Liked Songs" && playlist.displayName !== "Disliked Songs") {
+						ActivitiesModule.runJob("ADD_ACTIVITY", {
+							userId: session.userId,
+							type: "playlist__remove_song",
+							payload: {
+								message: `Removed <songId>${songName}</songId> from playlist <playlistId>${playlist.displayName}</playlistId>`,
+								thumbnail: song.thumbnail,
+								playlistId,
+								songId
+							}
+						});
+					}
+
+					return next(null, playlist);
 				}
 			],
 			async (err, playlist) => {
@@ -1146,11 +1201,13 @@ export default {
 					);
 					return cb({ status: "failure", message: err });
 				}
+
 				this.log(
 					"SUCCESS",
 					"PLAYLIST_REMOVE_SONG",
 					`Successfully removed song "${songId}" from private playlist "${playlistId}" for user "${session.userId}".`
 				);
+
 				CacheModule.runJob("PUB", {
 					channel: "playlist.removeSong",
 					value: {
@@ -1160,6 +1217,7 @@ export default {
 						privacy: playlist.privacy
 					}
 				});
+
 				return cb({
 					status: "success",
 					message: "Song has been successfully removed from playlist",
@@ -1203,9 +1261,7 @@ export default {
 
 				(res, next) => {
 					PlaylistsModule.runJob("UPDATE_PLAYLIST", { playlistId }, this)
-						.then(playlist => {
-							next(null, playlist);
-						})
+						.then(playlist => next(null, playlist))
 						.catch(next);
 				}
 			],
@@ -1219,11 +1275,13 @@ export default {
 					);
 					return cb({ status: "failure", message: err });
 				}
+
 				this.log(
 					"SUCCESS",
 					"PLAYLIST_UPDATE_DISPLAY_NAME",
 					`Successfully updated display name to "${displayName}" for private playlist "${playlistId}" for user "${session.userId}".`
 				);
+
 				CacheModule.runJob("PUB", {
 					channel: "playlist.updateDisplayName",
 					value: {
@@ -1233,6 +1291,16 @@ export default {
 						privacy: playlist.privacy
 					}
 				});
+
+				ActivitiesModule.runJob("ADD_ACTIVITY", {
+					userId: session.userId,
+					type: "playlist__edit_display_name",
+					payload: {
+						message: `Changed display name of playlist <playlistId>${displayName}</playlistId>`,
+						playlistId
+					}
+				});
+
 				return cb({
 					status: "success",
 					message: "Playlist has been successfully updated"
@@ -1269,24 +1337,23 @@ export default {
 					userModel.updateOne(
 						{ _id: playlist.createdBy },
 						{ $pull: { "preferences.orderOfPlaylists": playlist._id } },
-						err => {
-							if (err) return next(err);
-							return next(null);
-						}
+						err => next(err, playlist)
 					);
 				},
 
-				next => {
-					PlaylistsModule.runJob("DELETE_PLAYLIST", { playlistId }, this).then(next).catch(next);
+				(playlist, next) => {
+					PlaylistsModule.runJob("DELETE_PLAYLIST", { playlistId }, this)
+						.then(() => next(null, playlist))
+						.catch(next);
 				},
 
-				next => {
+				(playlist, next) => {
 					stationModel.find({ privatePlaylist: playlistId }, (err, res) => {
-						next(err, res);
+						next(err, playlist, res);
 					});
 				},
 
-				(stations, next) => {
+				(playlist, stations, next) => {
 					async.each(
 						stations,
 						(station, next) => {
@@ -1304,13 +1371,7 @@ export default {
 									(res, next) => {
 										if (!station.partyMode) {
 											moduleManager.modules.stations
-												.runJob(
-													"UPDATE_STATION",
-													{
-														stationId: station._id
-													},
-													this
-												)
+												.runJob("UPDATE_STATION", { stationId: station._id }, this)
 												.then(station => next(null, station))
 												.catch(next);
 											CacheModule.runJob("PUB", {
@@ -1324,18 +1385,14 @@ export default {
 									}
 								],
 
-								() => {
-									next();
-								}
+								() => next()
 							);
 						},
-						() => {
-							next();
-						}
+						() => next(null, playlist)
 					);
 				}
 			],
-			async err => {
+			async (err, playlist) => {
 				if (err) {
 					err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
 					this.log(
@@ -1361,9 +1418,12 @@ export default {
 				});
 
 				ActivitiesModule.runJob("ADD_ACTIVITY", {
-					userId: session.userId,
-					activityType: "deleted_playlist",
-					payload: [playlistId]
+					userId: playlist.createdBy,
+					type: "playlist__remove",
+					payload: {
+						message: `Removed playlist <playlistId>${playlist.displayName}</playlistId>`,
+						playlistId
+					}
 				});
 
 				return cb({
@@ -1405,11 +1465,13 @@ export default {
 			async (err, playlist) => {
 				if (err) {
 					err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
+
 					this.log(
 						"ERROR",
 						"PLAYLIST_UPDATE_PRIVACY",
 						`Updating privacy to "${privacy}" for private playlist "${playlistId}" failed for user "${session.userId}". "${err}"`
 					);
+
 					return cb({ status: "failure", message: err });
 				}
 
@@ -1427,6 +1489,15 @@ export default {
 					}
 				});
 
+				ActivitiesModule.runJob("ADD_ACTIVITY", {
+					userId: session.userId,
+					type: "playlist__edit_privacy",
+					payload: {
+						message: `Changed privacy of playlist <playlistId>${playlist.displayName}</playlistId> to ${privacy}`,
+						playlistId
+					}
+				});
+
 				return cb({
 					status: "success",
 					message: "Playlist has been successfully updated"

+ 25 - 16
backend/logic/actions/reports.js

@@ -9,6 +9,7 @@ const UtilsModule = moduleManager.modules.utils;
 const IOModule = moduleManager.modules.io;
 const SongsModule = moduleManager.modules.songs;
 const CacheModule = moduleManager.modules.cache;
+const ActivitiesModule = moduleManager.modules.activities;
 
 const reportableIssues = [
 	{
@@ -224,14 +225,9 @@ export default {
 	 * @param {Function} cb - gets called with the result
 	 */
 	create: isLoginRequired(async function create(session, data, cb) {
-		const reportModel = await DBModule.runJob(
-			"GET_MODEL",
-			{
-				modelName: "report"
-			},
-			this
-		);
+		const reportModel = await DBModule.runJob("GET_MODEL", { modelName: "report" }, this);
 		const songModel = await DBModule.runJob("GET_MODEL", { modelName: "song" }, this);
+
 		async.waterfall(
 			[
 				next => {
@@ -240,10 +236,9 @@ export default {
 
 				(song, next) => {
 					if (!song) return next("Song not found.");
+
 					return SongsModule.runJob("GET_SONG", { id: song._id }, this)
-						.then(response => {
-							next(null, response.song);
-						})
+						.then(res => next(null, res.song))
 						.catch(next);
 				},
 
@@ -277,10 +272,10 @@ export default {
 							});
 					}
 
-					return next();
+					return next(null, { title: song.title, artists: song.artists, thumbnail: song.thumbnail });
 				},
 
-				next => {
+				(song, next) => {
 					const issues = [];
 
 					for (let r = 0; r < data.issues.length; r += 1) {
@@ -289,16 +284,17 @@ export default {
 
 					data.issues = issues;
 
-					next();
+					next(null, song);
 				},
 
-				next => {
+				(song, next) => {
 					data.createdBy = session.userId;
 					data.createdAt = Date.now();
-					reportModel.create(data, next);
+
+					reportModel.create(data, (err, report) => next(err, report, song));
 				}
 			],
-			async (err, report) => {
+			async (err, report, song) => {
 				if (err) {
 					err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
 					this.log(
@@ -308,11 +304,24 @@ export default {
 					);
 					return cb({ status: "failure", message: err });
 				}
+
 				CacheModule.runJob("PUB", {
 					channel: "report.create",
 					value: report
 				});
+
+				ActivitiesModule.runJob("ADD_ACTIVITY", {
+					userId: report.createdBy,
+					type: "song__report",
+					payload: {
+						message: `Reported song <songId>${song.title} by ${song.artists.join(", ")}</songId>`,
+						songId: data.song._id,
+						thumbnail: song.thumbnail
+					}
+				});
+
 				this.log("SUCCESS", "REPORTS_CREATE", `User "${session.userId}" created report for "${data.songId}".`);
+
 				return cb({
 					status: "success",
 					message: "Successfully created report"

+ 85 - 49
backend/logic/actions/songs.js

@@ -540,18 +540,16 @@ export default {
 
 		async.waterfall(
 			[
-				next => {
-					songModel.findOne({ songId: musareSongId }, next);
-				},
+				next => songModel.findOne({ songId: musareSongId }, next),
 
 				(song, next) => {
 					if (!song) return next("No song found with that id.");
-					return next(null, song._id);
+					return next(null, song);
 				},
 
-				(songId, next) => userModel.findOne({ _id: session.userId }, (err, user) => next(err, songId, user)),
+				(song, next) => userModel.findOne({ _id: session.userId }, (err, user) => next(err, song, user)),
 
-				(songId, user, next) => {
+				(song, user, next) => {
 					if (!user) return next("User does not exist.");
 
 					return this.module
@@ -568,12 +566,12 @@ export default {
 						.then(res => {
 							if (res.status === "failure")
 								return next("Unable to add song to the 'Liked Songs' playlist.");
-							return next(null, songId, user.dislikedSongsPlaylist);
+							return next(null, song, user.dislikedSongsPlaylist);
 						})
 						.catch(err => next(err));
 				},
 
-				(songId, dislikedSongsPlaylist, next) => {
+				(song, dislikedSongsPlaylist, next) => {
 					this.module
 						.runJob(
 							"RUN_ACTION2",
@@ -588,18 +586,18 @@ export default {
 						.then(res => {
 							if (res.status === "failure")
 								return next("Unable to remove song from the 'Disliked Songs' playlist.");
-							return next(null, songId);
+							return next(null, song);
 						})
 						.catch(err => next(err));
 				},
 
-				(songId, next) => {
-					SongsModule.runJob("RECALCULATE_SONG_RATINGS", { songId, musareSongId })
-						.then(ratings => next(null, songId, ratings))
+				(song, next) => {
+					SongsModule.runJob("RECALCULATE_SONG_RATINGS", { songId: song._id, musareSongId })
+						.then(ratings => next(null, song, ratings))
 						.catch(err => next(err));
 				}
 			],
-			async (err, songId, { likes, dislikes }) => {
+			async (err, song, { likes, dislikes }) => {
 				if (err) {
 					err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
 					this.log(
@@ -610,7 +608,7 @@ export default {
 					return cb({ status: "failure", message: err });
 				}
 
-				SongsModule.runJob("UPDATE_SONG", { songId });
+				SongsModule.runJob("UPDATE_SONG", { songId: song._id });
 
 				CacheModule.runJob("PUB", {
 					channel: "song.like",
@@ -624,8 +622,12 @@ export default {
 
 				ActivitiesModule.runJob("ADD_ACTIVITY", {
 					userId: session.userId,
-					activityType: "liked_song",
-					payload: [songId]
+					type: "song__like",
+					payload: {
+						message: `Liked song <songId>${song.title} by ${song.artists.join(", ")}</songId>`,
+						songId: song._id,
+						thumbnail: song.thumbnail
+					}
 				});
 
 				return cb({
@@ -657,12 +659,12 @@ export default {
 
 				(song, next) => {
 					if (!song) return next("No song found with that id.");
-					return next(null, song._id);
+					return next(null, song);
 				},
 
-				(songId, next) => userModel.findOne({ _id: session.userId }, (err, user) => next(err, songId, user)),
+				(song, next) => userModel.findOne({ _id: session.userId }, (err, user) => next(err, song, user)),
 
-				(songId, user, next) => {
+				(song, user, next) => {
 					if (!user) return next("User does not exist.");
 
 					return this.module
@@ -679,12 +681,12 @@ export default {
 						.then(res => {
 							if (res.status === "failure")
 								return next("Unable to add song to the 'Disliked Songs' playlist.");
-							return next(null, songId, user.likedSongsPlaylist);
+							return next(null, song, user.likedSongsPlaylist);
 						})
 						.catch(err => next(err));
 				},
 
-				(songId, likedSongsPlaylist, next) => {
+				(song, likedSongsPlaylist, next) => {
 					this.module
 						.runJob(
 							"RUN_ACTION2",
@@ -699,18 +701,18 @@ export default {
 						.then(res => {
 							if (res.status === "failure")
 								return next("Unable to remove song from the 'Liked Songs' playlist.");
-							return next(null, songId);
+							return next(null, song);
 						})
 						.catch(err => next(err));
 				},
 
-				(songId, next) => {
-					SongsModule.runJob("RECALCULATE_SONG_RATINGS", { songId, musareSongId })
-						.then(ratings => next(null, songId, ratings))
+				(song, next) => {
+					SongsModule.runJob("RECALCULATE_SONG_RATINGS", { songId: song._id, musareSongId })
+						.then(ratings => next(null, song, ratings))
 						.catch(err => next(err));
 				}
 			],
-			async (err, songId, { likes, dislikes }) => {
+			async (err, song, { likes, dislikes }) => {
 				if (err) {
 					err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
 					this.log(
@@ -721,7 +723,7 @@ export default {
 					return cb({ status: "failure", message: err });
 				}
 
-				SongsModule.runJob("UPDATE_SONG", { songId });
+				SongsModule.runJob("UPDATE_SONG", { songId: song._id });
 
 				CacheModule.runJob("PUB", {
 					channel: "song.dislike",
@@ -733,6 +735,16 @@ export default {
 					})
 				});
 
+				ActivitiesModule.runJob("ADD_ACTIVITY", {
+					userId: session.userId,
+					type: "song__dislike",
+					payload: {
+						message: `Disliked song <songId>${song.title} by ${song.artists.join(", ")}</songId>`,
+						songId: song._id,
+						thumbnail: song.thumbnail
+					}
+				});
+
 				return cb({
 					status: "success",
 					message: "You have successfully disliked this song."
@@ -760,12 +772,12 @@ export default {
 
 				(song, next) => {
 					if (!song) return next("No song found with that id.");
-					return next(null, song._id);
+					return next(null, song);
 				},
 
-				(songId, next) => userModel.findOne({ _id: session.userId }, (err, user) => next(err, songId, user)),
+				(song, next) => userModel.findOne({ _id: session.userId }, (err, user) => next(err, song, user)),
 
-				(songId, user, next) => {
+				(song, user, next) => {
 					if (!user) return next("User does not exist.");
 
 					return this.module
@@ -782,12 +794,12 @@ export default {
 						.then(res => {
 							if (res.status === "failure")
 								return next("Unable to remove song from the 'Disliked Songs' playlist.");
-							return next(null, songId, user.likedSongsPlaylist);
+							return next(null, song, user.likedSongsPlaylist);
 						})
 						.catch(err => next(err));
 				},
 
-				(songId, likedSongsPlaylist, next) => {
+				(song, likedSongsPlaylist, next) => {
 					this.module
 						.runJob(
 							"RUN_ACTION2",
@@ -802,18 +814,18 @@ export default {
 						.then(res => {
 							if (res.status === "failure")
 								return next("Unable to remove song from the 'Liked Songs' playlist.");
-							return next(null, songId);
+							return next(null, song);
 						})
 						.catch(err => next(err));
 				},
 
-				(songId, next) => {
-					SongsModule.runJob("RECALCULATE_SONG_RATINGS", { songId, musareSongId })
-						.then(ratings => next(null, songId, ratings))
+				(song, next) => {
+					SongsModule.runJob("RECALCULATE_SONG_RATINGS", { songId: song._id, musareSongId })
+						.then(ratings => next(null, song, ratings))
 						.catch(err => next(err));
 				}
 			],
-			async (err, songId, { likes, dislikes }) => {
+			async (err, song, { likes, dislikes }) => {
 				if (err) {
 					err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
 					this.log(
@@ -824,7 +836,7 @@ export default {
 					return cb({ status: "failure", message: err });
 				}
 
-				SongsModule.runJob("UPDATE_SONG", { songId });
+				SongsModule.runJob("UPDATE_SONG", { songId: song._id });
 
 				CacheModule.runJob("PUB", {
 					channel: "song.undislike",
@@ -836,6 +848,18 @@ export default {
 					})
 				});
 
+				ActivitiesModule.runJob("ADD_ACTIVITY", {
+					userId: session.userId,
+					type: "song__undislike",
+					payload: {
+						message: `Removed <songId>${song.title} by ${song.artists.join(
+							", "
+						)}</songId> from your Disliked Songs`,
+						songId: song._id,
+						thumbnail: song.thumbnail
+					}
+				});
+
 				return cb({
 					status: "success",
 					message: "You have successfully undisliked this song."
@@ -863,12 +887,12 @@ export default {
 
 				(song, next) => {
 					if (!song) return next("No song found with that id.");
-					return next(null, song._id);
+					return next(null, song);
 				},
 
-				(songId, next) => userModel.findOne({ _id: session.userId }, (err, user) => next(err, songId, user)),
+				(song, next) => userModel.findOne({ _id: session.userId }, (err, user) => next(err, song, user)),
 
-				(songId, user, next) => {
+				(song, user, next) => {
 					if (!user) return next("User does not exist.");
 
 					return this.module
@@ -885,12 +909,12 @@ export default {
 						.then(res => {
 							if (res.status === "failure")
 								return next("Unable to remove song from the 'Disliked Songs' playlist.");
-							return next(null, songId, user.likedSongsPlaylist);
+							return next(null, song, user.likedSongsPlaylist);
 						})
 						.catch(err => next(err));
 				},
 
-				(songId, likedSongsPlaylist, next) => {
+				(song, likedSongsPlaylist, next) => {
 					this.module
 						.runJob(
 							"RUN_ACTION2",
@@ -905,18 +929,18 @@ export default {
 						.then(res => {
 							if (res.status === "failure")
 								return next("Unable to remove song from the 'Liked Songs' playlist.");
-							return next(null, songId);
+							return next(null, song);
 						})
 						.catch(err => next(err));
 				},
 
-				(songId, next) => {
-					SongsModule.runJob("RECALCULATE_SONG_RATINGS", { songId, musareSongId })
-						.then(ratings => next(null, songId, ratings))
+				(song, next) => {
+					SongsModule.runJob("RECALCULATE_SONG_RATINGS", { songId: song._id, musareSongId })
+						.then(ratings => next(null, song, ratings))
 						.catch(err => next(err));
 				}
 			],
-			async (err, songId, { likes, dislikes }) => {
+			async (err, song, { likes, dislikes }) => {
 				if (err) {
 					err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
 					this.log(
@@ -927,7 +951,7 @@ export default {
 					return cb({ status: "failure", message: err });
 				}
 
-				SongsModule.runJob("UPDATE_SONG", { songId });
+				SongsModule.runJob("UPDATE_SONG", { songId: song._id });
 
 				CacheModule.runJob("PUB", {
 					channel: "song.unlike",
@@ -939,6 +963,18 @@ export default {
 					})
 				});
 
+				ActivitiesModule.runJob("ADD_ACTIVITY", {
+					userId: session.userId,
+					type: "song__unlike",
+					payload: {
+						message: `Removed <songId>${song.title} by ${song.artists.join(
+							", "
+						)}</songId> from your Liked Songs`,
+						songId: song._id,
+						thumbnail: song.thumbnail
+					}
+				});
+
 				return cb({
 					status: "success",
 					message: "You have successfully unliked this song."

+ 206 - 164
backend/logic/actions/stations.js

@@ -645,12 +645,14 @@ export default {
 					);
 					return cb({ status: "failure", message: err });
 				}
+
 				this.log(
 					"SUCCESS",
 					"STATIONS_GET_PLAYLIST",
 					`Got playlist for station "${stationId}" successfully.`,
 					false
 				);
+
 				return cb({ status: "success", data: playlist.songs });
 			}
 		);
@@ -1134,13 +1136,8 @@ export default {
 	 * @param cb
 	 */
 	updateName: isOwnerRequired(async function updateName(session, stationId, newName, cb) {
-		const stationModel = await DBModule.runJob(
-			"GET_MODEL",
-			{
-				modelName: "station"
-			},
-			this
-		);
+		const stationModel = await DBModule.runJob("GET_MODEL", { modelName: "station" }, this);
+
 		async.waterfall(
 			[
 				next => {
@@ -1154,20 +1151,20 @@ export default {
 
 				(res, next) => {
 					StationsModule.runJob("UPDATE_STATION", { stationId }, this)
-						.then(station => {
-							next(null, station);
-						})
+						.then(station => next(null, station))
 						.catch(next);
 				}
 			],
-			async err => {
+			async (err, station) => {
 				if (err) {
 					err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
+
 					this.log(
 						"ERROR",
 						"STATIONS_UPDATE_NAME",
 						`Updating station "${stationId}" name to "${newName}" failed. "${err}"`
 					);
+
 					return cb({ status: "failure", message: err });
 				}
 
@@ -1182,6 +1179,15 @@ export default {
 					value: { stationId, name: newName }
 				});
 
+				ActivitiesModule.runJob("ADD_ACTIVITY", {
+					userId: session.userId,
+					type: "station__edit_name",
+					payload: {
+						message: `Changed name of station <stationId>${station.displayName}</stationId> to ${newName}`,
+						stationId
+					}
+				});
+
 				return cb({
 					status: "success",
 					message: "Successfully updated the name."
@@ -1199,13 +1205,7 @@ export default {
 	 * @param cb
 	 */
 	updateDisplayName: isOwnerRequired(async function updateDisplayName(session, stationId, newDisplayName, cb) {
-		const stationModel = await DBModule.runJob(
-			"GET_MODEL",
-			{
-				modelName: "station"
-			},
-			this
-		);
+		const stationModel = await DBModule.runJob("GET_MODEL", { modelName: "station" }, this);
 
 		async.waterfall(
 			[
@@ -1220,9 +1220,7 @@ export default {
 
 				(res, next) => {
 					StationsModule.runJob("UPDATE_STATION", { stationId }, this)
-						.then(station => {
-							next(null, station);
-						})
+						.then(station => next(null, station))
 						.catch(next);
 				}
 			],
@@ -1248,6 +1246,15 @@ export default {
 					value: { stationId, displayName: newDisplayName }
 				});
 
+				ActivitiesModule.runJob("ADD_ACTIVITY", {
+					userId: session.userId,
+					type: "station__edit_display_name",
+					payload: {
+						message: `Changed display name of station <stationId>${newDisplayName}</stationId>`,
+						stationId
+					}
+				});
+
 				return cb({
 					status: "success",
 					message: "Successfully updated the display name."
@@ -1265,13 +1272,7 @@ export default {
 	 * @param cb
 	 */
 	updateDescription: isOwnerRequired(async function updateDescription(session, stationId, newDescription, cb) {
-		const stationModel = await DBModule.runJob(
-			"GET_MODEL",
-			{
-				modelName: "station"
-			},
-			this
-		);
+		const stationModel = await DBModule.runJob("GET_MODEL", { modelName: "station" }, this);
 
 		async.waterfall(
 			[
@@ -1286,13 +1287,11 @@ export default {
 
 				(res, next) => {
 					StationsModule.runJob("UPDATE_STATION", { stationId }, this)
-						.then(station => {
-							next(null, station);
-						})
+						.then(station => next(null, station))
 						.catch(next);
 				}
 			],
-			async err => {
+			async (err, station) => {
 				if (err) {
 					err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
 					this.log(
@@ -1309,6 +1308,15 @@ export default {
 					`Updated station "${stationId}" description to "${newDescription}" successfully.`
 				);
 
+				ActivitiesModule.runJob("ADD_ACTIVITY", {
+					userId: session.userId,
+					type: "station__edit_description",
+					payload: {
+						message: `Changed description of station <stationId>${station.displayName}</stationId> to ${newDescription}`,
+						stationId
+					}
+				});
+
 				CacheModule.runJob("PUB", {
 					channel: "station.descriptionUpdate",
 					value: { stationId, description: newDescription }
@@ -1331,14 +1339,10 @@ export default {
 	 * @param cb
 	 */
 	updatePrivacy: isOwnerRequired(async function updatePrivacy(session, stationId, newPrivacy, cb) {
-		const stationModel = await DBModule.runJob(
-			"GET_MODEL",
-			{
-				modelName: "station"
-			},
-			this
-		);
+		const stationModel = await DBModule.runJob("GET_MODEL", { modelName: "station" }, this);
+
 		let previousPrivacy = null;
+
 		async.waterfall(
 			[
 				next => {
@@ -1364,13 +1368,11 @@ export default {
 
 				(res, next) => {
 					StationsModule.runJob("UPDATE_STATION", { stationId }, this)
-						.then(station => {
-							next(null, station);
-						})
+						.then(station => next(null, station))
 						.catch(next);
 				}
 			],
-			async err => {
+			async (err, station) => {
 				if (err) {
 					err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
 					this.log(
@@ -1385,10 +1387,21 @@ export default {
 					"STATIONS_UPDATE_PRIVACY",
 					`Updated station "${stationId}" privacy to "${newPrivacy}" successfully.`
 				);
+
 				CacheModule.runJob("PUB", {
 					channel: "station.privacyUpdate",
 					value: { stationId, previousPrivacy }
 				});
+
+				ActivitiesModule.runJob("ADD_ACTIVITY", {
+					userId: session.userId,
+					type: "station__edit_privacy",
+					payload: {
+						message: `Changed privacy of station <stationId>${station.displayName}</stationId> to ${newPrivacy}`,
+						stationId
+					}
+				});
+
 				return cb({
 					status: "success",
 					message: "Successfully updated the privacy."
@@ -1426,13 +1439,11 @@ export default {
 
 				(res, next) => {
 					StationsModule.runJob("UPDATE_STATION", { stationId }, this)
-						.then(station => {
-							next(null, station);
-						})
+						.then(station => next(null, station))
 						.catch(next);
 				}
 			],
-			async err => {
+			async (err, station) => {
 				if (err) {
 					err = await UtilsModule.runJob("GET_ERROR", { error: err });
 					this.log(
@@ -1442,11 +1453,23 @@ export default {
 					);
 					return cb({ status: "failure", message: err });
 				}
+
 				this.log(
 					"SUCCESS",
 					"STATIONS_UPDATE_GENRES",
 					`Updated station "${stationId}" genres to "${newGenres}" successfully.`
 				);
+
+				ActivitiesModule.runJob("ADD_ACTIVITY", {
+					userId: session.userId,
+					type: "station__edit_genres",
+					payload: {
+						message: `Updated genres of station <stationId>${station.displayName}</stationId> to 
+						${newGenres.join(", ")}`,
+						stationId
+					}
+				});
+
 				return cb({
 					status: "success",
 					message: "Successfully updated the genres."
@@ -1469,13 +1492,8 @@ export default {
 		newBlacklistedGenres,
 		cb
 	) {
-		const stationModel = await DBModule.runJob(
-			"GET_MODEL",
-			{
-				modelName: "station"
-			},
-			this
-		);
+		const stationModel = await DBModule.runJob("GET_MODEL", { modelName: "station" }, this);
+
 		async.waterfall(
 			[
 				next => {
@@ -1493,13 +1511,11 @@ export default {
 
 				(res, next) => {
 					StationsModule.runJob("UPDATE_STATION", { stationId }, this)
-						.then(station => {
-							next(null, station);
-						})
+						.then(station => next(null, station))
 						.catch(next);
 				}
 			],
-			async err => {
+			async (err, station) => {
 				if (err) {
 					err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
 					this.log(
@@ -1509,11 +1525,24 @@ export default {
 					);
 					return cb({ status: "failure", message: err });
 				}
+
 				this.log(
 					"SUCCESS",
 					"STATIONS_UPDATE_BLACKLISTED_GENRES",
 					`Updated station "${stationId}" blacklisted genres to "${newBlacklistedGenres}" successfully.`
 				);
+
+				ActivitiesModule.runJob("ADD_ACTIVITY", {
+					userId: session.userId,
+					type: "station__edit_blacklisted_genres",
+					payload: {
+						message: `Updated blacklisted genres of station <stationId>${
+							station.displayName
+						}</stationId> to ${newBlacklistedGenres.join(", ")}`,
+						stationId
+					}
+				});
+
 				return cb({
 					status: "success",
 					message: "Successfully updated the blacklisted genres."
@@ -1531,13 +1560,8 @@ export default {
 	 * @param cb
 	 */
 	updatePartyMode: isOwnerRequired(async function updatePartyMode(session, stationId, newPartyMode, cb) {
-		const stationModel = await DBModule.runJob(
-			"GET_MODEL",
-			{
-				modelName: "station"
-			},
-			this
-		);
+		const stationModel = await DBModule.runJob("GET_MODEL", { modelName: "station" }, this);
+
 		async.waterfall(
 			[
 				next => {
@@ -1608,13 +1632,8 @@ export default {
 	 * @param cb
 	 */
 	updateTheme: isOwnerRequired(async function updateTheme(session, stationId, newTheme, cb) {
-		const stationModel = await DBModule.runJob(
-			"GET_MODEL",
-			{
-				modelName: "station"
-			},
-			this
-		);
+		const stationModel = await DBModule.runJob("GET_MODEL", { modelName: "station" }, this);
+
 		async.waterfall(
 			[
 				next => {
@@ -1638,13 +1657,11 @@ export default {
 
 				(res, next) => {
 					StationsModule.runJob("UPDATE_STATION", { stationId }, this)
-						.then(station => {
-							next(null, station);
-						})
+						.then(station => next(null, station))
 						.catch(next);
 				}
 			],
-			async err => {
+			async (err, station) => {
 				if (err) {
 					err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
 					this.log(
@@ -1654,15 +1671,27 @@ export default {
 					);
 					return cb({ status: "failure", message: err });
 				}
+
 				this.log(
 					"SUCCESS",
 					"STATIONS_UPDATE_THEME",
 					`Updated station "${stationId}" theme to "${newTheme}" successfully.`
 				);
+
 				CacheModule.runJob("PUB", {
 					channel: "station.themeUpdate",
 					value: { stationId }
 				});
+
+				ActivitiesModule.runJob("ADD_ACTIVITY", {
+					userId: session.userId,
+					type: "station__edit_theme",
+					payload: {
+						message: `Changed theme of station <stationId>${station.displayName}</stationId> to ${newTheme}`,
+						stationId
+					}
+				});
+
 				return cb({
 					status: "success",
 					message: "Successfully updated the theme."
@@ -1810,40 +1839,47 @@ export default {
 	 * @param cb
 	 */
 	remove: isOwnerRequired(async function remove(session, stationId, cb) {
-		const stationModel = await DBModule.runJob(
-			"GET_MODEL",
-			{
-				modelName: "station"
-			},
-			this
-		);
+		const stationModel = await DBModule.runJob("GET_MODEL", { modelName: "station" }, this);
 
 		async.waterfall(
 			[
 				next => {
-					stationModel.deleteOne({ _id: stationId }, err => next(err));
+					stationModel.findById(stationId, (err, station) => {
+						if (err) return next(err);
+						return next(null, station);
+					});
 				},
 
-				next => {
-					CacheModule.runJob("HDEL", { table: "stations", key: stationId }, this).then(next).catch(next);
+				(station, next) => {
+					stationModel.deleteOne({ _id: stationId }, err => next(err, station));
+				},
+
+				(station, next) => {
+					CacheModule.runJob("HDEL", { table: "stations", key: stationId }, this)
+						.then(next(null, station))
+						.catch(next);
 				}
 			],
-			async err => {
+			async (err, station) => {
 				if (err) {
 					err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
 					this.log("ERROR", "STATIONS_REMOVE", `Removing station "${stationId}" failed. "${err}"`);
 					return cb({ status: "failure", message: err });
 				}
+
 				this.log("SUCCESS", "STATIONS_REMOVE", `Removing station "${stationId}" successfully.`);
+
 				CacheModule.runJob("PUB", {
 					channel: "station.remove",
 					value: stationId
 				});
+
 				ActivitiesModule.runJob("ADD_ACTIVITY", {
 					userId: session.userId,
-					activityType: "deleted_station",
-					payload: [stationId]
+					type: "station__remove",
+					payload: { message: `Removed a station named <stationId>${station.displayName}</stationId>` }
 				});
+
 				return cb({
 					status: "success",
 					message: "Successfully removed."
@@ -1861,13 +1897,7 @@ export default {
 	 */
 	create: isLoginRequired(async function create(session, data, cb) {
 		const userModel = await DBModule.runJob("GET_MODEL", { modelName: "user" }, this);
-		const stationModel = await DBModule.runJob(
-			"GET_MODEL",
-			{
-				modelName: "station"
-			},
-			this
-		);
+		const stationModel = await DBModule.runJob("GET_MODEL", { modelName: "station" }, this);
 
 		data.name = data.name.toLowerCase();
 
@@ -1910,6 +1940,7 @@ export default {
 			"auth",
 			"reset_password"
 		];
+
 		async.waterfall(
 			[
 				next => {
@@ -1984,15 +2015,21 @@ export default {
 					return cb({ status: "failure", message: err });
 				}
 				this.log("SUCCESS", "STATIONS_CREATE", `Created station "${station._id}" successfully.`);
+
 				CacheModule.runJob("PUB", {
 					channel: "station.create",
 					value: station._id
 				});
+
 				ActivitiesModule.runJob("ADD_ACTIVITY", {
 					userId: session.userId,
-					activityType: "created_station",
-					payload: [station._id]
+					type: "station__create",
+					payload: {
+						message: `Created a station named <stationId>${station.displayName}</stationId>`,
+						stationId: station._id
+					}
 				});
+
 				return cb({
 					status: "success",
 					message: "Successfully created station."
@@ -2150,7 +2187,7 @@ export default {
 				(song, next) => {
 					stationModel.updateOne(
 						{ _id: stationId },
-						{ $push: { queue: song } },
+						{ $pushr: { queue: song } },
 						{ runValidators: true },
 						next
 					);
@@ -2158,9 +2195,7 @@ export default {
 
 				(res, next) => {
 					StationsModule.runJob("UPDATE_STATION", { stationId }, this)
-						.then(station => {
-							next(null, station);
-						})
+						.then(station => next(null, station))
 						.catch(next);
 				}
 			],
@@ -2174,15 +2209,18 @@ export default {
 					);
 					return cb({ status: "failure", message: err });
 				}
+
 				this.log(
 					"SUCCESS",
 					"STATIONS_ADD_SONG_TO_QUEUE",
 					`Added song "${songId}" to station "${stationId}" successfully.`
 				);
+
 				CacheModule.runJob("PUB", {
 					channel: "station.queueUpdate",
 					value: stationId
 				});
+
 				return cb({
 					status: "success",
 					message: "Successfully added song to queue."
@@ -2200,27 +2238,21 @@ export default {
 	 * @param cb
 	 */
 	removeFromQueue: isOwnerRequired(async function removeFromQueue(session, stationId, songId, cb) {
-		const stationModel = await DBModule.runJob(
-			"GET_MODEL",
-			{
-				modelName: "station"
-			},
-			this
-		);
+		const stationModel = await DBModule.runJob("GET_MODEL", { modelName: "station" }, this);
+
 		async.waterfall(
 			[
 				next => {
 					if (!songId) return next("Invalid song id.");
 					return StationsModule.runJob("GET_STATION", { stationId }, this)
-						.then(station => {
-							next(null, station);
-						})
+						.then(station => next(null, station))
 						.catch(next);
 				},
 
 				(station, next) => {
 					if (!station) return next("Station not found.");
 					if (station.type !== "community") return next("Station is not a community station.");
+
 					return async.each(
 						station.queue,
 						(queueSong, next) => {
@@ -2240,9 +2272,7 @@ export default {
 
 				(res, next) => {
 					StationsModule.runJob("UPDATE_STATION", { stationId }, this)
-						.then(station => {
-							next(null, station);
-						})
+						.then(station => next(null, station))
 						.catch(next);
 				}
 			],
@@ -2256,15 +2286,18 @@ export default {
 					);
 					return cb({ status: "failure", message: err });
 				}
+
 				this.log(
 					"SUCCESS",
 					"STATIONS_REMOVE_SONG_TO_QUEUE",
 					`Removed song "${songId}" from station "${stationId}" successfully.`
 				);
+
 				CacheModule.runJob("PUB", {
 					channel: "station.queueUpdate",
 					value: stationId
 				});
+
 				return cb({
 					status: "success",
 					message: "Successfully removed song from queue."
@@ -2285,9 +2318,7 @@ export default {
 			[
 				next => {
 					StationsModule.runJob("GET_STATION", { stationId }, this)
-						.then(station => {
-							next(null, station);
-						})
+						.then(station => next(null, station))
 						.catch(next);
 				},
 
@@ -2298,14 +2329,7 @@ export default {
 				},
 
 				(station, next) => {
-					StationsModule.runJob(
-						"CAN_USER_VIEW_STATION",
-						{
-							station,
-							userId: session.userId
-						},
-						this
-					)
+					StationsModule.runJob("CAN_USER_VIEW_STATION", { station, userId: session.userId }, this)
 						.then(canView => {
 							if (canView) return next(null, station);
 							return next("Insufficient permissions.");
@@ -2323,7 +2347,9 @@ export default {
 					);
 					return cb({ status: "failure", message: err });
 				}
+
 				this.log("SUCCESS", "STATIONS_GET_QUEUE", `Got queue for station "${stationId}" successfully.`);
+
 				return cb({
 					status: "success",
 					message: "Successfully got queue.",
@@ -2342,27 +2368,14 @@ export default {
 	 * @param cb
 	 */
 	selectPrivatePlaylist: isOwnerRequired(async function selectPrivatePlaylist(session, stationId, playlistId, cb) {
-		const stationModel = await DBModule.runJob(
-			"GET_MODEL",
-			{
-				modelName: "station"
-			},
-			this
-		);
-		const playlistModel = await DBModule.runJob(
-			"GET_MODEL",
-			{
-				modelName: "playlist"
-			},
-			this
-		);
+		const stationModel = await DBModule.runJob("GET_MODEL", { modelName: "station" }, this);
+		const playlistModel = await DBModule.runJob("GET_MODEL", { modelName: "playlist" }, this);
+
 		async.waterfall(
 			[
 				next => {
 					StationsModule.runJob("GET_STATION", { stationId }, this)
-						.then(station => {
-							next(null, station);
-						})
+						.then(station => next(null, station))
 						.catch(next);
 				},
 
@@ -2392,9 +2405,7 @@ export default {
 
 				(res, next) => {
 					StationsModule.runJob("UPDATE_STATION", { stationId }, this)
-						.then(station => {
-							next(null, station);
-						})
+						.then(station => next(null, station))
 						.catch(next);
 				}
 			],
@@ -2408,15 +2419,19 @@ export default {
 					);
 					return cb({ status: "failure", message: err });
 				}
+
 				this.log(
 					"SUCCESS",
 					"STATIONS_SELECT_PRIVATE_PLAYLIST",
 					`Selected private playlist "${playlistId}" for station "${stationId}" successfully.`
 				);
+
 				NotificationsModule.runJob("UNSCHEDULE", {
 					name: `stations.nextSong?id${stationId}`
 				});
+
 				if (!station.partyMode) StationsModule.runJob("SKIP_STATION", { stationId });
+
 				CacheModule.runJob("PUB", {
 					channel: "privatePlaylist.selected",
 					value: {
@@ -2424,6 +2439,7 @@ export default {
 						stationId
 					}
 				});
+
 				return cb({
 					status: "success",
 					message: "Successfully selected playlist."
@@ -2528,45 +2544,42 @@ export default {
 			[
 				next => {
 					StationsModule.runJob("GET_STATION", { stationId }, this)
-						.then(station => {
-							next(null, station);
-						})
+						.then(station => next(null, station))
 						.catch(next);
 				},
 
 				(station, next) => {
 					if (!station) return next("Station not found.");
-					return StationsModule.runJob(
-						"CAN_USER_VIEW_STATION",
-						{
-							station,
-							userId: session.userId
-						},
-						this
-					)
+					return StationsModule.runJob("CAN_USER_VIEW_STATION", { station, userId: session.userId }, this)
 						.then(canView => {
-							if (canView) return next();
+							if (canView) return next(null, station);
 							return next("Insufficient permissions.");
 						})
 						.catch(err => next(err));
 				},
 
-				next => {
-					userModel.updateOne({ _id: session.userId }, { $addToSet: { favoriteStations: stationId } }, next);
+				(station, next) => {
+					userModel.updateOne(
+						{ _id: session.userId },
+						{ $addToSet: { favoriteStations: stationId } },
+						(err, res) => next(err, station, res)
+					);
 				},
 
-				(res, next) => {
+				(station, res, next) => {
 					if (res.nModified === 0) return next("The station was already favorited.");
-					return next();
+					return next(null, station);
 				}
 			],
-			async err => {
+			async (err, station) => {
 				if (err) {
 					err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
 					this.log("ERROR", "FAVORITE_STATION", `Favoriting station "${stationId}" failed. "${err}"`);
 					return cb({ status: "failure", message: err });
 				}
+
 				this.log("SUCCESS", "FAVORITE_STATION", `Favorited station "${stationId}" successfully.`);
+
 				CacheModule.runJob("PUB", {
 					channel: "user.favoritedStation",
 					value: {
@@ -2574,6 +2587,16 @@ export default {
 						stationId
 					}
 				});
+
+				ActivitiesModule.runJob("ADD_ACTIVITY", {
+					userId: session.userId,
+					type: "station__favorite",
+					payload: {
+						message: `Favorited station <stationId>${station.displayName}</stationId>`,
+						stationId
+					}
+				});
+
 				return cb({
 					status: "success",
 					message: "Succesfully favorited station."
@@ -2584,6 +2607,7 @@ export default {
 
 	unfavoriteStation: isLoginRequired(async function unfavoriteStation(session, stationId, cb) {
 		const userModel = await DBModule.runJob("GET_MODEL", { modelName: "user" }, this);
+
 		async.waterfall(
 			[
 				next => {
@@ -2593,15 +2617,23 @@ export default {
 				(res, next) => {
 					if (res.nModified === 0) return next("The station wasn't favorited.");
 					return next();
+				},
+
+				next => {
+					StationsModule.runJob("GET_STATION", { stationId }, this)
+						.then(station => next(null, station))
+						.catch(next);
 				}
 			],
-			async err => {
+			async (err, station) => {
 				if (err) {
 					err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
 					this.log("ERROR", "UNFAVORITE_STATION", `Unfavoriting station "${stationId}" failed. "${err}"`);
 					return cb({ status: "failure", message: err });
 				}
+
 				this.log("SUCCESS", "UNFAVORITE_STATION", `Unfavorited station "${stationId}" successfully.`);
+
 				CacheModule.runJob("PUB", {
 					channel: "user.unfavoritedStation",
 					value: {
@@ -2609,6 +2641,16 @@ export default {
 						stationId
 					}
 				});
+
+				ActivitiesModule.runJob("ADD_ACTIVITY", {
+					userId: session.userId,
+					type: "station__unfavorite",
+					payload: {
+						message: `Unfavorited station <stationId>${station.displayName}</stationId>`,
+						stationId
+					}
+				});
+
 				return cb({
 					status: "success",
 					message: "Succesfully unfavorited station."

+ 157 - 150
backend/logic/actions/users.js

@@ -462,7 +462,7 @@ export default {
 					);
 				}
 			],
-			async (err, user) => {
+			async (err, userId) => {
 				if (err && err !== true) {
 					err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
 					this.log(
@@ -474,9 +474,11 @@ export default {
 				}
 
 				ActivitiesModule.runJob("ADD_ACTIVITY", {
-					userId: user._id,
-					activityType: "created_account"
+					userId,
+					type: "user__joined",
+					payload: { message: "Welcome to Musare!" }
 				});
+
 				this.log(
 					"SUCCESS",
 					"USER_PASSWORD_REGISTER",
@@ -723,8 +725,8 @@ export default {
 		async.waterfall(
 			[
 				next => {
-					userModel.updateOne(
-						{ _id: session.userId },
+					userModel.findByIdAndUpdate(
+						session.userId,
 						{
 							$set: {
 								preferences: {
@@ -734,12 +736,12 @@ export default {
 								}
 							}
 						},
-						{ runValidators: true },
+						{ new: false },
 						next
 					);
 				}
 			],
-			async err => {
+			async (err, user) => {
 				if (err) {
 					err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
 
@@ -762,6 +764,24 @@ export default {
 					}
 				});
 
+				if (preferences.nightmode !== user.preferences.nightmode)
+					ActivitiesModule.runJob("ADD_ACTIVITY", {
+						userId: session.userId,
+						type: "user__toggle_nightmode",
+						payload: { message: preferences.nightmode ? "Enabled nightmode" : "Disabled nightmode" }
+					});
+
+				if (preferences.autoSkipDisliked !== user.preferences.autoSkipDisliked)
+					ActivitiesModule.runJob("ADD_ACTIVITY", {
+						userId: session.userId,
+						type: "user__toggle_autoskip_disliked_songs",
+						payload: {
+							message: preferences.autoSkipDisliked
+								? "Enabled the autoskipping of your disliked songs"
+								: "Disabled the autoskipping of your disliked songs"
+						}
+					});
+
 				this.log(
 					"SUCCESS",
 					"UPDATE_USER_PREFERENCES",
@@ -988,9 +1008,7 @@ export default {
 						},
 						this
 					)
-						.then(session => {
-							next(null, session);
-						})
+						.then(session => next(null, session))
 						.catch(next);
 				},
 
@@ -1047,13 +1065,8 @@ export default {
 	 * @param {Function} cb - gets called with the result
 	 */
 	updateUsername: isLoginRequired(async function updateUsername(session, updatingUserId, newUsername, cb) {
-		const userModel = await DBModule.runJob(
-			"GET_MODEL",
-			{
-				modelName: "user"
-			},
-			this
-		);
+		const userModel = await DBModule.runJob("GET_MODEL", { modelName: "user" }, this);
+
 		async.waterfall(
 			[
 				next => {
@@ -1139,20 +1152,8 @@ export default {
 		newEmail = newEmail.toLowerCase();
 		const verificationToken = await UtilsModule.runJob("GENERATE_RANDOM_STRING", { length: 64 }, this);
 
-		const userModel = await DBModule.runJob(
-			"GET_MODEL",
-			{
-				modelName: "user"
-			},
-			this
-		);
-		const verifyEmailSchema = await MailModule.runJob(
-			"GET_SCHEMA",
-			{
-				schemaName: "verifyEmail"
-			},
-			this
-		);
+		const userModel = await DBModule.runJob("GET_MODEL", { modelName: "user" }, this);
+		const verifyEmailSchema = await MailModule.runJob("GET_SCHEMA", { schemaName: "verifyEmail" }, this);
 
 		async.waterfall(
 			[
@@ -1290,18 +1291,21 @@ export default {
 						"UPDATE_NAME",
 						`Couldn't update name for user "${updatingUserId}" to name "${newName}". "${err}"`
 					);
-					cb({ status: "failure", message: err });
-				} else {
-					this.log(
-						"SUCCESS",
-						"UPDATE_NAME",
-						`Updated name for user "${updatingUserId}" to name "${newName}".`
-					);
-					cb({
-						status: "success",
-						message: "Name updated successfully"
-					});
+					return cb({ status: "failure", message: err });
 				}
+
+				ActivitiesModule.runJob("ADD_ACTIVITY", {
+					userId: updatingUserId,
+					type: "user__edit_name",
+					payload: { message: `Changed name to ${newName}` }
+				});
+
+				this.log("SUCCESS", "UPDATE_NAME", `Updated name for user "${updatingUserId}" to name "${newName}".`);
+
+				return cb({
+					status: "success",
+					message: "Name updated successfully"
+				});
 			}
 		);
 	}),
@@ -1358,6 +1362,12 @@ export default {
 					return cb({ status: "failure", message: err });
 				}
 
+				ActivitiesModule.runJob("ADD_ACTIVITY", {
+					userId: updatingUserId,
+					type: "user__edit_location",
+					payload: { message: `Changed location to ${newLocation}` }
+				});
+
 				this.log(
 					"SUCCESS",
 					"UPDATE_LOCATION",
@@ -1381,13 +1391,7 @@ export default {
 	 * @param {Function} cb - gets called with the result
 	 */
 	updateBio: isLoginRequired(async function updateBio(session, updatingUserId, newBio, cb) {
-		const userModel = await DBModule.runJob(
-			"GET_MODEL",
-			{
-				modelName: "user"
-			},
-			this
-		);
+		const userModel = await DBModule.runJob("GET_MODEL", { modelName: "user" }, this);
 
 		async.waterfall(
 			[
@@ -1419,14 +1423,21 @@ export default {
 						"UPDATE_BIO",
 						`Couldn't update bio for user "${updatingUserId}" to bio "${newBio}". "${err}"`
 					);
-					cb({ status: "failure", message: err });
-				} else {
-					this.log("SUCCESS", "UPDATE_BIO", `Updated bio for user "${updatingUserId}" to bio "${newBio}".`);
-					cb({
-						status: "success",
-						message: "Bio updated successfully"
-					});
+					return cb({ status: "failure", message: err });
 				}
+
+				ActivitiesModule.runJob("ADD_ACTIVITY", {
+					userId: updatingUserId,
+					type: "user__edit_bio",
+					payload: { message: `Changed bio to ${newBio}` }
+				});
+
+				this.log("SUCCESS", "UPDATE_BIO", `Updated bio for user "${updatingUserId}" to bio "${newBio}".`);
+
+				return cb({
+					status: "success",
+					message: "Bio updated successfully"
+				});
 			}
 		);
 	}),
@@ -1440,13 +1451,7 @@ export default {
 	 * @param {Function} cb - gets called with the result
 	 */
 	updateAvatarType: isLoginRequired(async function updateAvatarType(session, updatingUserId, newAvatar, cb) {
-		const userModel = await DBModule.runJob(
-			"GET_MODEL",
-			{
-				modelName: "user"
-			},
-			this
-		);
+		const userModel = await DBModule.runJob("GET_MODEL", { modelName: "user" }, this);
 
 		async.waterfall(
 			[
@@ -1481,6 +1486,12 @@ export default {
 					return cb({ status: "failure", message: err });
 				}
 
+				ActivitiesModule.runJob("ADD_ACTIVITY", {
+					userId: updatingUserId,
+					type: "user__edit_avatar",
+					payload: { message: `Changed avatar to use ${newAvatar.type}` }
+				});
+
 				this.log(
 					"SUCCESS",
 					"UPDATE_AVATAR_TYPE",
@@ -1505,13 +1516,8 @@ export default {
 	 */
 	updateRole: isAdminRequired(async function updateRole(session, updatingUserId, newRole, cb) {
 		newRole = newRole.toLowerCase();
-		const userModel = await DBModule.runJob(
-			"GET_MODEL",
-			{
-				modelName: "user"
-			},
-			this
-		);
+		const userModel = await DBModule.runJob("GET_MODEL", { modelName: "user" }, this);
+
 		async.waterfall(
 			[
 				next => {
@@ -1627,6 +1633,7 @@ export default {
 				}
 
 				this.log("SUCCESS", "UPDATE_PASSWORD", `User '${session.userId}' updated their password.`);
+
 				return cb({
 					status: "success",
 					message: "Password successfully updated."
@@ -1819,18 +1826,20 @@ export default {
 				if (err && err !== true) {
 					err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
 					this.log("ERROR", "ADD_PASSWORD_WITH_CODE", `Code '${code}' failed to add password. '${err}'`);
-					cb({ status: "failure", message: err });
-				} else {
-					this.log("SUCCESS", "ADD_PASSWORD_WITH_CODE", `Code '${code}' successfully added password.`);
-					CacheModule.runJob("PUB", {
-						channel: "user.linkPassword",
-						value: session.userId
-					});
-					cb({
-						status: "success",
-						message: "Successfully added password."
-					});
+					return cb({ status: "failure", message: err });
 				}
+
+				this.log("SUCCESS", "ADD_PASSWORD_WITH_CODE", `Code '${code}' successfully added password.`);
+
+				CacheModule.runJob("PUB", {
+					channel: "user.linkPassword",
+					value: session.userId
+				});
+
+				return cb({
+					status: "success",
+					message: "Successfully added password."
+				});
 			}
 		);
 	}),
@@ -1864,22 +1873,20 @@ export default {
 						"UNLINK_PASSWORD",
 						`Unlinking password failed for userId '${session.userId}'. '${err}'`
 					);
-					cb({ status: "failure", message: err });
-				} else {
-					this.log(
-						"SUCCESS",
-						"UNLINK_PASSWORD",
-						`Unlinking password successful for userId '${session.userId}'.`
-					);
-					CacheModule.runJob("PUB", {
-						channel: "user.unlinkPassword",
-						value: session.userId
-					});
-					cb({
-						status: "success",
-						message: "Successfully unlinked password."
-					});
+					return cb({ status: "failure", message: err });
 				}
+
+				this.log("SUCCESS", "UNLINK_PASSWORD", `Unlinking password successful for userId '${session.userId}'.`);
+
+				CacheModule.runJob("PUB", {
+					channel: "user.unlinkPassword",
+					value: session.userId
+				});
+
+				return cb({
+					status: "success",
+					message: "Successfully unlinked password."
+				});
 			}
 		);
 	}),
@@ -1913,18 +1920,20 @@ export default {
 						"UNLINK_GITHUB",
 						`Unlinking GitHub failed for userId '${session.userId}'. '${err}'`
 					);
-					cb({ status: "failure", message: err });
-				} else {
-					this.log("SUCCESS", "UNLINK_GITHUB", `Unlinking GitHub successful for userId '${session.userId}'.`);
-					CacheModule.runJob("PUB", {
-						channel: "user.unlinkGithub",
-						value: session.userId
-					});
-					cb({
-						status: "success",
-						message: "Successfully unlinked GitHub."
-					});
+					return cb({ status: "failure", message: err });
 				}
+
+				this.log("SUCCESS", "UNLINK_GITHUB", `Unlinking GitHub successful for userId '${session.userId}'.`);
+
+				CacheModule.runJob("PUB", {
+					channel: "user.unlinkGithub",
+					value: session.userId
+				});
+
+				return cb({
+					status: "success",
+					message: "Successfully unlinked GitHub."
+				});
 			}
 		);
 	}),
@@ -1942,9 +1951,7 @@ export default {
 
 		const resetPasswordRequestSchema = await MailModule.runJob(
 			"GET_SCHEMA",
-			{
-				schemaName: "resetPasswordRequest"
-			},
+			{ schemaName: "resetPasswordRequest" },
 			this
 		);
 
@@ -1993,18 +2000,19 @@ export default {
 						"REQUEST_PASSWORD_RESET",
 						`Email '${email}' failed to request password reset. '${err}'`
 					);
-					cb({ status: "failure", message: err });
-				} else {
-					this.log(
-						"SUCCESS",
-						"REQUEST_PASSWORD_RESET",
-						`Email '${email}' successfully requested a password reset.`
-					);
-					cb({
-						status: "success",
-						message: "Successfully requested password reset."
-					});
+					return cb({ status: "failure", message: err });
 				}
+
+				this.log(
+					"SUCCESS",
+					"REQUEST_PASSWORD_RESET",
+					`Email '${email}' successfully requested a password reset.`
+				);
+
+				return cb({
+					status: "success",
+					message: "Successfully requested password reset."
+				});
 			}
 		);
 	},
@@ -2035,14 +2043,15 @@ export default {
 				if (err && err !== true) {
 					err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
 					this.log("ERROR", "VERIFY_PASSWORD_RESET_CODE", `Code '${code}' failed to verify. '${err}'`);
-					cb({ status: "failure", message: err });
-				} else {
-					this.log("SUCCESS", "VERIFY_PASSWORD_RESET_CODE", `Code '${code}' successfully verified.`);
-					cb({
-						status: "success",
-						message: "Successfully verified password reset code."
-					});
+					return cb({ status: "failure", message: err });
 				}
+
+				this.log("SUCCESS", "VERIFY_PASSWORD_RESET_CODE", `Code '${code}' successfully verified.`);
+
+				return cb({
+					status: "success",
+					message: "Successfully verified password reset code."
+				});
 			}
 		);
 	},
@@ -2107,18 +2116,15 @@ export default {
 						"CHANGE_PASSWORD_WITH_RESET_CODE",
 						`Code '${code}' failed to change password. '${err}'`
 					);
-					cb({ status: "failure", message: err });
-				} else {
-					this.log(
-						"SUCCESS",
-						"CHANGE_PASSWORD_WITH_RESET_CODE",
-						`Code '${code}' successfully changed password.`
-					);
-					cb({
-						status: "success",
-						message: "Successfully changed password."
-					});
+					return cb({ status: "failure", message: err });
 				}
+
+				this.log("SUCCESS", "CHANGE_PASSWORD_WITH_RESET_CODE", `Code '${code}' successfully changed password.`);
+
+				return cb({
+					status: "success",
+					message: "Successfully changed password."
+				});
 			}
 		);
 	},
@@ -2211,18 +2217,19 @@ export default {
 						"BAN_USER_BY_ID",
 						`User ${session.userId} failed to ban user ${userId} with the reason ${reason}. '${err}'`
 					);
-					cb({ status: "failure", message: err });
-				} else {
-					this.log(
-						"SUCCESS",
-						"BAN_USER_BY_ID",
-						`User ${session.userId} has successfully banned user ${userId} with the reason ${reason}.`
-					);
-					cb({
-						status: "success",
-						message: "Successfully banned user."
-					});
+					return cb({ status: "failure", message: err });
 				}
+
+				this.log(
+					"SUCCESS",
+					"BAN_USER_BY_ID",
+					`User ${session.userId} has successfully banned user ${userId} with the reason ${reason}.`
+				);
+
+				return cb({
+					status: "success",
+					message: "Successfully banned user."
+				});
 			}
 		);
 	})

+ 27 - 28
backend/logic/activities.js

@@ -36,8 +36,13 @@ class _ActivitiesModule extends CoreClass {
 	 *
 	 * @param {object} payload - object that contains the payload
 	 * @param {string} payload.userId - the id of the user who's activity is to be added
-	 * @param {string} payload.activityType - the type of activity (enum specified in schema)
-	 * @param {Array} payload.payload - the details of the activity e.g. an array of songs that were added
+	 * @param {string} payload.type - the type of activity (enum specified in schema)
+	 * @param {object} payload.payload - the details of the activity e.g. an array of songs that were added
+	 * @param {string} payload.payload.message - the main message describing the activity e.g. 50 songs added to playlist 'playlist name'
+	 * @param {string} payload.payload.thumbnail - url to a thumbnail e.g. song album art to be used when display an activity
+	 * @param {string} payload.payload.songId - (optional) if relevant, the id of the song related to the activity
+	 * @param {string} payload.payload.playlistId - (optional) if relevant, the id of the playlist related to the activity
+	 * @param {string} payload.payload.stationId - (optional) if relevant, the id of the station related to the activity
 	 * @returns {Promise} - returns promise (reject, resolve)
 	 */
 	ADD_ACTIVITY(payload) {
@@ -50,47 +55,41 @@ class _ActivitiesModule extends CoreClass {
 							.catch(next);
 					},
 					(ActivityModel, next) => {
+						const { userId, type } = payload;
+
 						const activity = new ActivityModel({
-							userId: payload.userId,
-							activityType: payload.activityType,
+							userId,
+							type,
 							payload: payload.payload
 						});
 
-						activity.save((err, activity) => {
-							if (err) return next(err);
-							return next(null, activity);
-						});
+						activity.save(next);
 					},
 
 					(activity, next) => {
-						IOModule.runJob(
-							"SOCKETS_FROM_USER",
-							{
-								userId: activity.userId
-							},
-							this
-						)
-							.then(response => {
-								response.sockets.forEach(socket => {
-									socket.emit("event:activity.create", activity);
-								});
-								next();
+						IOModule.runJob("SOCKETS_FROM_USER", { userId: activity.userId }, this)
+							.then(res => {
+								res.sockets.forEach(socket => socket.emit("event:activity.create", activity));
+								next(null, activity);
 							})
 							.catch(next);
+					},
+
+					(activity, next) => {
+						IOModule.runJob("EMIT_TO_ROOM", {
+							room: `profile-${activity.userId}-activities`,
+							args: ["event:activity.create", activity]
+						});
+
+						return next(null, activity);
 					}
 				],
 				async (err, activity) => {
 					if (err) {
-						err = await UtilsModule.runJob(
-							"GET_ERROR",
-							{
-								error: err
-							},
-							this
-						);
+						err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
 						reject(new Error(err));
 					} else {
-						resolve({ activity });
+						resolve(activity);
 					}
 				}
 			);

+ 3 - 2
backend/logic/app.js

@@ -36,7 +36,7 @@ class _AppModule extends CoreClass {
 			MailModule = this.moduleManager.modules.mail;
 			CacheModule = this.moduleManager.modules.cache;
 			DBModule = this.moduleManager.modules.db;
-			ActivitiesModule = this.moduleManager.modules.activities;
+			// ActivitiesModule = this.moduleManager.modules.activities;
 			PlaylistsModule = this.moduleManager.modules.playlists;
 			UtilsModule = this.moduleManager.modules.utils;
 
@@ -363,7 +363,8 @@ class _AppModule extends CoreClass {
 						(userId, next) => {
 							ActivitiesModule.runJob("ADD_ACTIVITY", {
 								userId,
-								activityType: "created_account"
+								type: "user__joined",
+								payload: { message: "Welcome to Musare!" }
 							});
 
 							next(null, userId);

+ 42 - 10
backend/logic/db/schemas/activity.js

@@ -2,20 +2,52 @@ export default {
 	createdAt: { type: Date, default: Date.now, required: true },
 	hidden: { type: Boolean, default: false, required: true },
 	userId: { type: String, required: true },
-	activityType: {
+	type: {
 		type: String,
 		enum: [
-			"created_account",
-			"created_station",
-			"deleted_station",
-			"created_playlist",
-			"deleted_playlist",
-			"liked_song",
-			"added_song_to_playlist",
-			"added_songs_to_playlist"
+			/** User */
+			"user__joined",
+			"user__edit_bio",
+			"user__edit_avatar",
+			"user__edit_name",
+			"user__edit_location",
+			"user__toggle_nightmode",
+			"user__toggle_autoskip_disliked_songs",
+			/** Songs */
+			"song__report",
+			"song__like",
+			"song__dislike",
+			"song__unlike",
+			"song__undislike",
+			/** Stations */
+			"station__favorite",
+			"station__unfavorite",
+			"station__create",
+			"station__remove",
+			"station__edit_name",
+			"station__edit_display_name",
+			"station__edit_description",
+			"station__edit_theme",
+			"station__edit_privacy",
+			"station__edit_genres",
+			"station__edit_blacklisted_genres",
+			/** Playlists */
+			"playlist__create",
+			"playlist__remove",
+			"playlist__remove_song",
+			"playlist__add_song",
+			"playlist__edit_privacy",
+			"playlist__edit_display_name",
+			"playlist__import_playlist"
 		],
 		required: true
 	},
-	payload: { type: Array, required: true },
+	payload: {
+		message: { type: String, default: "", required: true },
+		thumbnail: { type: String, required: false },
+		songId: { type: String, required: false },
+		stationId: { type: String, required: false },
+		playlistId: { type: String, required: false }
+	},
 	documentVersion: { type: Number, default: 1, required: true }
 };

+ 101 - 16
frontend/src/components/ui/ActivityItem.vue

@@ -1,14 +1,19 @@
 <template>
 	<div class="item activity-item universal-item">
 		<div class="thumbnail">
-			<img :src="activity.thumbnail" :alt="activity.message" />
-			<i class="material-icons activity-type-icon">{{ activity.icon }}</i>
+			<img
+				v-if="activity.payload.thumbnail"
+				:src="activity.payload.thumbnail"
+				:alt="formattedMessage()"
+			/>
+			<i class="material-icons activity-type-icon">{{ getIcon() }}</i>
 		</div>
 		<div class="left-part">
-			<p class="item-title">
-				{{ activity.activityType }}
-				<!-- v-html="activity.message" -->
-			</p>
+			<p
+				class="item-title"
+				v-html="formattedMessage()"
+				:title="formattedMessage()"
+			></p>
 			<p class="item-description">
 				{{
 					formatDistance(parseISO(activity.createdAt), new Date(), {
@@ -33,7 +38,78 @@ export default {
 			default: () => {}
 		}
 	},
-	methods: { formatDistance, parseISO }
+	methods: {
+		formattedMessage() {
+			// const { songId, playlistId, stationId } = this.activity.payload;
+
+			// console.log(stationId);
+
+			// if (songId) {
+			// 	return this.activity.payload.message.replace(
+			// 		/<songId>(.*)<\/songId>/g,
+			// 		"$1"
+			// 	);
+			// }
+
+			// if (playlistId) {
+			// 	return this.activity.payload.message.replace(
+			// 		/<playlistId>(.*)<\/playlistId>/g,
+			// 		"$1"
+			// 	);
+			// }
+
+			// if (stationId) {
+			// 	return this.activity.payload.message.replace(
+			// 		/<stationId>(.*)<\/stationId>/g,
+			// 		"$1"
+			// 	);
+			// }
+
+			return this.activity.payload.message;
+		},
+		getIcon() {
+			const icons = {
+				/** User */
+				user__joined: "account_circle",
+				user__edit_bio: "create",
+				user__edit_avatar: "insert_photo",
+				user__edit_name: "create",
+				user__edit_location: "place",
+				user__toggle_nightmode: "nightlight_round",
+				user__toggle_autoskip_disliked_songs: "thumb_down_alt",
+				/** Songs */
+				song__report: "flag",
+				song__like: "thumb_up_alt",
+				song__dislike: "thumb_down_alt",
+				song__unlike: "not_interested",
+				song__undislike: "not_interested",
+				/** Stations */
+				station__favorite: "star",
+				station__unfavorite: "star_border",
+				station__create: "create",
+				station__remove: "delete",
+				station__edit_theme: "color_lens",
+				station__edit_name: "create",
+				station__edit_display_name: "create",
+				station__edit_description: "create",
+				station__edit_privacy: "security",
+				station__edit_genres: "create",
+				station__edit_blacklisted_genres: "create",
+				/** Playlists */
+				playlist__create: "create",
+				playlist__remove: "delete",
+				playlist__remove_song: "not_interested",
+				playlist__add_song: "library_add",
+				playlist__edit_privacy: "security",
+				playlist__edit_display_name: "create",
+				playlist__import_playlist: "publish"
+			};
+
+			return icons[this.activity.type];
+		},
+		formatDistance,
+		parseISO
+	}
 };
 </script>
 
@@ -42,37 +118,46 @@ export default {
 	height: 72px;
 	border: 0.5px var(--light-grey-3) solid;
 	border-radius: 3px;
+	padding: 0;
 
 	.thumbnail {
 		position: relative;
 		display: flex;
 		align-items: center;
 		justify-content: center;
-		width: 70.5px;
+		min-width: 70.5px;
+		max-width: 70.5px;
 		height: 70.5px;
-
-		img {
-			opacity: 0.4;
-		}
+		background-color: var(--primary-color);
 
 		.activity-type-icon {
 			position: absolute;
-			color: var(--dark-grey);
-			font-size: 30px;
+			color: var(--light-grey);
+			font-size: 25px;
+			background-color: rgba(0, 0, 0, 0.8);
+			padding: 5px;
+			border-radius: 100%;
 		}
 	}
 
 	.left-part {
 		flex: 1;
 		padding: 12px;
+		width: calc(100% - 150px);
 
 		.item-title {
 			margin: 0;
+			font-size: 16px;
 		}
 	}
 
-	.universal-item-actions a {
-		border-bottom: 0;
+	.universal-item-actions {
+		right: 10px;
+		position: sticky;
+
+		a {
+			border-bottom: 0;
+		}
 	}
 }
 </style>

+ 8 - 3
frontend/src/pages/Profile/index.vue

@@ -153,7 +153,13 @@ export default {
 </script>
 
 <style lang="scss" scoped>
-@media only screen and (max-width: 750px) {
+@media only screen and (max-width: 1250px) {
+	.bottom-section .content {
+		width: 650px !important;
+	}
+}
+
+@media only screen and (max-width: 900px) {
 	.info-section {
 		margin-top: 0 !important;
 
@@ -310,7 +316,6 @@ export default {
 }
 
 .bottom-section {
-	width: 962px;
 	max-width: 100%;
 	margin-left: auto;
 	margin-right: auto;
@@ -344,7 +349,7 @@ export default {
 	}
 
 	.content /deep/ {
-		width: 600px;
+		width: 800px;
 		max-width: 100%;
 		background-color: var(--white);
 		padding: 30px 50px;

+ 10 - 139
frontend/src/pages/Profile/tabs/RecentActivity.vue

@@ -79,29 +79,20 @@ export default {
 			}
 
 			this.socket.emit("activities.getSet", this.userId, 1, res => {
-				if (res.status === "success") {
-					// for (let a = 0; a < res.data.length; a += 1) {
-					// 	this.formatActivity(res.data[a], activity => {
-					// 		this.activities.unshift(activity);
-					// 	});
-					// }
-					this.getSetOfActivities({
+				if (res.status === "success")
+					this.addSetOfActivities({
 						activities: res.data,
 						set: 1
 					});
-				}
 			});
 
-			this.socket.on("event:activity.create", activity => {
-				console.log("activity created (socket event): ", activity);
-				this.formatActivity(activity, activity => {
-					this.activities.unshift(activity);
-				});
-			});
+			this.socket.on("event:activity.create", activity =>
+				this.addActivity(activity)
+			);
 
-			this.socket.on("event:activity.hide", activityId => {
-				this.removeActivity(activityId);
-			});
+			this.socket.on("event:activity.hide", activityId =>
+				this.removeActivity(activityId)
+			);
 		});
 	},
 	methods: {
@@ -111,129 +102,9 @@ export default {
 					new Toast({ content: res.message, timeout: 3000 });
 			});
 		},
-		formatActivity(res, cb) {
-			console.log("activity", res);
-
-			const icons = {
-				created_account: "account_circle",
-				created_station: "radio",
-				deleted_station: "delete",
-				created_playlist: "playlist_add_check",
-				deleted_playlist: "delete_sweep",
-				liked_song: "favorite",
-				added_song_to_playlist: "playlist_add",
-				added_songs_to_playlist: "playlist_add"
-			};
-
-			const activity = {
-				...res,
-				thumbnail: "",
-				message: "",
-				icon: ""
-			};
-
-			const plural = activity.payload.length > 1;
-
-			activity.icon = icons[activity.activityType];
-
-			if (activity.activityType === "created_account") {
-				activity.message = "Welcome to Musare!";
-				return cb(activity);
-			}
-			if (activity.activityType === "created_station") {
-				this.socket.emit(
-					"stations.getStationForActivity",
-					activity.payload[0],
-					res => {
-						if (res.status === "success") {
-							activity.message = `Created the station <strong>${res.data.title}</strong>`;
-							activity.thumbnail = res.data.thumbnail;
-							return cb(activity);
-						}
-						activity.message = "Created a station";
-						return cb(activity);
-					}
-				);
-			}
-			if (activity.activityType === "deleted_station") {
-				activity.message = `Deleted a station`;
-				return cb(activity);
-			}
-			if (activity.activityType === "created_playlist") {
-				this.socket.emit(
-					"playlists.getPlaylistForActivity",
-					activity.payload[0],
-					res => {
-						if (res.status === "success") {
-							activity.message = `Created the playlist <strong>${res.data.title}</strong>`;
-							// activity.thumbnail = res.data.thumbnail;
-							return cb(activity);
-						}
-						activity.message = "Created a playlist";
-						return cb(activity);
-					}
-				);
-			}
-			if (activity.activityType === "deleted_playlist") {
-				activity.message = `Deleted a playlist`;
-				return cb(activity);
-			}
-			if (activity.activityType === "liked_song") {
-				if (plural) {
-					activity.message = `Liked ${activity.payload.length} songs.`;
-					return cb(activity);
-				}
-				this.socket.emit(
-					"songs.getSongForActivity",
-					activity.payload[0],
-					res => {
-						if (res.status === "success") {
-							activity.message = `Liked the song <strong>${res.data.title}</strong>`;
-							activity.thumbnail = res.data.thumbnail;
-							return cb(activity);
-						}
-						activity.message = "Liked a song";
-						return cb(activity);
-					}
-				);
-			}
-			if (activity.activityType === "added_song_to_playlist") {
-				this.socket.emit(
-					"songs.getSongForActivity",
-					activity.payload[0].songId,
-					song => {
-						console.log(song);
-						this.socket.emit(
-							"playlists.getPlaylistForActivity",
-							activity.payload[0].playlistId,
-							playlist => {
-								if (song.status === "success") {
-									if (playlist.status === "success")
-										activity.message = `Added the song <strong>${song.data.title}</strong> to the playlist <strong>${playlist.data.title}</strong>`;
-									else
-										activity.message = `Added the song <strong>${song.data.title}</strong> to a playlist`;
-									activity.thumbnail = song.data.thumbnail;
-									return cb(activity);
-								}
-								if (playlist.status === "success") {
-									activity.message = `Added a song to the playlist <strong>${playlist.data.title}</strong>`;
-									return cb(activity);
-								}
-								activity.message = "Added a song to a playlist";
-								return cb(activity);
-							}
-						);
-					}
-				);
-			}
-			if (activity.activityType === "added_songs_to_playlist") {
-				activity.message = `Added ${activity.payload.length} songs to a playlist`;
-				return cb(activity);
-			}
-			return false;
-		},
 		...mapActions("user/activities", [
-			"getSetOfActivities",
+			"addSetOfActivities",
+			"addActivity",
 			"removeActivity"
 		])
 	}

+ 14 - 6
frontend/src/store/modules/user.js

@@ -203,21 +203,29 @@ const modules = {
 		namespaced: true,
 		state: {
 			activities: [],
-			sets: []
+			position: 0,
+			maxPosition: 1,
+			offsettedFromNextSet: 0
 		},
 		actions: {
-			getSetOfActivities: ({ commit }, data) =>
-				commit("getSetOfActivities", data),
+			addSetOfActivities: ({ commit }, data) =>
+				commit("addSetOfActivities", data),
+			addActivity: ({ commit }, activity) =>
+				commit("addActivity", activity),
 			removeActivity: ({ commit }, activityId) =>
 				commit("removeActivity", activityId)
 		},
 		mutations: {
-			getSetOfActivities(state, data) {
+			addActivity(state, activity) {
+				state.activities.unshift(activity);
+				state.offsettedFromNextSet += 1;
+			},
+			addSetOfActivities(state, data) {
 				const { activities, set } = data;
 
-				if (!state.sets.includes(set)) {
+				if (set > state.position && set <= state.maxPosition) {
 					state.activities.push(...activities);
-					state.sets.push(set);
+					state.position = set;
 				}
 			},
 			removeActivity(state, activityId) {