media.js 23 KB

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