media.js 23 KB

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