songs.js 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939
  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 (session, cb) => {
  152. const songModel = await DBModule.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 UtilsModule.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 DBModule.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 UtilsModule.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. SongsModule.runJob("GET_SONG_FROM_ID", { songId })
  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 });
  223. console.log("ERROR", "SONGS_GET_SONG", `Failed to get song ${songId}. "${err}"`);
  224. return cb({ status: "failure", message: err });
  225. }
  226. console.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 })
  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 });
  250. console.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. console.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. console.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 (session, songId, song, cb) => {
  289. const songModel = await DBModule.runJob("GET_MODEL", { modelName: "song" });
  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 })
  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 });
  306. console.log("ERROR", "SONGS_UPDATE", `Failed to update song "${songId}". "${err}"`);
  307. return cb({ status: "failure", message: err });
  308. }
  309. console.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 (session, songId, cb) => {
  330. const songModel = await DBModule.runJob("GET_MODEL", { modelName: "song" });
  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 })
  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 });
  348. console.log("ERROR", "SONGS_UPDATE", `Failed to remove song "${songId}". "${err}"`);
  349. return cb({ status: "failure", message: err });
  350. }
  351. console.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 (session, song, cb) => {
  368. const SongModel = await DBModule.runJob("GET_MODEL", { modelName: "song" });
  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 });
  393. console.log("ERROR", "SONGS_ADD", `User "${session.userId}" failed to add song. "${err}"`);
  394. return cb({ status: "failure", message: err });
  395. }
  396. console.log(
  397. "SUCCESS",
  398. "SONGS_ADD",
  399. `User "${session.userId}" successfully added song "${song.songId}".`
  400. );
  401. CacheModule.runJob("PUB", {
  402. channel: "song.added",
  403. value: song.songId
  404. });
  405. return cb({
  406. status: "success",
  407. message: "Song has been moved from the queue successfully."
  408. });
  409. }
  410. );
  411. // TODO Check if video is in queue and Add the song to the appropriate stations
  412. }),
  413. /**
  414. * Likes a song
  415. *
  416. * @param session
  417. * @param songId - the song id
  418. * @param cb
  419. */
  420. like: isLoginRequired(async (session, songId, cb) => {
  421. const userModel = await DBModule.runJob("GET_MODEL", { modelName: "user" });
  422. const songModel = await DBModule.runJob("GET_MODEL", { modelName: "song" });
  423. async.waterfall(
  424. [
  425. next => {
  426. songModel.findOne({ songId }, next);
  427. },
  428. (song, next) => {
  429. if (!song) return next("No song found with that id.");
  430. return next(null, song);
  431. }
  432. ],
  433. async (err, song) => {
  434. if (err) {
  435. err = await UtilsModule.runJob("GET_ERROR", { error: err });
  436. console.log(
  437. "ERROR",
  438. "SONGS_LIKE",
  439. `User "${session.userId}" failed to like song ${songId}. "${err}"`
  440. );
  441. return cb({ status: "failure", message: err });
  442. }
  443. const oldSongId = songId;
  444. songId = song._id;
  445. return userModel.findOne({ _id: session.userId }, (err, user) => {
  446. if (user.liked.indexOf(songId) !== -1)
  447. return cb({
  448. status: "failure",
  449. message: "You have already liked this song."
  450. });
  451. return userModel.updateOne(
  452. { _id: session.userId },
  453. {
  454. $push: { liked: songId },
  455. $pull: { disliked: songId }
  456. },
  457. err => {
  458. if (!err) {
  459. return userModel.countDocuments({ liked: songId }, (err, likes) => {
  460. if (err)
  461. return cb({
  462. status: "failure",
  463. message: "Something went wrong while liking this song."
  464. });
  465. return userModel.countDocuments({ disliked: songId }, (err, dislikes) => {
  466. if (err)
  467. return cb({
  468. status: "failure",
  469. message: "Something went wrong while liking this song."
  470. });
  471. return songModel.update(
  472. { _id: songId },
  473. {
  474. $set: {
  475. likes,
  476. dislikes
  477. }
  478. },
  479. err => {
  480. if (err)
  481. return cb({
  482. status: "failure",
  483. message: "Something went wrong while liking this song."
  484. });
  485. SongsModule.runJob("UPDATE_SONG", { songId });
  486. CacheModule.runJob("PUB", {
  487. channel: "song.like",
  488. value: JSON.stringify({
  489. songId: oldSongId,
  490. userId: session.userId,
  491. likes,
  492. dislikes
  493. })
  494. });
  495. ActivitiesModule.runJob("ADD_ACTIVITY", {
  496. userId: session.userId,
  497. activityType: "liked_song",
  498. payload: [songId]
  499. });
  500. return cb({
  501. status: "success",
  502. message: "You have successfully liked this song."
  503. });
  504. }
  505. );
  506. });
  507. });
  508. }
  509. return cb({
  510. status: "failure",
  511. message: "Something went wrong while liking this song."
  512. });
  513. }
  514. );
  515. });
  516. }
  517. );
  518. }),
  519. /**
  520. * Dislikes a song
  521. *
  522. * @param session
  523. * @param songId - the song id
  524. * @param cb
  525. */
  526. dislike: isLoginRequired(async (session, songId, cb) => {
  527. const userModel = await DBModule.runJob("GET_MODEL", { modelName: "user" });
  528. const songModel = await DBModule.runJob("GET_MODEL", { modelName: "song" });
  529. async.waterfall(
  530. [
  531. next => {
  532. songModel.findOne({ songId }, next);
  533. },
  534. (song, next) => {
  535. if (!song) return next("No song found with that id.");
  536. return next(null, song);
  537. }
  538. ],
  539. async (err, song) => {
  540. if (err) {
  541. err = await UtilsModule.runJob("GET_ERROR", { error: err });
  542. console.log(
  543. "ERROR",
  544. "SONGS_DISLIKE",
  545. `User "${session.userId}" failed to like song ${songId}. "${err}"`
  546. );
  547. return cb({ status: "failure", message: err });
  548. }
  549. const oldSongId = songId;
  550. songId = song._id;
  551. return userModel.findOne({ _id: session.userId }, (err, user) => {
  552. if (user.disliked.indexOf(songId) !== -1)
  553. return cb({
  554. status: "failure",
  555. message: "You have already disliked this song."
  556. });
  557. return userModel.updateOne(
  558. { _id: session.userId },
  559. {
  560. $push: { disliked: songId },
  561. $pull: { liked: songId }
  562. },
  563. err => {
  564. if (!err) {
  565. return userModel.countDocuments({ liked: songId }, (err, likes) => {
  566. if (err)
  567. return cb({
  568. status: "failure",
  569. message: "Something went wrong while disliking this song."
  570. });
  571. return userModel.countDocuments({ disliked: songId }, (err, dislikes) => {
  572. if (err)
  573. return cb({
  574. status: "failure",
  575. message: "Something went wrong while disliking this song."
  576. });
  577. return songModel.update(
  578. { _id: songId },
  579. {
  580. $set: {
  581. likes,
  582. dislikes
  583. }
  584. },
  585. err => {
  586. if (err)
  587. return cb({
  588. status: "failure",
  589. message: "Something went wrong while disliking this song."
  590. });
  591. SongsModule.runJob("UPDATE_SONG", { songId });
  592. CacheModule.runJob("PUB", {
  593. channel: "song.dislike",
  594. value: JSON.stringify({
  595. songId: oldSongId,
  596. userId: session.userId,
  597. likes,
  598. dislikes
  599. })
  600. });
  601. return cb({
  602. status: "success",
  603. message: "You have successfully disliked this song."
  604. });
  605. }
  606. );
  607. });
  608. });
  609. }
  610. return cb({
  611. status: "failure",
  612. message: "Something went wrong while disliking this song."
  613. });
  614. }
  615. );
  616. });
  617. }
  618. );
  619. }),
  620. /**
  621. * Undislikes a song
  622. *
  623. * @param session
  624. * @param songId - the song id
  625. * @param cb
  626. */
  627. undislike: isLoginRequired(async (session, songId, cb) => {
  628. const userModel = await DBModule.runJob("GET_MODEL", { modelName: "user" });
  629. const songModel = await DBModule.runJob("GET_MODEL", { modelName: "song" });
  630. async.waterfall(
  631. [
  632. next => {
  633. songModel.findOne({ songId }, next);
  634. },
  635. (song, next) => {
  636. if (!song) return next("No song found with that id.");
  637. return next(null, song);
  638. }
  639. ],
  640. async (err, song) => {
  641. if (err) {
  642. err = await UtilsModule.runJob("GET_ERROR", { error: err });
  643. console.log(
  644. "ERROR",
  645. "SONGS_UNDISLIKE",
  646. `User "${session.userId}" failed to like song ${songId}. "${err}"`
  647. );
  648. return cb({ status: "failure", message: err });
  649. }
  650. const oldSongId = songId;
  651. songId = song._id;
  652. return userModel.findOne({ _id: session.userId }, (err, user) => {
  653. if (user.disliked.indexOf(songId) === -1)
  654. return cb({
  655. status: "failure",
  656. message: "You have not disliked this song."
  657. });
  658. return userModel.updateOne(
  659. { _id: session.userId },
  660. { $pull: { liked: songId, disliked: songId } },
  661. err => {
  662. if (!err) {
  663. return userModel.countDocuments({ liked: songId }, (err, likes) => {
  664. if (err)
  665. return cb({
  666. status: "failure",
  667. message: "Something went wrong while undisliking this song."
  668. });
  669. return userModel.countDocuments({ disliked: songId }, (err, dislikes) => {
  670. if (err)
  671. return cb({
  672. status: "failure",
  673. message: "Something went wrong while undisliking this song."
  674. });
  675. return songModel.update(
  676. { _id: songId },
  677. {
  678. $set: {
  679. likes,
  680. dislikes
  681. }
  682. },
  683. err => {
  684. if (err)
  685. return cb({
  686. status: "failure",
  687. message: "Something went wrong while undisliking this song."
  688. });
  689. SongsModule.runJob("UPDATE_SONG", { songId });
  690. CacheModule.runJob("PUB", {
  691. channel: "song.undislike",
  692. value: JSON.stringify({
  693. songId: oldSongId,
  694. userId: session.userId,
  695. likes,
  696. dislikes
  697. })
  698. });
  699. return cb({
  700. status: "success",
  701. message: "You have successfully undisliked this song."
  702. });
  703. }
  704. );
  705. });
  706. });
  707. }
  708. return cb({
  709. status: "failure",
  710. message: "Something went wrong while undisliking this song."
  711. });
  712. }
  713. );
  714. });
  715. }
  716. );
  717. }),
  718. /**
  719. * Unlikes a song
  720. *
  721. * @param session
  722. * @param songId - the song id
  723. * @param cb
  724. */
  725. unlike: isLoginRequired(async (session, songId, cb) => {
  726. const userModel = await DBModule.runJob("GET_MODEL", { modelName: "user" });
  727. const songModel = await DBModule.runJob("GET_MODEL", { modelName: "song" });
  728. async.waterfall(
  729. [
  730. next => {
  731. songModel.findOne({ songId }, next);
  732. },
  733. (song, next) => {
  734. if (!song) return next("No song found with that id.");
  735. return next(null, song);
  736. }
  737. ],
  738. async (err, song) => {
  739. if (err) {
  740. err = await UtilsModule.runJob("GET_ERROR", { error: err });
  741. console.log(
  742. "ERROR",
  743. "SONGS_UNLIKE",
  744. `User "${session.userId}" failed to like song ${songId}. "${err}"`
  745. );
  746. return cb({ status: "failure", message: err });
  747. }
  748. const oldSongId = songId;
  749. songId = song._id;
  750. return userModel.findOne({ _id: session.userId }, (err, user) => {
  751. if (user.liked.indexOf(songId) === -1)
  752. return cb({
  753. status: "failure",
  754. message: "You have not liked this song."
  755. });
  756. return userModel.updateOne(
  757. { _id: session.userId },
  758. { $pull: { liked: songId, disliked: songId } },
  759. err => {
  760. if (!err) {
  761. return userModel.countDocuments({ liked: songId }, (err, likes) => {
  762. if (err)
  763. return cb({
  764. status: "failure",
  765. message: "Something went wrong while unliking this song."
  766. });
  767. return userModel.countDocuments({ disliked: songId }, (err, dislikes) => {
  768. if (err)
  769. return cb({
  770. status: "failure",
  771. message: "Something went wrong while undiking this song."
  772. });
  773. return songModel.updateOne(
  774. { _id: songId },
  775. {
  776. $set: {
  777. likes,
  778. dislikes
  779. }
  780. },
  781. err => {
  782. if (err)
  783. return cb({
  784. status: "failure",
  785. message: "Something went wrong while unliking this song."
  786. });
  787. SongsModule.runJob("UPDATE_SONG", { songId });
  788. CacheModule.runJob("PUB", {
  789. channel: "song.unlike",
  790. value: JSON.stringify({
  791. songId: oldSongId,
  792. userId: session.userId,
  793. likes,
  794. dislikes
  795. })
  796. });
  797. return cb({
  798. status: "success",
  799. message: "You have successfully unliked this song."
  800. });
  801. }
  802. );
  803. });
  804. });
  805. }
  806. return cb({
  807. status: "failure",
  808. message: "Something went wrong while unliking this song."
  809. });
  810. }
  811. );
  812. });
  813. }
  814. );
  815. }),
  816. /**
  817. * Gets user's own song ratings
  818. *
  819. * @param session
  820. * @param songId - the song id
  821. * @param cb
  822. */
  823. getOwnSongRatings: isLoginRequired(async (session, songId, cb) => {
  824. const userModel = await DBModule.runJob("GET_MODEL", { modelName: "user" });
  825. const songModel = await DBModule.runJob("GET_MODEL", { modelName: "song" });
  826. async.waterfall(
  827. [
  828. next => {
  829. songModel.findOne({ songId }, next);
  830. },
  831. (song, next) => {
  832. if (!song) return next("No song found with that id.");
  833. return next(null, song);
  834. }
  835. ],
  836. async (err, song) => {
  837. if (err) {
  838. err = await UtilsModule.runJob("GET_ERROR", { error: err });
  839. console.log(
  840. "ERROR",
  841. "SONGS_GET_OWN_RATINGS",
  842. `User "${session.userId}" failed to get ratings for ${songId}. "${err}"`
  843. );
  844. return cb({ status: "failure", message: err });
  845. }
  846. const newSongId = song._id;
  847. return userModel.findOne({ _id: session.userId }, async (err, user) => {
  848. if (!err && user) {
  849. return cb({
  850. status: "success",
  851. songId,
  852. liked: user.liked.indexOf(newSongId) !== -1,
  853. disliked: user.disliked.indexOf(newSongId) !== -1
  854. });
  855. }
  856. return cb({
  857. status: "failure",
  858. message: await UtilsModule.runJob("GET_ERROR", {
  859. error: err
  860. })
  861. });
  862. });
  863. }
  864. );
  865. })
  866. };