songs.js 23 KB

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