media.js 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915
  1. import async from "async";
  2. import { isAdminRequired, isLoginRequired } from "./hooks";
  3. // eslint-disable-next-line
  4. import moduleManager from "../../index";
  5. const DBModule = moduleManager.modules.db;
  6. const UtilsModule = moduleManager.modules.utils;
  7. const WSModule = moduleManager.modules.ws;
  8. const CacheModule = moduleManager.modules.cache;
  9. const SongsModule = moduleManager.modules.songs;
  10. const ActivitiesModule = moduleManager.modules.activities;
  11. const MediaModule = moduleManager.modules.media;
  12. CacheModule.runJob("SUB", {
  13. channel: "ratings.like",
  14. cb: data => {
  15. WSModule.runJob("EMIT_TO_ROOM", {
  16. room: `song.${data.youtubeId}`,
  17. args: [
  18. "event:ratings.liked",
  19. {
  20. data: { youtubeId: data.youtubeId, likes: data.likes, dislikes: data.dislikes }
  21. }
  22. ]
  23. });
  24. WSModule.runJob("SOCKETS_FROM_USER", { userId: data.userId }).then(sockets => {
  25. sockets.forEach(socket => {
  26. socket.dispatch("event:ratings.updated", {
  27. data: {
  28. youtubeId: data.youtubeId,
  29. liked: true,
  30. disliked: false
  31. }
  32. });
  33. });
  34. });
  35. }
  36. });
  37. CacheModule.runJob("SUB", {
  38. channel: "ratings.dislike",
  39. cb: data => {
  40. WSModule.runJob("EMIT_TO_ROOM", {
  41. room: `song.${data.youtubeId}`,
  42. args: [
  43. "event:ratings.disliked",
  44. {
  45. data: { youtubeId: data.youtubeId, likes: data.likes, dislikes: data.dislikes }
  46. }
  47. ]
  48. });
  49. WSModule.runJob("SOCKETS_FROM_USER", { userId: data.userId }).then(sockets => {
  50. sockets.forEach(socket => {
  51. socket.dispatch("event:ratings.updated", {
  52. data: {
  53. youtubeId: data.youtubeId,
  54. liked: false,
  55. disliked: true
  56. }
  57. });
  58. });
  59. });
  60. }
  61. });
  62. CacheModule.runJob("SUB", {
  63. channel: "ratings.unlike",
  64. cb: data => {
  65. WSModule.runJob("EMIT_TO_ROOM", {
  66. room: `song.${data.youtubeId}`,
  67. args: [
  68. "event:ratings.unliked",
  69. {
  70. data: { youtubeId: data.youtubeId, likes: data.likes, dislikes: data.dislikes }
  71. }
  72. ]
  73. });
  74. WSModule.runJob("SOCKETS_FROM_USER", { userId: data.userId }).then(sockets => {
  75. sockets.forEach(socket => {
  76. socket.dispatch("event:ratings.updated", {
  77. data: {
  78. youtubeId: data.youtubeId,
  79. liked: false,
  80. disliked: false
  81. }
  82. });
  83. });
  84. });
  85. }
  86. });
  87. CacheModule.runJob("SUB", {
  88. channel: "ratings.undislike",
  89. cb: data => {
  90. WSModule.runJob("EMIT_TO_ROOM", {
  91. room: `song.${data.youtubeId}`,
  92. args: [
  93. "event:ratings.undisliked",
  94. {
  95. data: { youtubeId: data.youtubeId, likes: data.likes, dislikes: data.dislikes }
  96. }
  97. ]
  98. });
  99. WSModule.runJob("SOCKETS_FROM_USER", { userId: data.userId }).then(sockets => {
  100. sockets.forEach(socket => {
  101. socket.dispatch("event:ratings.updated", {
  102. data: {
  103. youtubeId: data.youtubeId,
  104. liked: false,
  105. disliked: false
  106. }
  107. });
  108. });
  109. });
  110. }
  111. });
  112. export default {
  113. /**
  114. * Recalculates all ratings
  115. *
  116. * @param {object} session - the session object automatically added by the websocket
  117. * @param cb
  118. */
  119. recalculateAllRatings: isAdminRequired(async function recalculateAllRatings(session, cb) {
  120. this.keepLongJob();
  121. this.publishProgress({
  122. status: "started",
  123. title: "Recalculate all ratings",
  124. message: "Recalculating all ratings.",
  125. id: this.toString()
  126. });
  127. await CacheModule.runJob("RPUSH", { key: `longJobs.${session.userId}`, value: this.toString() }, this);
  128. await CacheModule.runJob(
  129. "PUB",
  130. {
  131. channel: "longJob.added",
  132. value: { jobId: this.toString(), userId: session.userId }
  133. },
  134. this
  135. );
  136. async.waterfall(
  137. [
  138. next => {
  139. MediaModule.runJob("RECALCULATE_ALL_RATINGS", {}, this)
  140. .then(() => {
  141. next();
  142. })
  143. .catch(err => {
  144. next(err);
  145. });
  146. }
  147. ],
  148. async err => {
  149. if (err) {
  150. err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
  151. this.log("ERROR", "MEDIA_RECALCULATE_ALL_RATINGS", `Failed to recalculate all ratings. "${err}"`);
  152. this.publishProgress({
  153. status: "error",
  154. message: err
  155. });
  156. return cb({ status: "error", message: err });
  157. }
  158. this.log("SUCCESS", "MEDIA_RECALCULATE_ALL_RATINGS", `Recalculated all ratings successfully.`);
  159. this.publishProgress({
  160. status: "success",
  161. message: "Successfully recalculated all ratings."
  162. });
  163. return cb({ status: "success", message: "Successfully recalculated all ratings." });
  164. }
  165. );
  166. }),
  167. /**
  168. * Like
  169. *
  170. * @param session
  171. * @param youtubeId - the youtube id
  172. * @param cb
  173. */
  174. like: isLoginRequired(async function like(session, youtubeId, cb) {
  175. const userModel = await DBModule.runJob("GET_MODEL", { modelName: "user" }, this);
  176. async.waterfall(
  177. [
  178. next => {
  179. MediaModule.runJob(
  180. "GET_MEDIA",
  181. {
  182. youtubeId
  183. },
  184. this
  185. )
  186. .then(response => {
  187. const { song } = response;
  188. const { _id, title, artists, thumbnail, duration, verified } = song;
  189. next(null, {
  190. _id,
  191. youtubeId,
  192. title,
  193. artists,
  194. thumbnail,
  195. duration,
  196. verified
  197. });
  198. })
  199. .catch(next);
  200. },
  201. (song, next) => userModel.findOne({ _id: session.userId }, (err, user) => next(err, song, user)),
  202. (song, user, next) => {
  203. if (!user) return next("User does not exist.");
  204. return this.module
  205. .runJob(
  206. "RUN_ACTION2",
  207. {
  208. session,
  209. namespace: "playlists",
  210. action: "removeSongFromPlaylist",
  211. args: [youtubeId, user.dislikedSongsPlaylist]
  212. },
  213. this
  214. )
  215. .then(() => next(null, song, user.likedSongsPlaylist))
  216. .catch(res => {
  217. if (!(res.message && res.message === "That song is not currently in the playlist."))
  218. return next("Unable to remove song from the 'Disliked Songs' playlist.");
  219. return next(null, song, user.likedSongsPlaylist);
  220. });
  221. },
  222. (song, likedSongsPlaylist, next) =>
  223. this.module
  224. .runJob(
  225. "RUN_ACTION2",
  226. {
  227. session,
  228. namespace: "playlists",
  229. action: "addSongToPlaylist",
  230. args: [false, youtubeId, likedSongsPlaylist]
  231. },
  232. this
  233. )
  234. .then(() => next(null, song))
  235. .catch(res => {
  236. if (res.message && res.message === "That song is already in the playlist")
  237. return next("You have already liked this song.");
  238. return next("Unable to add song to the 'Liked Songs' playlist.");
  239. }),
  240. (song, next) => {
  241. MediaModule.runJob("RECALCULATE_RATINGS", { youtubeId })
  242. .then(ratings => next(null, song, ratings))
  243. .catch(err => next(err));
  244. }
  245. ],
  246. async (err, song, ratings) => {
  247. if (err) {
  248. err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
  249. this.log(
  250. "ERROR",
  251. "MEDIA_RATINGS_LIKE",
  252. `User "${session.userId}" failed to like song ${youtubeId}. "${err}"`
  253. );
  254. return cb({ status: "error", message: err });
  255. }
  256. const { likes, dislikes } = ratings;
  257. if (song._id) SongsModule.runJob("UPDATE_SONG", { songId: song._id });
  258. CacheModule.runJob("PUB", {
  259. channel: "ratings.like",
  260. value: JSON.stringify({
  261. youtubeId,
  262. userId: session.userId,
  263. likes,
  264. dislikes
  265. })
  266. });
  267. ActivitiesModule.runJob("ADD_ACTIVITY", {
  268. userId: session.userId,
  269. type: "song__like",
  270. payload: {
  271. message: `Liked song <youtubeId>${song.title} by ${song.artists.join(", ")}</youtubeId>`,
  272. youtubeId,
  273. thumbnail: song.thumbnail
  274. }
  275. });
  276. return cb({
  277. status: "success",
  278. message: "You have successfully liked this song."
  279. });
  280. }
  281. );
  282. }),
  283. /**
  284. * Dislike
  285. *
  286. * @param session
  287. * @param youtubeId - the youtube id
  288. * @param cb
  289. */
  290. dislike: isLoginRequired(async function dislike(session, youtubeId, cb) {
  291. const userModel = await DBModule.runJob("GET_MODEL", { modelName: "user" }, this);
  292. async.waterfall(
  293. [
  294. next => {
  295. MediaModule.runJob(
  296. "GET_MEDIA",
  297. {
  298. youtubeId
  299. },
  300. this
  301. )
  302. .then(response => {
  303. const { song } = response;
  304. const { _id, title, artists, thumbnail, duration, verified } = song;
  305. next(null, {
  306. _id,
  307. youtubeId,
  308. title,
  309. artists,
  310. thumbnail,
  311. duration,
  312. verified
  313. });
  314. })
  315. .catch(next);
  316. },
  317. (song, next) => userModel.findOne({ _id: session.userId }, (err, user) => next(err, song, user)),
  318. (song, user, next) => {
  319. if (!user) return next("User does not exist.");
  320. return this.module
  321. .runJob(
  322. "RUN_ACTION2",
  323. {
  324. session,
  325. namespace: "playlists",
  326. action: "removeSongFromPlaylist",
  327. args: [youtubeId, user.likedSongsPlaylist]
  328. },
  329. this
  330. )
  331. .then(() => next(null, song, user.dislikedSongsPlaylist))
  332. .catch(res => {
  333. if (!(res.message && res.message === "That song is not currently in the playlist."))
  334. return next("Unable to remove song from the 'Liked Songs' playlist.");
  335. return next(null, song, user.dislikedSongsPlaylist);
  336. });
  337. },
  338. (song, dislikedSongsPlaylist, next) =>
  339. this.module
  340. .runJob(
  341. "RUN_ACTION2",
  342. {
  343. session,
  344. namespace: "playlists",
  345. action: "addSongToPlaylist",
  346. args: [false, youtubeId, dislikedSongsPlaylist]
  347. },
  348. this
  349. )
  350. .then(() => next(null, song))
  351. .catch(res => {
  352. if (res.message && res.message === "That song is already in the playlist")
  353. return next("You have already disliked this song.");
  354. return next("Unable to add song to the 'Disliked Songs' playlist.");
  355. }),
  356. (song, next) => {
  357. MediaModule.runJob("RECALCULATE_RATINGS", { youtubeId })
  358. .then(ratings => next(null, song, ratings))
  359. .catch(err => next(err));
  360. }
  361. ],
  362. async (err, song, ratings) => {
  363. if (err) {
  364. err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
  365. this.log(
  366. "ERROR",
  367. "MEDIA_RATINGS_DISLIKE",
  368. `User "${session.userId}" failed to dislike song ${youtubeId}. "${err}"`
  369. );
  370. return cb({ status: "error", message: err });
  371. }
  372. const { likes, dislikes } = ratings;
  373. if (song._id) SongsModule.runJob("UPDATE_SONG", { songId: song._id });
  374. CacheModule.runJob("PUB", {
  375. channel: "ratings.dislike",
  376. value: JSON.stringify({
  377. youtubeId,
  378. userId: session.userId,
  379. likes,
  380. dislikes
  381. })
  382. });
  383. ActivitiesModule.runJob("ADD_ACTIVITY", {
  384. userId: session.userId,
  385. type: "song__dislike",
  386. payload: {
  387. message: `Disliked song <youtubeId>${song.title} by ${song.artists.join(", ")}</youtubeId>`,
  388. youtubeId,
  389. thumbnail: song.thumbnail
  390. }
  391. });
  392. return cb({
  393. status: "success",
  394. message: "You have successfully disliked this song."
  395. });
  396. }
  397. );
  398. }),
  399. /**
  400. * Undislike
  401. *
  402. * @param session
  403. * @param youtubeId - the youtube id
  404. * @param cb
  405. */
  406. undislike: isLoginRequired(async function undislike(session, youtubeId, cb) {
  407. const userModel = await DBModule.runJob("GET_MODEL", { modelName: "user" }, this);
  408. async.waterfall(
  409. [
  410. next => {
  411. MediaModule.runJob(
  412. "GET_MEDIA",
  413. {
  414. youtubeId
  415. },
  416. this
  417. )
  418. .then(response => {
  419. const { song } = response;
  420. const { _id, title, artists, thumbnail, duration, verified } = song;
  421. next(null, {
  422. _id,
  423. youtubeId,
  424. title,
  425. artists,
  426. thumbnail,
  427. duration,
  428. verified
  429. });
  430. })
  431. .catch(next);
  432. },
  433. (song, next) => userModel.findOne({ _id: session.userId }, (err, user) => next(err, song, user)),
  434. (song, user, next) => {
  435. if (!user) return next("User does not exist.");
  436. return this.module
  437. .runJob(
  438. "RUN_ACTION2",
  439. {
  440. session,
  441. namespace: "playlists",
  442. action: "removeSongFromPlaylist",
  443. args: [youtubeId, user.dislikedSongsPlaylist]
  444. },
  445. this
  446. )
  447. .then(res => {
  448. if (res.status === "error")
  449. return next("Unable to remove song from the 'Disliked Songs' playlist.");
  450. return next(null, song, user.likedSongsPlaylist);
  451. })
  452. .catch(err => next(err));
  453. },
  454. (song, likedSongsPlaylist, next) => {
  455. this.module
  456. .runJob(
  457. "RUN_ACTION2",
  458. {
  459. session,
  460. namespace: "playlists",
  461. action: "removeSongFromPlaylist",
  462. args: [youtubeId, likedSongsPlaylist]
  463. },
  464. this
  465. )
  466. .then(() => next(null, song))
  467. .catch(res => {
  468. if (!(res.message && res.message === "That song is not currently in the playlist."))
  469. return next("Unable to remove song from the 'Liked Songs' playlist.");
  470. return next(null, song);
  471. });
  472. },
  473. (song, next) => {
  474. MediaModule.runJob("RECALCULATE_RATINGS", { youtubeId })
  475. .then(ratings => next(null, song, ratings))
  476. .catch(err => next(err));
  477. }
  478. ],
  479. async (err, song, ratings) => {
  480. if (err) {
  481. err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
  482. this.log(
  483. "ERROR",
  484. "MEDIA_RATINGS_UNDISLIKE",
  485. `User "${session.userId}" failed to undislike song ${youtubeId}. "${err}"`
  486. );
  487. return cb({ status: "error", message: err });
  488. }
  489. const { likes, dislikes } = ratings;
  490. if (song._id) SongsModule.runJob("UPDATE_SONG", { songId: song._id });
  491. CacheModule.runJob("PUB", {
  492. channel: "ratings.undislike",
  493. value: JSON.stringify({
  494. youtubeId,
  495. userId: session.userId,
  496. likes,
  497. dislikes
  498. })
  499. });
  500. ActivitiesModule.runJob("ADD_ACTIVITY", {
  501. userId: session.userId,
  502. type: "song__undislike",
  503. payload: {
  504. message: `Removed <youtubeId>${song.title} by ${song.artists.join(
  505. ", "
  506. )}</youtubeId> from your Disliked Songs`,
  507. youtubeId,
  508. thumbnail: song.thumbnail
  509. }
  510. });
  511. return cb({
  512. status: "success",
  513. message: "You have successfully undisliked this song."
  514. });
  515. }
  516. );
  517. }),
  518. /**
  519. * Unlike
  520. *
  521. * @param session
  522. * @param youtubeId - the youtube id
  523. * @param cb
  524. */
  525. unlike: isLoginRequired(async function unlike(session, youtubeId, cb) {
  526. const userModel = await DBModule.runJob("GET_MODEL", { modelName: "user" }, this);
  527. async.waterfall(
  528. [
  529. next => {
  530. MediaModule.runJob(
  531. "GET_MEDIA",
  532. {
  533. youtubeId
  534. },
  535. this
  536. )
  537. .then(response => {
  538. const { song } = response;
  539. const { _id, title, artists, thumbnail, duration, verified } = song;
  540. next(null, {
  541. _id,
  542. youtubeId,
  543. title,
  544. artists,
  545. thumbnail,
  546. duration,
  547. verified
  548. });
  549. })
  550. .catch(next);
  551. },
  552. (song, next) => userModel.findOne({ _id: session.userId }, (err, user) => next(err, song, user)),
  553. (song, user, next) => {
  554. if (!user) return next("User does not exist.");
  555. return this.module
  556. .runJob(
  557. "RUN_ACTION2",
  558. {
  559. session,
  560. namespace: "playlists",
  561. action: "removeSongFromPlaylist",
  562. args: [youtubeId, user.dislikedSongsPlaylist]
  563. },
  564. this
  565. )
  566. .then(() => next(null, song, user.likedSongsPlaylist))
  567. .catch(res => {
  568. if (!(res.message && res.message === "That song is not currently in the playlist."))
  569. return next("Unable to remove song from the 'Disliked Songs' playlist.");
  570. return next(null, song, user.likedSongsPlaylist);
  571. });
  572. },
  573. (song, likedSongsPlaylist, next) => {
  574. this.module
  575. .runJob(
  576. "RUN_ACTION2",
  577. {
  578. session,
  579. namespace: "playlists",
  580. action: "removeSongFromPlaylist",
  581. args: [youtubeId, likedSongsPlaylist]
  582. },
  583. this
  584. )
  585. .then(res => {
  586. if (res.status === "error")
  587. return next("Unable to remove song from the 'Liked Songs' playlist.");
  588. return next(null, song);
  589. })
  590. .catch(err => next(err));
  591. },
  592. (song, next) => {
  593. MediaModule.runJob("RECALCULATE_RATINGS", { youtubeId })
  594. .then(ratings => next(null, song, ratings))
  595. .catch(err => next(err));
  596. }
  597. ],
  598. async (err, song, ratings) => {
  599. if (err) {
  600. err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
  601. this.log(
  602. "ERROR",
  603. "MEDIA_RATINGS_UNLIKE",
  604. `User "${session.userId}" failed to unlike song ${youtubeId}. "${err}"`
  605. );
  606. return cb({ status: "error", message: err });
  607. }
  608. const { likes, dislikes } = ratings;
  609. if (song._id) SongsModule.runJob("UPDATE_SONG", { songId: song._id });
  610. CacheModule.runJob("PUB", {
  611. channel: "ratings.unlike",
  612. value: JSON.stringify({
  613. youtubeId,
  614. userId: session.userId,
  615. likes,
  616. dislikes
  617. })
  618. });
  619. ActivitiesModule.runJob("ADD_ACTIVITY", {
  620. userId: session.userId,
  621. type: "song__unlike",
  622. payload: {
  623. message: `Removed <youtubeId>${song.title} by ${song.artists.join(
  624. ", "
  625. )}</youtubeId> from your Liked Songs`,
  626. youtubeId,
  627. thumbnail: song.thumbnail
  628. }
  629. });
  630. return cb({
  631. status: "success",
  632. message: "You have successfully unliked this song."
  633. });
  634. }
  635. );
  636. }),
  637. /**
  638. * Get ratings
  639. *
  640. * @param session
  641. * @param youtubeId - the youtube id
  642. * @param cb
  643. */
  644. getRatings: isLoginRequired(async function getRatings(session, youtubeId, cb) {
  645. async.waterfall(
  646. [
  647. next => {
  648. MediaModule.runJob("GET_RATINGS", { youtubeId, createMissing: true }, this)
  649. .then(res => next(null, res.ratings))
  650. .catch(next);
  651. },
  652. (ratings, next) => {
  653. next(null, {
  654. likes: ratings.likes,
  655. dislikes: ratings.dislikes
  656. });
  657. }
  658. ],
  659. async (err, ratings) => {
  660. if (err) {
  661. err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
  662. this.log(
  663. "ERROR",
  664. "MEDIA_GET_RATINGS",
  665. `User "${session.userId}" failed to get ratings for ${youtubeId}. "${err}"`
  666. );
  667. return cb({ status: "error", message: err });
  668. }
  669. const { likes, dislikes } = ratings;
  670. return cb({
  671. status: "success",
  672. data: {
  673. likes,
  674. dislikes
  675. }
  676. });
  677. }
  678. );
  679. }),
  680. /**
  681. * Gets user's own ratings
  682. *
  683. * @param session
  684. * @param youtubeId - the youtube id
  685. * @param cb
  686. */
  687. getOwnRatings: isLoginRequired(async function getOwnRatings(session, youtubeId, cb) {
  688. const playlistModel = await DBModule.runJob("GET_MODEL", { modelName: "playlist" }, this);
  689. async.waterfall(
  690. [
  691. next => {
  692. MediaModule.runJob(
  693. "GET_MEDIA",
  694. {
  695. youtubeId
  696. },
  697. this
  698. )
  699. .then(() => next())
  700. .catch(next);
  701. },
  702. next =>
  703. playlistModel.findOne(
  704. { createdBy: session.userId, displayName: "Liked Songs" },
  705. (err, playlist) => {
  706. if (err) return next(err);
  707. if (!playlist) return next("'Liked Songs' playlist does not exist.");
  708. let isLiked = false;
  709. Object.values(playlist.songs).forEach(song => {
  710. // song is found in 'liked songs' playlist
  711. if (song.youtubeId === youtubeId) isLiked = true;
  712. });
  713. return next(null, isLiked);
  714. }
  715. ),
  716. (isLiked, next) =>
  717. playlistModel.findOne(
  718. { createdBy: session.userId, displayName: "Disliked Songs" },
  719. (err, playlist) => {
  720. if (err) return next(err);
  721. if (!playlist) return next("'Disliked Songs' playlist does not exist.");
  722. const ratings = { isLiked, isDisliked: false };
  723. Object.values(playlist.songs).forEach(song => {
  724. // song is found in 'disliked songs' playlist
  725. if (song.youtubeId === youtubeId) ratings.isDisliked = true;
  726. });
  727. return next(null, ratings);
  728. }
  729. )
  730. ],
  731. async (err, ratings) => {
  732. if (err) {
  733. err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
  734. this.log(
  735. "ERROR",
  736. "MEDIA_GET_OWN_RATINGS",
  737. `User "${session.userId}" failed to get ratings for ${youtubeId}. "${err}"`
  738. );
  739. return cb({ status: "error", message: err });
  740. }
  741. const { isLiked, isDisliked } = ratings;
  742. return cb({
  743. status: "success",
  744. data: {
  745. youtubeId,
  746. liked: isLiked,
  747. disliked: isDisliked
  748. }
  749. });
  750. }
  751. );
  752. }),
  753. /**
  754. * Gets importJobs, used in the admin import page by the AdvancedTable component
  755. *
  756. * @param {object} session - the session object automatically added by the websocket
  757. * @param page - the page
  758. * @param pageSize - the size per page
  759. * @param properties - the properties to return for each news item
  760. * @param sort - the sort object
  761. * @param queries - the queries array
  762. * @param operator - the operator for queries
  763. * @param cb
  764. */
  765. getImportJobs: isAdminRequired(async function getImportJobs(
  766. session,
  767. page,
  768. pageSize,
  769. properties,
  770. sort,
  771. queries,
  772. operator,
  773. cb
  774. ) {
  775. async.waterfall(
  776. [
  777. next => {
  778. DBModule.runJob(
  779. "GET_DATA",
  780. {
  781. page,
  782. pageSize,
  783. properties,
  784. sort,
  785. queries,
  786. operator,
  787. modelName: "importJob",
  788. blacklistedProperties: [],
  789. specialProperties: {},
  790. specialQueries: {}
  791. },
  792. this
  793. )
  794. .then(response => {
  795. next(null, response);
  796. })
  797. .catch(err => {
  798. next(err);
  799. });
  800. }
  801. ],
  802. async (err, response) => {
  803. if (err && err !== true) {
  804. err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
  805. this.log("ERROR", "MEDIA_GET_IMPORT_JOBS", `Failed to get import jobs. "${err}"`);
  806. return cb({ status: "error", message: err });
  807. }
  808. this.log("SUCCESS", "MEDIA_GET_IMPORT_JOBS", `Fetched import jobs successfully.`);
  809. return cb({
  810. status: "success",
  811. message: "Successfully fetched import jobs.",
  812. data: response
  813. });
  814. }
  815. );
  816. }),
  817. /**
  818. * Remove import jobs
  819. *
  820. * @returns {{status: string, data: object}}
  821. */
  822. removeImportJobs: isAdminRequired(function removeImportJobs(session, jobIds, cb) {
  823. MediaModule.runJob("REMOVE_IMPORT_JOBS", { jobIds }, this)
  824. .then(() => {
  825. this.log("SUCCESS", "MEDIA_REMOVE_IMPORT_JOBS", `Removing import jobs was successful.`);
  826. return cb({ status: "success", message: "Successfully removed import jobs" });
  827. })
  828. .catch(async err => {
  829. err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
  830. this.log("ERROR", "MEDIA_REMOVE_IMPORT_JOBS", `Removing import jobs failed. "${err}"`);
  831. return cb({ status: "error", message: err });
  832. });
  833. })
  834. };