playlists.js 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860
  1. import async from "async";
  2. import CoreClass from "../core";
  3. let PlaylistsModule;
  4. let StationsModule;
  5. let SongsModule;
  6. let CacheModule;
  7. let DBModule;
  8. let UtilsModule;
  9. class _PlaylistsModule extends CoreClass {
  10. // eslint-disable-next-line require-jsdoc
  11. constructor() {
  12. super("playlists");
  13. PlaylistsModule = this;
  14. }
  15. /**
  16. * Initialises the playlists module
  17. *
  18. * @returns {Promise} - returns promise (reject, resolve)
  19. */
  20. async initialize() {
  21. this.setStage(1);
  22. StationsModule = this.moduleManager.modules.stations;
  23. CacheModule = this.moduleManager.modules.cache;
  24. DBModule = this.moduleManager.modules.db;
  25. UtilsModule = this.moduleManager.modules.utils;
  26. SongsModule = this.moduleManager.modules.songs;
  27. this.playlistModel = await DBModule.runJob("GET_MODEL", { modelName: "playlist" });
  28. this.playlistSchemaCache = await CacheModule.runJob("GET_SCHEMA", { schemaName: "playlist" });
  29. this.setStage(2);
  30. return new Promise((resolve, reject) =>
  31. async.waterfall(
  32. [
  33. next => {
  34. this.setStage(3);
  35. CacheModule.runJob("HGETALL", { table: "playlists" })
  36. .then(playlists => {
  37. next(null, playlists);
  38. })
  39. .catch(next);
  40. },
  41. (playlists, next) => {
  42. this.setStage(4);
  43. if (!playlists) return next();
  44. const playlistIds = Object.keys(playlists);
  45. return async.each(
  46. playlistIds,
  47. (playlistId, next) => {
  48. PlaylistsModule.playlistModel.findOne({ _id: playlistId }, (err, playlist) => {
  49. if (err) next(err);
  50. else if (!playlist) {
  51. CacheModule.runJob("HDEL", {
  52. table: "playlists",
  53. key: playlistId
  54. })
  55. .then(() => next())
  56. .catch(next);
  57. } else next();
  58. });
  59. },
  60. next
  61. );
  62. },
  63. next => {
  64. this.setStage(5);
  65. PlaylistsModule.playlistModel.find({}, next);
  66. },
  67. (playlists, next) => {
  68. this.setStage(6);
  69. async.each(
  70. playlists,
  71. (playlist, cb) => {
  72. CacheModule.runJob("HSET", {
  73. table: "playlists",
  74. key: playlist._id,
  75. value: PlaylistsModule.playlistSchemaCache(playlist)
  76. })
  77. .then(() => cb())
  78. .catch(next);
  79. },
  80. next
  81. );
  82. }
  83. ],
  84. async err => {
  85. if (err) {
  86. const formattedErr = await UtilsModule.runJob("GET_ERROR", {
  87. error: err
  88. });
  89. reject(new Error(formattedErr));
  90. } else {
  91. resolve();
  92. // PlaylistsModule.runJob("CREATE_MISSING_GENRE_PLAYLISTS", {})
  93. // .then()
  94. // .catch()
  95. // .finally(() => {
  96. // SongsModule.runJob("GET_ALL_GENRES", {})
  97. // .then(response => {
  98. // const { genres } = response;
  99. // genres.forEach(genre => {
  100. // PlaylistsModule.runJob("AUTOFILL_GENRE_PLAYLIST", { genre }).then().catch();
  101. // });
  102. // })
  103. // .catch();
  104. // });
  105. }
  106. }
  107. )
  108. );
  109. }
  110. /**
  111. * Creates a playlist that is not generated or editable by a user e.g. liked songs playlist
  112. *
  113. * @param {object} payload - object that contains the payload
  114. * @param {string} payload.userId - the id of the user to create the playlist for
  115. * @param {string} payload.displayName - the display name of the playlist
  116. * @returns {Promise} - returns promise (reject, resolve)
  117. */
  118. CREATE_READ_ONLY_PLAYLIST(payload) {
  119. return new Promise((resolve, reject) => {
  120. PlaylistsModule.playlistModel.create(
  121. {
  122. isUserModifiable: false,
  123. displayName: payload.displayName,
  124. songs: [],
  125. createdBy: payload.userId,
  126. createdAt: Date.now(),
  127. createdFor: null,
  128. type: payload.type
  129. },
  130. (err, playlist) => {
  131. if (err) return reject(new Error(err));
  132. return resolve(playlist._id);
  133. }
  134. );
  135. });
  136. }
  137. /**
  138. * Creates a playlist that contains all songs of a specific genre
  139. *
  140. * @param {object} payload - object that contains the payload
  141. * @param {string} payload.genre - the genre
  142. * @returns {Promise} - returns promise (reject, resolve)
  143. */
  144. CREATE_GENRE_PLAYLIST(payload) {
  145. return new Promise((resolve, reject) => {
  146. PlaylistsModule.runJob("GET_GENRE_PLAYLIST", { genre: payload.genre.toLowerCase() }, this)
  147. .then(() => {
  148. reject(new Error("Playlist already exists"));
  149. })
  150. .catch(err => {
  151. if (err.message === "Playlist not found") {
  152. PlaylistsModule.playlistModel.create(
  153. {
  154. isUserModifiable: false,
  155. displayName: `Genre - ${payload.genre}`,
  156. songs: [],
  157. createdBy: "Musare",
  158. createdFor: `${payload.genre.toLowerCase()}`,
  159. createdAt: Date.now(),
  160. type: "genre"
  161. },
  162. (err, playlist) => {
  163. if (err) return reject(new Error(err));
  164. return resolve(playlist._id);
  165. }
  166. );
  167. } else reject(new Error(err));
  168. });
  169. });
  170. }
  171. /**
  172. * Gets all genre playlists
  173. *
  174. * @param {object} payload - object that contains the payload
  175. * @param {string} payload.includeSongs - include the songs
  176. * @returns {Promise} - returns promise (reject, resolve)
  177. */
  178. GET_ALL_GENRE_PLAYLISTS(payload) {
  179. return new Promise((resolve, reject) => {
  180. const includeObject = payload.includeSongs ? null : { songs: false };
  181. PlaylistsModule.playlistModel.find({ type: "genre" }, includeObject, (err, playlists) => {
  182. if (err) reject(new Error(err));
  183. else resolve({ playlists });
  184. });
  185. });
  186. }
  187. /**
  188. * Gets a genre playlist
  189. *
  190. * @param {object} payload - object that contains the payload
  191. * @param {string} payload.genre - the genre
  192. * @param {string} payload.includeSongs - include the songs
  193. * @returns {Promise} - returns promise (reject, resolve)
  194. */
  195. GET_GENRE_PLAYLIST(payload) {
  196. return new Promise((resolve, reject) => {
  197. const includeObject = payload.includeSongs ? null : { songs: false };
  198. PlaylistsModule.playlistModel.findOne(
  199. { type: "genre", createdFor: payload.genre },
  200. includeObject,
  201. (err, playlist) => {
  202. if (err) reject(new Error(err));
  203. else if (!playlist) reject(new Error("Playlist not found"));
  204. else resolve({ playlist });
  205. }
  206. );
  207. });
  208. }
  209. /**
  210. * Gets all missing genre playlists
  211. *
  212. * @returns {Promise} - returns promise (reject, resolve)
  213. */
  214. GET_MISSING_GENRE_PLAYLISTS() {
  215. return new Promise((resolve, reject) => {
  216. SongsModule.runJob("GET_ALL_GENRES", {}, this)
  217. .then(response => {
  218. const { genres } = response;
  219. const missingGenres = [];
  220. async.eachLimit(
  221. genres,
  222. 1,
  223. (genre, next) => {
  224. PlaylistsModule.runJob(
  225. "GET_GENRE_PLAYLIST",
  226. { genre: genre.toLowerCase(), includeSongs: false },
  227. this
  228. )
  229. .then(() => {
  230. next();
  231. })
  232. .catch(err => {
  233. if (err.message === "Playlist not found") {
  234. missingGenres.push(genre);
  235. next();
  236. } else next(err);
  237. });
  238. },
  239. err => {
  240. if (err) reject(err);
  241. else resolve({ genres: missingGenres });
  242. }
  243. );
  244. })
  245. .catch(err => {
  246. reject(err);
  247. });
  248. });
  249. }
  250. /**
  251. * Creates all missing genre playlists
  252. *
  253. * @returns {Promise} - returns promise (reject, resolve)
  254. */
  255. CREATE_MISSING_GENRE_PLAYLISTS() {
  256. return new Promise((resolve, reject) => {
  257. PlaylistsModule.runJob("GET_MISSING_GENRE_PLAYLISTS", {}, this)
  258. .then(response => {
  259. const { genres } = response;
  260. async.eachLimit(
  261. genres,
  262. 1,
  263. (genre, next) => {
  264. PlaylistsModule.runJob("CREATE_GENRE_PLAYLIST", { genre }, this)
  265. .then(() => {
  266. next();
  267. })
  268. .catch(err => {
  269. next(err);
  270. });
  271. },
  272. err => {
  273. if (err) reject(err);
  274. else resolve();
  275. }
  276. );
  277. })
  278. .catch(err => {
  279. reject(err);
  280. });
  281. });
  282. }
  283. /**
  284. * Gets a station playlist
  285. *
  286. * @param {object} payload - object that contains the payload
  287. * @param {string} payload.staationId - the station id
  288. * @param {string} payload.includeSongs - include the songs
  289. * @returns {Promise} - returns promise (reject, resolve)
  290. */
  291. GET_STATION_PLAYLIST(payload) {
  292. return new Promise((resolve, reject) => {
  293. const includeObject = payload.includeSongs ? null : { songs: false };
  294. PlaylistsModule.playlistModel.findOne(
  295. { type: "station", createdFor: payload.stationId },
  296. includeObject,
  297. (err, playlist) => {
  298. if (err) reject(new Error(err));
  299. else if (!playlist) reject(new Error("Playlist not found"));
  300. else resolve({ playlist });
  301. }
  302. );
  303. });
  304. }
  305. /**
  306. * Adds a song to a playlist
  307. *
  308. * @param {object} payload - object that contains the payload
  309. * @param {string} payload.playlistId - the playlist id
  310. * @param {string} payload.song - the song
  311. * @returns {Promise} - returns promise (reject, resolve)
  312. */
  313. ADD_SONG_TO_PLAYLIST(payload) {
  314. return new Promise((resolve, reject) => {
  315. const song = {
  316. _id: payload.song._id,
  317. songId: payload.song.songId,
  318. title: payload.song.title,
  319. duration: payload.song.duration
  320. };
  321. PlaylistsModule.playlistModel.updateOne(
  322. { _id: payload.playlistId },
  323. { $push: { songs: song } },
  324. { runValidators: true },
  325. err => {
  326. if (err) reject(new Error(err));
  327. else {
  328. PlaylistsModule.runJob("UPDATE_PLAYLIST", { playlistId: payload.playlistId }, this)
  329. .then(() => resolve())
  330. .catch(err => {
  331. reject(new Error(err));
  332. });
  333. }
  334. }
  335. );
  336. });
  337. }
  338. /**
  339. * Deletes a song from a playlist based on the songId
  340. *
  341. * @param {object} payload - object that contains the payload
  342. * @param {string} payload.playlistId - the playlist id
  343. * @param {string} payload.songId - the songId
  344. * @returns {Promise} - returns promise (reject, resolve)
  345. */
  346. DELETE_SONG_FROM_PLAYLIST_BY_SONGID(payload) {
  347. return new Promise((resolve, reject) => {
  348. PlaylistsModule.playlistModel.updateOne(
  349. { _id: payload.playlistId },
  350. { $pull: { songs: { songId: payload.songId } } },
  351. err => {
  352. if (err) reject(new Error(err));
  353. else {
  354. PlaylistsModule.runJob("UPDATE_PLAYLIST", { playlistId: payload.playlistId }, this)
  355. .then(() => resolve())
  356. .catch(err => {
  357. reject(new Error(err));
  358. });
  359. }
  360. }
  361. );
  362. });
  363. }
  364. /**
  365. * Fills a genre playlist with songs
  366. *
  367. * @param {object} payload - object that contains the payload
  368. * @param {string} payload.genre - the genre
  369. * @returns {Promise} - returns promise (reject, resolve)
  370. */
  371. AUTOFILL_GENRE_PLAYLIST(payload) {
  372. return new Promise((resolve, reject) => {
  373. async.waterfall(
  374. [
  375. next => {
  376. PlaylistsModule.runJob(
  377. "GET_GENRE_PLAYLIST",
  378. { genre: payload.genre.toLowerCase(), includeSongs: true },
  379. this
  380. )
  381. .then(response => {
  382. next(null, { playlist: response.playlist });
  383. })
  384. .catch(err => {
  385. if (err.message === "Playlist not found") {
  386. PlaylistsModule.runJob("CREATE_GENRE_PLAYLIST", { genre: payload.genre }, this)
  387. .then(playlistId => {
  388. next(null, { playlist: { _id: playlistId, songs: [] } });
  389. })
  390. .catch(err => {
  391. next(err);
  392. });
  393. } else next(err);
  394. });
  395. },
  396. (data, next) => {
  397. SongsModule.runJob("GET_ALL_SONGS_WITH_GENRE", { genre: payload.genre }, this)
  398. .then(response => {
  399. data.songs = response.songs;
  400. next(null, data);
  401. })
  402. .catch(err => {
  403. console.log(err);
  404. next(err);
  405. });
  406. },
  407. (data, next) => {
  408. data.songsToDelete = [];
  409. data.songsToAdd = [];
  410. data.playlist.songs.forEach(playlistSong => {
  411. const found = data.songs.find(song => playlistSong.songId === song.songId);
  412. if (!found) data.songsToDelete.push(playlistSong);
  413. });
  414. data.songs.forEach(song => {
  415. const found = data.playlist.songs.find(playlistSong => song.songId === playlistSong.songId);
  416. if (!found) data.songsToAdd.push(song);
  417. });
  418. next(null, data);
  419. },
  420. (data, next) => {
  421. const promises = [];
  422. data.songsToAdd.forEach(song => {
  423. promises.push(
  424. PlaylistsModule.runJob(
  425. "ADD_SONG_TO_PLAYLIST",
  426. { playlistId: data.playlist._id, song },
  427. this
  428. )
  429. );
  430. });
  431. data.songsToDelete.forEach(song => {
  432. promises.push(
  433. PlaylistsModule.runJob(
  434. "DELETE_SONG_FROM_PLAYLIST_BY_SONGID",
  435. {
  436. playlistId: data.playlist._id,
  437. songId: song.songId
  438. },
  439. this
  440. )
  441. );
  442. });
  443. Promise.allSettled(promises)
  444. .then(() => {
  445. next(null, data.playlist._id);
  446. })
  447. .catch(err => {
  448. next(err);
  449. });
  450. },
  451. (playlistId, next) => {
  452. StationsModule.runJob("GET_STATIONS_THAT_INCLUDE_OR_EXCLUDE_PLAYLIST", { playlistId })
  453. .then(response => {
  454. response.stationIds.forEach(stationId => {
  455. PlaylistsModule.runJob("AUTOFILL_STATION_PLAYLIST", { stationId }).then().catch();
  456. });
  457. })
  458. .catch();
  459. next();
  460. }
  461. ],
  462. err => {
  463. if (err && err !== true) return reject(new Error(err));
  464. return resolve({});
  465. }
  466. );
  467. });
  468. }
  469. /**
  470. * Gets a orphaned station playlists
  471. *
  472. * @returns {Promise} - returns promise (reject, resolve)
  473. */
  474. GET_ORPHANED_STATION_PLAYLISTS() {
  475. return new Promise((resolve, reject) => {
  476. PlaylistsModule.playlistModel.find({ type: "station" }, { songs: false }, (err, playlists) => {
  477. if (err) reject(new Error(err));
  478. else {
  479. const orphanedPlaylists = [];
  480. async.eachLimit(
  481. playlists,
  482. 1,
  483. (playlist, next) => {
  484. StationsModule.runJob("GET_STATION", { stationId: playlist.createdFor }, this)
  485. .then(station => {
  486. if (station.playlist !== playlist._id.toString()) {
  487. orphanedPlaylists.push(playlist);
  488. }
  489. next();
  490. })
  491. .catch(err => {
  492. if (err.message === "Station not found") {
  493. orphanedPlaylists.push(playlist);
  494. next();
  495. } else next(err);
  496. });
  497. },
  498. err => {
  499. if (err) reject(new Error(err));
  500. else resolve({ playlists: orphanedPlaylists });
  501. }
  502. );
  503. }
  504. });
  505. });
  506. }
  507. /**
  508. * Deletes all orphaned station playlists
  509. *
  510. * @returns {Promise} - returns promise (reject, resolve)
  511. */
  512. DELETE_ORPHANED_STATION_PLAYLISTS() {
  513. return new Promise((resolve, reject) => {
  514. PlaylistsModule.runJob("GET_ORPHANED_STATION_PLAYLISTS", {}, this)
  515. .then(response => {
  516. async.eachLimit(
  517. response.playlists,
  518. 1,
  519. (playlist, next) => {
  520. PlaylistsModule.runJob("DELETE_PLAYLIST", { playlistId: playlist._id }, this)
  521. .then(() => {
  522. this.log("INFO", "Deleting orphaned station playlist");
  523. next();
  524. })
  525. .catch(err => {
  526. next(err);
  527. });
  528. },
  529. err => {
  530. if (err) reject(new Error(err));
  531. else resolve({});
  532. }
  533. );
  534. })
  535. .catch(err => {
  536. reject(new Error(err));
  537. });
  538. });
  539. }
  540. /**
  541. * Fills a station playlist with songs
  542. *
  543. * @param {object} payload - object that contains the payload
  544. * @param {string} payload.stationId - the station id
  545. * @returns {Promise} - returns promise (reject, resolve)
  546. */
  547. AUTOFILL_STATION_PLAYLIST(payload) {
  548. return new Promise((resolve, reject) => {
  549. let originalPlaylist = null;
  550. async.waterfall(
  551. [
  552. next => {
  553. if (!payload.stationId) next("Please specify a station id");
  554. else next();
  555. },
  556. next => {
  557. StationsModule.runJob("GET_STATION", { stationId: payload.stationId }, this)
  558. .then(station => {
  559. next(null, station);
  560. })
  561. .catch(next);
  562. },
  563. (station, next) => {
  564. PlaylistsModule.runJob("GET_PLAYLIST", { playlistId: station.playlist }, this)
  565. .then(playlist => {
  566. originalPlaylist = playlist;
  567. next(null, station);
  568. })
  569. .catch(err => {
  570. next(err);
  571. });
  572. },
  573. (station, next) => {
  574. const includedPlaylists = [];
  575. async.eachLimit(
  576. station.includedPlaylists,
  577. 1,
  578. (playlistId, next) => {
  579. PlaylistsModule.runJob("GET_PLAYLIST", { playlistId }, this)
  580. .then(playlist => {
  581. includedPlaylists.push(playlist);
  582. next();
  583. })
  584. .catch(next);
  585. },
  586. err => {
  587. next(err, station, includedPlaylists);
  588. }
  589. );
  590. },
  591. (station, includedPlaylists, next) => {
  592. const excludedPlaylists = [];
  593. async.eachLimit(
  594. station.excludedPlaylists,
  595. 1,
  596. (playlistId, next) => {
  597. PlaylistsModule.runJob("GET_PLAYLIST", { playlistId }, this)
  598. .then(playlist => {
  599. excludedPlaylists.push(playlist);
  600. next();
  601. })
  602. .catch(next);
  603. },
  604. err => {
  605. next(err, station, includedPlaylists, excludedPlaylists);
  606. }
  607. );
  608. },
  609. (station, includedPlaylists, excludedPlaylists, next) => {
  610. const excludedSongs = excludedPlaylists
  611. .flatMap(excludedPlaylist => excludedPlaylist.songs)
  612. .reduce(
  613. (items, item) =>
  614. items.find(x => x.songId === item.songId) ? [...items] : [...items, item],
  615. []
  616. );
  617. const includedSongs = includedPlaylists
  618. .flatMap(includedPlaylist => includedPlaylist.songs)
  619. .reduce(
  620. (songs, song) =>
  621. songs.find(x => x.songId === song.songId) ? [...songs] : [...songs, song],
  622. []
  623. )
  624. .filter(song => !excludedSongs.find(x => x.songId === song.songId));
  625. next(null, station, includedSongs);
  626. },
  627. (station, includedSongs, next) => {
  628. PlaylistsModule.playlistModel.updateOne(
  629. { _id: station.playlist },
  630. { $set: { songs: includedSongs } },
  631. err => {
  632. next(err, includedSongs);
  633. }
  634. );
  635. },
  636. (includedSongs, next) => {
  637. PlaylistsModule.runJob("UPDATE_PLAYLIST", { playlistId: originalPlaylist._id }, this)
  638. .then(() => {
  639. next(null, includedSongs);
  640. })
  641. .catch(next);
  642. },
  643. (includedSongs, next) => {
  644. if (originalPlaylist.songs.length === 0 && includedSongs.length > 0)
  645. StationsModule.runJob("SKIP_STATION", { stationId: payload.stationId });
  646. next();
  647. }
  648. ],
  649. err => {
  650. if (err && err !== true) return reject(new Error(err));
  651. return resolve({});
  652. }
  653. );
  654. });
  655. }
  656. /**
  657. * Gets a playlist by id from the cache or Mongo, and if it isn't in the cache yet, adds it the cache
  658. *
  659. * @param {object} payload - object that contains the payload
  660. * @param {string} payload.playlistId - the id of the playlist we are trying to get
  661. * @returns {Promise} - returns promise (reject, resolve)
  662. */
  663. GET_PLAYLIST(payload) {
  664. return new Promise((resolve, reject) =>
  665. async.waterfall(
  666. [
  667. next => {
  668. CacheModule.runJob(
  669. "HGET",
  670. {
  671. table: "playlists",
  672. key: payload.playlistId
  673. },
  674. this
  675. )
  676. .then(playlist => next(null, playlist))
  677. .catch(next);
  678. },
  679. (playlist, next) => {
  680. if (playlist)
  681. PlaylistsModule.playlistModel.exists({ _id: payload.playlistId }, (err, exists) => {
  682. if (err) next(err);
  683. else if (exists) next(null, playlist);
  684. else {
  685. CacheModule.runJob(
  686. "HDEL",
  687. {
  688. table: "playlists",
  689. key: payload.playlistId
  690. },
  691. this
  692. )
  693. .then(() => next())
  694. .catch(next);
  695. }
  696. });
  697. else PlaylistsModule.playlistModel.findOne({ _id: payload.playlistId }, next);
  698. },
  699. (playlist, next) => {
  700. if (playlist) {
  701. CacheModule.runJob(
  702. "HSET",
  703. {
  704. table: "playlists",
  705. key: payload.playlistId,
  706. value: playlist
  707. },
  708. this
  709. )
  710. .then(playlist => {
  711. next(null, playlist);
  712. })
  713. .catch(next);
  714. } else next("Playlist not found");
  715. }
  716. ],
  717. (err, playlist) => {
  718. if (err && err !== true) return reject(new Error(err));
  719. return resolve(playlist);
  720. }
  721. )
  722. );
  723. }
  724. /**
  725. * Gets a playlist from id from Mongo and updates the cache with it
  726. *
  727. * @param {object} payload - object that contains the payload
  728. * @param {string} payload.playlistId - the id of the playlist we are trying to update
  729. * @returns {Promise} - returns promise (reject, resolve)
  730. */
  731. UPDATE_PLAYLIST(payload) {
  732. return new Promise((resolve, reject) =>
  733. async.waterfall(
  734. [
  735. next => {
  736. PlaylistsModule.playlistModel.findOne({ _id: payload.playlistId }, next);
  737. },
  738. (playlist, next) => {
  739. if (!playlist) {
  740. CacheModule.runJob("HDEL", {
  741. table: "playlists",
  742. key: payload.playlistId
  743. });
  744. return next("Playlist not found");
  745. }
  746. return CacheModule.runJob(
  747. "HSET",
  748. {
  749. table: "playlists",
  750. key: payload.playlistId,
  751. value: playlist
  752. },
  753. this
  754. )
  755. .then(playlist => {
  756. next(null, playlist);
  757. })
  758. .catch(next);
  759. }
  760. ],
  761. (err, playlist) => {
  762. if (err && err !== true) return reject(new Error(err));
  763. return resolve(playlist);
  764. }
  765. )
  766. );
  767. }
  768. /**
  769. * Deletes playlist from id from Mongo and cache
  770. *
  771. * @param {object} payload - object that contains the payload
  772. * @param {string} payload.playlistId - the id of the playlist we are trying to delete
  773. * @returns {Promise} - returns promise (reject, resolve)
  774. */
  775. DELETE_PLAYLIST(payload) {
  776. return new Promise((resolve, reject) =>
  777. async.waterfall(
  778. [
  779. next => {
  780. PlaylistsModule.playlistModel.deleteOne({ _id: payload.playlistId }, next);
  781. },
  782. (res, next) => {
  783. CacheModule.runJob(
  784. "HDEL",
  785. {
  786. table: "playlists",
  787. key: payload.playlistId
  788. },
  789. this
  790. )
  791. .then(() => next())
  792. .catch(next);
  793. }
  794. ],
  795. err => {
  796. if (err && err !== true) return reject(new Error(err));
  797. return resolve();
  798. }
  799. )
  800. );
  801. }
  802. }
  803. export default new _PlaylistsModule();