songs.js 23 KB

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