media.js 23 KB

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