songs.js 43 KB

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