playlists.js 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652
  1. 'use strict';
  2. const async = require('async');
  3. const hooks = require('./hooks');
  4. const moduleManager = require("../../index");
  5. const db = moduleManager.modules["db"];
  6. const cache = moduleManager.modules["cache"];
  7. const utils = moduleManager.modules["utils"];
  8. const logger = moduleManager.modules["logger"];
  9. const playlists = moduleManager.modules["playlists"];
  10. const songs = moduleManager.modules["songs"];
  11. const activities = moduleManager.modules["activities"];
  12. cache.sub('playlist.create', playlistId => {
  13. playlists.getPlaylist(playlistId, (err, playlist) => {
  14. if (!err) {
  15. utils.socketsFromUser(playlist.createdBy, (sockets) => {
  16. sockets.forEach(socket => {
  17. socket.emit('event:playlist.create', playlist);
  18. });
  19. });
  20. }
  21. });
  22. });
  23. cache.sub('playlist.delete', res => {
  24. utils.socketsFromUser(res.userId, sockets => {
  25. sockets.forEach(socket => {
  26. socket.emit('event:playlist.delete', res.playlistId);
  27. });
  28. });
  29. });
  30. cache.sub('playlist.moveSongToTop', res => {
  31. utils.socketsFromUser(res.userId, sockets => {
  32. sockets.forEach(socket => {
  33. socket.emit('event:playlist.moveSongToTop', {playlistId: res.playlistId, songId: res.songId});
  34. });
  35. });
  36. });
  37. cache.sub('playlist.moveSongToBottom', res => {
  38. utils.socketsFromUser(res.userId, sockets => {
  39. sockets.forEach(socket => {
  40. socket.emit('event:playlist.moveSongToBottom', {playlistId: res.playlistId, songId: res.songId});
  41. });
  42. });
  43. });
  44. cache.sub('playlist.addSong', res => {
  45. utils.socketsFromUser(res.userId, sockets => {
  46. sockets.forEach(socket => {
  47. socket.emit('event:playlist.addSong', { playlistId: res.playlistId, song: res.song });
  48. });
  49. });
  50. });
  51. cache.sub('playlist.removeSong', res => {
  52. utils.socketsFromUser(res.userId, sockets => {
  53. sockets.forEach(socket => {
  54. socket.emit('event:playlist.removeSong', { playlistId: res.playlistId, songId: res.songId });
  55. });
  56. });
  57. });
  58. cache.sub('playlist.updateDisplayName', res => {
  59. utils.socketsFromUser(res.userId, sockets => {
  60. sockets.forEach(socket => {
  61. socket.emit('event:playlist.updateDisplayName', { playlistId: res.playlistId, displayName: res.displayName });
  62. });
  63. });
  64. });
  65. let lib = {
  66. /**
  67. * Gets the first song from a private playlist
  68. *
  69. * @param {Object} session - the session object automatically added by socket.io
  70. * @param {String} playlistId - the id of the playlist we are getting the first song from
  71. * @param {Function} cb - gets called with the result
  72. */
  73. getFirstSong: hooks.loginRequired((session, playlistId, cb) => {
  74. async.waterfall([
  75. (next) => {
  76. playlists.getPlaylist(playlistId, next);
  77. },
  78. (playlist, next) => {
  79. if (!playlist || playlist.createdBy !== session.userId) return next('Playlist not found.');
  80. next(null, playlist.songs[0]);
  81. }
  82. ], async (err, song) => {
  83. if (err) {
  84. err = await utils.getError(err);
  85. logger.error("PLAYLIST_GET_FIRST_SONG", `Getting the first song of playlist "${playlistId}" failed for user "${session.userId}". "${err}"`);
  86. return cb({ status: 'failure', message: err});
  87. }
  88. logger.success("PLAYLIST_GET_FIRST_SONG", `Successfully got the first song of playlist "${playlistId}" for user "${session.userId}".`);
  89. cb({
  90. status: 'success',
  91. song: song
  92. });
  93. });
  94. }),
  95. /**
  96. * Gets all playlists for the user requesting it
  97. *
  98. * @param {Object} session - the session object automatically added by socket.io
  99. * @param {Function} cb - gets called with the result
  100. */
  101. indexForUser: hooks.loginRequired((session, cb) => {
  102. async.waterfall([
  103. (next) => {
  104. db.models.playlist.find({ createdBy: session.userId }, next);
  105. }
  106. ], async (err, playlists) => {
  107. if (err) {
  108. err = await utils.getError(err);
  109. logger.error("PLAYLIST_INDEX_FOR_USER", `Indexing playlists for user "${session.userId}" failed. "${err}"`);
  110. return cb({ status: 'failure', message: err});
  111. }
  112. logger.success("PLAYLIST_INDEX_FOR_USER", `Successfully indexed playlists for user "${session.userId}".`);
  113. cb({
  114. status: 'success',
  115. data: playlists
  116. });
  117. });
  118. }),
  119. /**
  120. * Creates a new private playlist
  121. *
  122. * @param {Object} session - the session object automatically added by socket.io
  123. * @param {Object} data - the data for the new private playlist
  124. * @param {Function} cb - gets called with the result
  125. */
  126. create: hooks.loginRequired((session, data, cb) => {
  127. async.waterfall([
  128. (next) => {
  129. return (data) ? next() : cb({ 'status': 'failure', 'message': 'Invalid data' });
  130. },
  131. (next) => {
  132. const { displayName, songs } = data;
  133. db.models.playlist.create({
  134. displayName,
  135. songs,
  136. createdBy: session.userId,
  137. createdAt: Date.now()
  138. }, next);
  139. }
  140. ], async (err, playlist) => {
  141. if (err) {
  142. err = await utils.getError(err);
  143. logger.error("PLAYLIST_CREATE", `Creating private playlist failed for user "${session.userId}". "${err}"`);
  144. return cb({ status: 'failure', message: err});
  145. }
  146. cache.pub('playlist.create', playlist._id);
  147. activities.addActivity(session.userId, "created_playlist", [ playlist._id ]);
  148. logger.success("PLAYLIST_CREATE", `Successfully created private playlist for user "${session.userId}".`);
  149. cb({ status: 'success', message: 'Successfully created playlist', data: {
  150. _id: playlist._id
  151. } });
  152. });
  153. }),
  154. /**
  155. * Gets a playlist from id
  156. *
  157. * @param {Object} session - the session object automatically added by socket.io
  158. * @param {String} playlistId - the id of the playlist we are getting
  159. * @param {Function} cb - gets called with the result
  160. */
  161. getPlaylist: hooks.loginRequired((session, playlistId, cb) => {
  162. async.waterfall([
  163. (next) => {
  164. playlists.getPlaylist(playlistId, next);
  165. },
  166. (playlist, next) => {
  167. if (!playlist || playlist.createdBy !== session.userId) return next('Playlist not found');
  168. next(null, playlist);
  169. }
  170. ], async (err, playlist) => {
  171. if (err) {
  172. err = await utils.getError(err);
  173. logger.error("PLAYLIST_GET", `Getting private playlist "${playlistId}" failed for user "${session.userId}". "${err}"`);
  174. return cb({ status: 'failure', message: err});
  175. }
  176. logger.success("PLAYLIST_GET", `Successfully got private playlist "${playlistId}" for user "${session.userId}".`);
  177. cb({
  178. status: 'success',
  179. data: playlist
  180. });
  181. });
  182. }),
  183. /**
  184. * Obtains basic metadata of a playlist in order to format an activity
  185. *
  186. * @param session
  187. * @param playlistId - the playlist id
  188. * @param cb
  189. */
  190. getPlaylistForActivity: (session, playlistId, cb) => {
  191. async.waterfall([
  192. (next) => {
  193. playlists.getPlaylist(playlistId, next);
  194. }
  195. ], async (err, playlist) => {
  196. if (err) {
  197. err = await utils.getError(err);
  198. logger.error("PLAYLISTS_GET_PLAYLIST_FOR_ACTIVITY", `Failed to obtain metadata of playlist ${playlistId} for activity formatting. "${err}"`);
  199. return cb({ status: 'failure', message: err });
  200. } else {
  201. logger.success("PLAYLISTS_GET_PLAYLIST_FOR_ACTIVITY", `Obtained metadata of playlist ${playlistId} for activity formatting successfully.`);
  202. cb({ status: "success", data: {
  203. title: playlist.displayName
  204. } });
  205. }
  206. });
  207. },
  208. //TODO Remove this
  209. /**
  210. * Updates a private playlist
  211. *
  212. * @param {Object} session - the session object automatically added by socket.io
  213. * @param {String} playlistId - the id of the playlist we are updating
  214. * @param {Object} playlist - the new private playlist object
  215. * @param {Function} cb - gets called with the result
  216. */
  217. update: hooks.loginRequired((session, playlistId, playlist, cb) => {
  218. async.waterfall([
  219. (next) => {
  220. db.models.playlist.updateOne({ _id: playlistId, createdBy: session.userId }, playlist, {runValidators: true}, next);
  221. },
  222. (res, next) => {
  223. playlists.updatePlaylist(playlistId, next)
  224. }
  225. ], async (err, playlist) => {
  226. if (err) {
  227. err = await utils.getError(err);
  228. logger.error("PLAYLIST_UPDATE", `Updating private playlist "${playlistId}" failed for user "${session.userId}". "${err}"`);
  229. return cb({ status: 'failure', message: err});
  230. }
  231. logger.success("PLAYLIST_UPDATE", `Successfully updated private playlist "${playlistId}" for user "${session.userId}".`);
  232. cb({
  233. status: 'success',
  234. data: playlist
  235. });
  236. });
  237. }),
  238. /**
  239. * Updates a private playlist
  240. *
  241. * @param {Object} session - the session object automatically added by socket.io
  242. * @param {String} playlistId - the id of the playlist we are updating
  243. * @param {Function} cb - gets called with the result
  244. */
  245. shuffle: hooks.loginRequired((session, playlistId, cb) => {
  246. async.waterfall([
  247. (next) => {
  248. if (!playlistId) return next("No playlist id.");
  249. db.models.playlist.findById(playlistId, next);
  250. },
  251. (playlist, next) => {
  252. utils.shuffle(playlist.songs).then(songs => {
  253. next(null, songs);
  254. }).catch(next);
  255. },
  256. (songs, next) => {
  257. db.models.playlist.updateOne({ _id: playlistId }, { $set: { songs } }, { runValidators: true }, next);
  258. },
  259. (res, next) => {
  260. playlists.updatePlaylist(playlistId, next)
  261. }
  262. ], async (err, playlist) => {
  263. if (err) {
  264. err = await utils.getError(err);
  265. logger.error("PLAYLIST_SHUFFLE", `Updating private playlist "${playlistId}" failed for user "${session.userId}". "${err}"`);
  266. return cb({ status: 'failure', message: err});
  267. }
  268. logger.success("PLAYLIST_SHUFFLE", `Successfully updated private playlist "${playlistId}" for user "${session.userId}".`);
  269. cb({
  270. status: 'success',
  271. message: "Successfully shuffled playlist.",
  272. data: playlist
  273. });
  274. });
  275. }),
  276. /**
  277. * Adds a song to a private playlist
  278. *
  279. * @param {Object} session - the session object automatically added by socket.io
  280. * @param {Boolean} isSet - is the song part of a set of songs to be added
  281. * @param {String} songId - the id of the song we are trying to add
  282. * @param {String} playlistId - the id of the playlist we are adding the song to
  283. * @param {Function} cb - gets called with the result
  284. */
  285. addSongToPlaylist: hooks.loginRequired((session, isSet, songId, playlistId, cb) => {
  286. async.waterfall([
  287. (next) => {
  288. playlists.getPlaylist(playlistId, (err, playlist) => {
  289. if (err || !playlist || playlist.createdBy !== session.userId) return next('Something went wrong when trying to get the playlist');
  290. async.each(playlist.songs, (song, next) => {
  291. if (song.songId === songId) return next('That song is already in the playlist');
  292. next();
  293. }, next);
  294. });
  295. },
  296. (next) => {
  297. songs.getSong(songId, (err, song) => {
  298. if (err) {
  299. utils.getSongFromYouTube(songId, (song) => {
  300. next(null, song);
  301. });
  302. } else {
  303. next(null, {
  304. _id: song._id,
  305. songId: songId,
  306. title: song.title,
  307. duration: song.duration
  308. });
  309. }
  310. });
  311. },
  312. (newSong, next) => {
  313. db.models.playlist.updateOne({_id: playlistId}, {$push: {songs: newSong}}, {runValidators: true}, (err) => {
  314. if (err) return next(err);
  315. playlists.updatePlaylist(playlistId, (err, playlist) => {
  316. next(err, playlist, newSong);
  317. });
  318. });
  319. }
  320. ],
  321. async (err, playlist, newSong) => {
  322. if (err) {
  323. err = await utils.getError(err);
  324. logger.error("PLAYLIST_ADD_SONG", `Adding song "${songId}" to private playlist "${playlistId}" failed for user "${session.userId}". "${err}"`);
  325. return cb({ status: 'failure', message: err});
  326. } else {
  327. logger.success("PLAYLIST_ADD_SONG", `Successfully added song "${songId}" to private playlist "${playlistId}" for user "${session.userId}".`);
  328. if (!isSet) activities.addActivity(session.userId, "added_song_to_playlist", [ { songId, playlistId } ]);
  329. cache.pub('playlist.addSong', { playlistId: playlist._id, song: newSong, userId: session.userId });
  330. return cb({ status: 'success', message: 'Song has been successfully added to the playlist', data: playlist.songs });
  331. }
  332. });
  333. }),
  334. /**
  335. * Adds a set of songs to a private playlist
  336. *
  337. * @param {Object} session - the session object automatically added by socket.io
  338. * @param {String} url - the url of the the YouTube playlist
  339. * @param {String} playlistId - the id of the playlist we are adding the set of songs to
  340. * @param {Boolean} musicOnly - whether to only add music to the playlist
  341. * @param {Function} cb - gets called with the result
  342. */
  343. addSetToPlaylist: hooks.loginRequired((session, url, playlistId, musicOnly, cb) => {
  344. let videosInPlaylistTotal = 0;
  345. let songsInPlaylistTotal = 0;
  346. let songsSuccess = 0;
  347. let songsFail = 0;
  348. let addedSongs = [];
  349. async.waterfall([
  350. (next) => {
  351. utils.getPlaylistFromYouTube(url, musicOnly, (songIds, otherSongIds) => {
  352. if (otherSongIds) {
  353. videosInPlaylistTotal = songIds.length;
  354. songsInPlaylistTotal = otherSongIds.length;
  355. } else {
  356. songsInPlaylistTotal = videosInPlaylistTotal = songIds.length;
  357. }
  358. next(null, songIds);
  359. });
  360. },
  361. (songIds, next) => {
  362. let processed = 0;
  363. function checkDone() {
  364. if (processed === songIds.length) next();
  365. }
  366. for (let s = 0; s < songIds.length; s++) {
  367. lib.addSongToPlaylist(session, true, songIds[s], playlistId, res => {
  368. processed++;
  369. if (res.status === "success") {
  370. addedSongs.push(songIds[s]);
  371. songsSuccess++;
  372. } else songsFail++;
  373. checkDone();
  374. });
  375. }
  376. },
  377. (next) => {
  378. playlists.getPlaylist(playlistId, next);
  379. },
  380. (playlist, next) => {
  381. if (!playlist || playlist.createdBy !== session.userId) return next('Playlist not found.');
  382. next(null, playlist);
  383. }
  384. ], async (err, playlist) => {
  385. if (err) {
  386. err = await utils.getError(err);
  387. logger.error("PLAYLIST_IMPORT", `Importing a YouTube playlist to private playlist "${playlistId}" failed for user "${session.userId}". "${err}"`);
  388. return cb({ status: 'failure', message: err});
  389. } else {
  390. activities.addActivity(session.userId, "added_songs_to_playlist", addedSongs);
  391. logger.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: ${songsSuccess}, songs failed: ${songsFail}.`);
  392. cb({
  393. status: 'success',
  394. message: 'Playlist has been successfully imported.',
  395. data: playlist.songs,
  396. stats: {
  397. videosInPlaylistTotal,
  398. songsInPlaylistTotal,
  399. songsAddedSuccessfully: songsSuccess,
  400. songsFailedToAdd: songsFail
  401. }
  402. });
  403. }
  404. });
  405. }),
  406. /**
  407. * Removes a song from a private playlist
  408. *
  409. * @param {Object} session - the session object automatically added by socket.io
  410. * @param {String} songId - the id of the song we are removing from the private playlist
  411. * @param {String} playlistId - the id of the playlist we are removing the song from
  412. * @param {Function} cb - gets called with the result
  413. */
  414. removeSongFromPlaylist: hooks.loginRequired((session, songId, playlistId, cb) => {
  415. async.waterfall([
  416. (next) => {
  417. if (!songId || typeof songId !== 'string') return next('Invalid song id.');
  418. if (!playlistId || typeof playlistId !== 'string') return next('Invalid playlist id.');
  419. next();
  420. },
  421. (next) => {
  422. playlists.getPlaylist(playlistId, next);
  423. },
  424. (playlist, next) => {
  425. if (!playlist || playlist.createdBy !== session.userId) return next('Playlist not found');
  426. db.models.playlist.updateOne({_id: playlistId}, {$pull: {songs: {songId: songId}}}, next);
  427. },
  428. (res, next) => {
  429. playlists.updatePlaylist(playlistId, next);
  430. }
  431. ], async (err, playlist) => {
  432. if (err) {
  433. err = await utils.getError(err);
  434. logger.error("PLAYLIST_REMOVE_SONG", `Removing song "${songId}" from private playlist "${playlistId}" failed for user "${session.userId}". "${err}"`);
  435. return cb({ status: 'failure', message: err});
  436. } else {
  437. logger.success("PLAYLIST_REMOVE_SONG", `Successfully removed song "${songId}" from private playlist "${playlistId}" for user "${session.userId}".`);
  438. cache.pub('playlist.removeSong', { playlistId: playlist._id, songId: songId, userId: session.userId });
  439. return cb({ status: 'success', message: 'Song has been successfully removed from playlist', data: playlist.songs });
  440. }
  441. });
  442. }),
  443. /**
  444. * Updates the displayName of a private playlist
  445. *
  446. * @param {Object} session - the session object automatically added by socket.io
  447. * @param {String} playlistId - the id of the playlist we are updating the displayName for
  448. * @param {Function} cb - gets called with the result
  449. */
  450. updateDisplayName: hooks.loginRequired((session, playlistId, displayName, cb) => {
  451. async.waterfall([
  452. (next) => {
  453. db.models.playlist.updateOne({ _id: playlistId, createdBy: session.userId }, { $set: { displayName } }, {runValidators: true}, next);
  454. },
  455. (res, next) => {
  456. playlists.updatePlaylist(playlistId, next);
  457. }
  458. ], async (err, playlist) => {
  459. if (err) {
  460. err = await utils.getError(err);
  461. logger.error("PLAYLIST_UPDATE_DISPLAY_NAME", `Updating display name to "${displayName}" for private playlist "${playlistId}" failed for user "${session.userId}". "${err}"`);
  462. return cb({ status: 'failure', message: err});
  463. }
  464. logger.success("PLAYLIST_UPDATE_DISPLAY_NAME", `Successfully updated display name to "${displayName}" for private playlist "${playlistId}" for user "${session.userId}".`);
  465. cache.pub('playlist.updateDisplayName', {playlistId: playlistId, displayName: displayName, userId: session.userId});
  466. return cb({ status: 'success', message: 'Playlist has been successfully updated' });
  467. });
  468. }),
  469. /**
  470. * Moves a song to the top of the list in a private playlist
  471. *
  472. * @param {Object} session - the session object automatically added by socket.io
  473. * @param {String} playlistId - the id of the playlist we are moving the song to the top from
  474. * @param {String} songId - the id of the song we are moving to the top of the list
  475. * @param {Function} cb - gets called with the result
  476. */
  477. moveSongToTop: hooks.loginRequired((session, playlistId, songId, cb) => {
  478. async.waterfall([
  479. (next) => {
  480. playlists.getPlaylist(playlistId, next);
  481. },
  482. (playlist, next) => {
  483. if (!playlist || playlist.createdBy !== session.userId) return next('Playlist not found');
  484. async.each(playlist.songs, (song, next) => {
  485. if (song.songId === songId) return next(song);
  486. next();
  487. }, (err) => {
  488. if (err && err.songId) return next(null, err);
  489. next('Song not found');
  490. });
  491. },
  492. (song, next) => {
  493. db.models.playlist.updateOne({_id: playlistId}, {$pull: {songs: {songId}}}, (err) => {
  494. if (err) return next(err);
  495. return next(null, song);
  496. });
  497. },
  498. (song, next) => {
  499. db.models.playlist.updateOne({_id: playlistId}, {
  500. $push: {
  501. songs: {
  502. $each: [song],
  503. $position: 0
  504. }
  505. }
  506. }, next);
  507. },
  508. (res, next) => {
  509. playlists.updatePlaylist(playlistId, next);
  510. }
  511. ], async (err, playlist) => {
  512. if (err) {
  513. err = await utils.getError(err);
  514. logger.error("PLAYLIST_MOVE_SONG_TO_TOP", `Moving song "${songId}" to the top for private playlist "${playlistId}" failed for user "${session.userId}". "${err}"`);
  515. return cb({ status: 'failure', message: err});
  516. }
  517. logger.success("PLAYLIST_MOVE_SONG_TO_TOP", `Successfully moved song "${songId}" to the top for private playlist "${playlistId}" for user "${session.userId}".`);
  518. cache.pub('playlist.moveSongToTop', {playlistId, songId, userId: session.userId});
  519. return cb({ status: 'success', message: 'Playlist has been successfully updated' });
  520. });
  521. }),
  522. /**
  523. * Moves a song to the bottom of the list in a private playlist
  524. *
  525. * @param {Object} session - the session object automatically added by socket.io
  526. * @param {String} playlistId - the id of the playlist we are moving the song to the bottom from
  527. * @param {String} songId - the id of the song we are moving to the bottom of the list
  528. * @param {Function} cb - gets called with the result
  529. */
  530. moveSongToBottom: hooks.loginRequired((session, playlistId, songId, cb) => {
  531. async.waterfall([
  532. (next) => {
  533. playlists.getPlaylist(playlistId, next);
  534. },
  535. (playlist, next) => {
  536. if (!playlist || playlist.createdBy !== session.userId) return next('Playlist not found');
  537. async.each(playlist.songs, (song, next) => {
  538. if (song.songId === songId) return next(song);
  539. next();
  540. }, (err) => {
  541. if (err && err.songId) return next(null, err);
  542. next('Song not found');
  543. });
  544. },
  545. (song, next) => {
  546. db.models.playlist.updateOne({_id: playlistId}, {$pull: {songs: {songId}}}, (err) => {
  547. if (err) return next(err);
  548. return next(null, song);
  549. });
  550. },
  551. (song, next) => {
  552. db.models.playlist.updateOne({_id: playlistId}, {
  553. $push: {
  554. songs: song
  555. }
  556. }, next);
  557. },
  558. (res, next) => {
  559. playlists.updatePlaylist(playlistId, next);
  560. }
  561. ], async (err, playlist) => {
  562. if (err) {
  563. err = await utils.getError(err);
  564. logger.error("PLAYLIST_MOVE_SONG_TO_BOTTOM", `Moving song "${songId}" to the bottom for private playlist "${playlistId}" failed for user "${session.userId}". "${err}"`);
  565. return cb({ status: 'failure', message: err});
  566. }
  567. logger.success("PLAYLIST_MOVE_SONG_TO_BOTTOM", `Successfully moved song "${songId}" to the bottom for private playlist "${playlistId}" for user "${session.userId}".`);
  568. cache.pub('playlist.moveSongToBottom', {playlistId, songId, userId: session.userId});
  569. return cb({ status: 'success', message: 'Playlist has been successfully updated' });
  570. });
  571. }),
  572. /**
  573. * Removes a private playlist
  574. *
  575. * @param {Object} session - the session object automatically added by socket.io
  576. * @param {String} playlistId - the id of the playlist we are moving the song to the top from
  577. * @param {Function} cb - gets called with the result
  578. */
  579. remove: hooks.loginRequired((session, playlistId, cb) => {
  580. async.waterfall([
  581. (next) => {
  582. playlists.deletePlaylist(playlistId, next);
  583. }
  584. ], async (err) => {
  585. if (err) {
  586. err = await utils.getError(err);
  587. logger.error("PLAYLIST_REMOVE", `Removing private playlist "${playlistId}" failed for user "${session.userId}". "${err}"`);
  588. return cb({ status: 'failure', message: err});
  589. }
  590. logger.success("PLAYLIST_REMOVE", `Successfully removed private playlist "${playlistId}" for user "${session.userId}".`);
  591. cache.pub('playlist.delete', { userId: session.userId, playlistId });
  592. activities.addActivity(session.userId, "deleted_playlist", [ playlistId ]);
  593. return cb({ status: 'success', message: 'Playlist successfully removed' });
  594. });
  595. })
  596. };
  597. module.exports = lib;