songs.js 23 KB

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