songs.js 43 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044
  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) => next(null, response.song))
  274. .catch(next);
  275. },
  276. ],
  277. async (err, song) => {
  278. if (err) {
  279. err = await utils.runJob("GET_ERROR", { error: err });
  280. console.log(
  281. "ERROR",
  282. "SONGS_GET_SONG_FOR_ACTIVITY",
  283. `Failed to obtain metadata of song ${songId} for activity formatting. "${err}"`
  284. );
  285. return cb({ status: "failure", message: err });
  286. } else {
  287. if (song) {
  288. console.log(
  289. "SUCCESS",
  290. "SONGS_GET_SONG_FOR_ACTIVITY",
  291. `Obtained metadata of song ${songId} for activity formatting successfully.`
  292. );
  293. cb({
  294. status: "success",
  295. data: {
  296. title: song.title,
  297. thumbnail: song.thumbnail,
  298. },
  299. });
  300. } else {
  301. console.log(
  302. "ERROR",
  303. "SONGS_GET_SONG_FOR_ACTIVITY",
  304. `Song ${songId} does not exist so failed to obtain for activity formatting.`
  305. );
  306. cb({ status: "failure" });
  307. }
  308. }
  309. }
  310. );
  311. },
  312. /**
  313. * Updates a song
  314. *
  315. * @param session
  316. * @param songId - the song id
  317. * @param song - the updated song object
  318. * @param cb
  319. */
  320. update: hooks.adminRequired(async (session, songId, song, cb) => {
  321. const songModel = await db.runJob("GET_MODEL", { modelName: "song" });
  322. async.waterfall(
  323. [
  324. (next) => {
  325. songModel.updateOne(
  326. { _id: songId },
  327. song,
  328. { runValidators: true },
  329. next
  330. );
  331. },
  332. (res, next) => {
  333. songs
  334. .runJob("UPDATE_SONG", { songId })
  335. .then((song) => next(null, song))
  336. .catch(next);
  337. },
  338. ],
  339. async (err, song) => {
  340. if (err) {
  341. err = await utils.runJob("GET_ERROR", { error: err });
  342. console.log(
  343. "ERROR",
  344. "SONGS_UPDATE",
  345. `Failed to update song "${songId}". "${err}"`
  346. );
  347. return cb({ status: "failure", message: err });
  348. }
  349. console.log(
  350. "SUCCESS",
  351. "SONGS_UPDATE",
  352. `Successfully updated song "${songId}".`
  353. );
  354. cache.runJob("PUB", {
  355. channel: "song.updated",
  356. value: song.songId,
  357. });
  358. cb({
  359. status: "success",
  360. message: "Song has been successfully updated",
  361. data: song,
  362. });
  363. }
  364. );
  365. }),
  366. /**
  367. * Removes a song
  368. *
  369. * @param session
  370. * @param songId - the song id
  371. * @param cb
  372. */
  373. remove: hooks.adminRequired(async (session, songId, cb) => {
  374. const songModel = await db.runJob("GET_MODEL", { modelName: "song" });
  375. async.waterfall(
  376. [
  377. (next) => {
  378. songModel.deleteOne({ _id: songId }, next);
  379. },
  380. (res, next) => {
  381. //TODO Check if res gets returned from above
  382. cache
  383. .runJob("HDEL", { table: "songs", key: songId })
  384. .then(() => next())
  385. .catch(next);
  386. },
  387. ],
  388. async (err) => {
  389. if (err) {
  390. err = await utils.runJob("GET_ERROR", { error: err });
  391. console.log(
  392. "ERROR",
  393. "SONGS_UPDATE",
  394. `Failed to remove song "${songId}". "${err}"`
  395. );
  396. return cb({ status: "failure", message: err });
  397. }
  398. console.log(
  399. "SUCCESS",
  400. "SONGS_UPDATE",
  401. `Successfully remove song "${songId}".`
  402. );
  403. cache.runJob("PUB", { channel: "song.removed", value: songId });
  404. cb({
  405. status: "success",
  406. message: "Song has been successfully updated",
  407. });
  408. }
  409. );
  410. }),
  411. /**
  412. * Adds a song
  413. *
  414. * @param session
  415. * @param song - the song object
  416. * @param cb
  417. */
  418. add: hooks.adminRequired(async (session, song, cb) => {
  419. const songModel = await db.runJob("GET_MODEL", { modelName: "song" });
  420. async.waterfall(
  421. [
  422. (next) => {
  423. songModel.findOne({ songId: song.songId }, next);
  424. },
  425. (existingSong, next) => {
  426. if (existingSong)
  427. return next("Song is already in rotation.");
  428. next();
  429. },
  430. (next) => {
  431. const newSong = new songModel(song);
  432. newSong.acceptedBy = session.userId;
  433. newSong.acceptedAt = Date.now();
  434. newSong.save(next);
  435. },
  436. (res, next) => {
  437. queueSongs.remove(session, song._id, () => {
  438. next();
  439. });
  440. },
  441. ],
  442. async (err) => {
  443. if (err) {
  444. err = await utils.runJob("GET_ERROR", { error: err });
  445. console.log(
  446. "ERROR",
  447. "SONGS_ADD",
  448. `User "${session.userId}" failed to add song. "${err}"`
  449. );
  450. return cb({ status: "failure", message: err });
  451. }
  452. console.log(
  453. "SUCCESS",
  454. "SONGS_ADD",
  455. `User "${session.userId}" successfully added song "${song.songId}".`
  456. );
  457. cache.runJob("PUB", {
  458. channel: "song.added",
  459. value: song.songId,
  460. });
  461. cb({
  462. status: "success",
  463. message: "Song has been moved from the queue successfully.",
  464. });
  465. }
  466. );
  467. //TODO Check if video is in queue and Add the song to the appropriate stations
  468. }),
  469. /**
  470. * Likes a song
  471. *
  472. * @param session
  473. * @param songId - the song id
  474. * @param cb
  475. */
  476. like: hooks.loginRequired(async (session, songId, cb) => {
  477. const userModel = await db.runJob("GET_MODEL", { modelName: "user" });
  478. const songModel = await db.runJob("GET_MODEL", { modelName: "song" });
  479. async.waterfall(
  480. [
  481. (next) => {
  482. songModel.findOne({ songId }, next);
  483. },
  484. (song, next) => {
  485. if (!song) return next("No song found with that id.");
  486. next(null, song);
  487. },
  488. ],
  489. async (err, song) => {
  490. if (err) {
  491. err = await utils.runJob("GET_ERROR", { error: err });
  492. console.log(
  493. "ERROR",
  494. "SONGS_LIKE",
  495. `User "${session.userId}" failed to like song ${songId}. "${err}"`
  496. );
  497. return cb({ status: "failure", message: err });
  498. }
  499. let oldSongId = songId;
  500. songId = song._id;
  501. userModel.findOne({ _id: session.userId }, (err, user) => {
  502. if (user.liked.indexOf(songId) !== -1)
  503. return cb({
  504. status: "failure",
  505. message: "You have already liked this song.",
  506. });
  507. userModel.updateOne(
  508. { _id: session.userId },
  509. {
  510. $push: { liked: songId },
  511. $pull: { disliked: songId },
  512. },
  513. (err) => {
  514. if (!err) {
  515. userModel.countDocuments(
  516. { liked: songId },
  517. (err, likes) => {
  518. if (err)
  519. return cb({
  520. status: "failure",
  521. message:
  522. "Something went wrong while liking this song.",
  523. });
  524. userModel.countDocuments(
  525. { disliked: songId },
  526. (err, dislikes) => {
  527. if (err)
  528. return cb({
  529. status: "failure",
  530. message:
  531. "Something went wrong while liking this song.",
  532. });
  533. songModel.update(
  534. { _id: songId },
  535. {
  536. $set: {
  537. likes: likes,
  538. dislikes: dislikes,
  539. },
  540. },
  541. (err) => {
  542. if (err)
  543. return cb({
  544. status:
  545. "failure",
  546. message:
  547. "Something went wrong while liking this song.",
  548. });
  549. songs.runJob(
  550. "UPDATE_SONG",
  551. { songId }
  552. );
  553. cache.runJob("PUB", {
  554. channel:
  555. "song.like",
  556. value: JSON.stringify(
  557. {
  558. songId: oldSongId,
  559. userId:
  560. session.userId,
  561. likes: likes,
  562. dislikes: dislikes,
  563. }
  564. ),
  565. });
  566. activities.runJob(
  567. "ADD_ACTIVITY",
  568. {
  569. userId:
  570. session.userId,
  571. activityType:
  572. "liked_song",
  573. payload: [
  574. songId,
  575. ],
  576. }
  577. );
  578. return cb({
  579. status: "success",
  580. message:
  581. "You have successfully liked this song.",
  582. });
  583. }
  584. );
  585. }
  586. );
  587. }
  588. );
  589. } else
  590. return cb({
  591. status: "failure",
  592. message:
  593. "Something went wrong while liking this song.",
  594. });
  595. }
  596. );
  597. });
  598. }
  599. );
  600. }),
  601. /**
  602. * Dislikes a song
  603. *
  604. * @param session
  605. * @param songId - the song id
  606. * @param cb
  607. */
  608. dislike: hooks.loginRequired(async (session, songId, cb) => {
  609. const userModel = await db.runJob("GET_MODEL", { modelName: "user" });
  610. const songModel = await db.runJob("GET_MODEL", { modelName: "song" });
  611. async.waterfall(
  612. [
  613. (next) => {
  614. songModel.findOne({ songId }, next);
  615. },
  616. (song, next) => {
  617. if (!song) return next("No song found with that id.");
  618. next(null, song);
  619. },
  620. ],
  621. async (err, song) => {
  622. if (err) {
  623. err = await utils.runJob("GET_ERROR", { error: err });
  624. console.log(
  625. "ERROR",
  626. "SONGS_DISLIKE",
  627. `User "${session.userId}" failed to like song ${songId}. "${err}"`
  628. );
  629. return cb({ status: "failure", message: err });
  630. }
  631. let oldSongId = songId;
  632. songId = song._id;
  633. userModel.findOne({ _id: session.userId }, (err, user) => {
  634. if (user.disliked.indexOf(songId) !== -1)
  635. return cb({
  636. status: "failure",
  637. message: "You have already disliked this song.",
  638. });
  639. userModel.updateOne(
  640. { _id: session.userId },
  641. {
  642. $push: { disliked: songId },
  643. $pull: { liked: songId },
  644. },
  645. (err) => {
  646. if (!err) {
  647. userModel.countDocuments(
  648. { liked: songId },
  649. (err, likes) => {
  650. if (err)
  651. return cb({
  652. status: "failure",
  653. message:
  654. "Something went wrong while disliking this song.",
  655. });
  656. userModel.countDocuments(
  657. { disliked: songId },
  658. (err, dislikes) => {
  659. if (err)
  660. return cb({
  661. status: "failure",
  662. message:
  663. "Something went wrong while disliking this song.",
  664. });
  665. songModel.update(
  666. { _id: songId },
  667. {
  668. $set: {
  669. likes: likes,
  670. dislikes: dislikes,
  671. },
  672. },
  673. (err, res) => {
  674. if (err)
  675. return cb({
  676. status:
  677. "failure",
  678. message:
  679. "Something went wrong while disliking this song.",
  680. });
  681. songs.runJob(
  682. "UPDATE_SONG",
  683. { songId }
  684. );
  685. cache.runJob("PUB", {
  686. channel:
  687. "song.dislike",
  688. value: JSON.stringify(
  689. {
  690. songId: oldSongId,
  691. userId:
  692. session.userId,
  693. likes: likes,
  694. dislikes: dislikes,
  695. }
  696. ),
  697. });
  698. return cb({
  699. status: "success",
  700. message:
  701. "You have successfully disliked this song.",
  702. });
  703. }
  704. );
  705. }
  706. );
  707. }
  708. );
  709. } else
  710. return cb({
  711. status: "failure",
  712. message:
  713. "Something went wrong while disliking this song.",
  714. });
  715. }
  716. );
  717. });
  718. }
  719. );
  720. }),
  721. /**
  722. * Undislikes a song
  723. *
  724. * @param session
  725. * @param songId - the song id
  726. * @param cb
  727. */
  728. undislike: hooks.loginRequired(async (session, songId, cb) => {
  729. const userModel = await db.runJob("GET_MODEL", { modelName: "user" });
  730. const songModel = await db.runJob("GET_MODEL", { modelName: "song" });
  731. async.waterfall(
  732. [
  733. (next) => {
  734. songModel.findOne({ songId }, next);
  735. },
  736. (song, next) => {
  737. if (!song) return next("No song found with that id.");
  738. next(null, song);
  739. },
  740. ],
  741. async (err, song) => {
  742. if (err) {
  743. err = await utils.runJob("GET_ERROR", { error: err });
  744. console.log(
  745. "ERROR",
  746. "SONGS_UNDISLIKE",
  747. `User "${session.userId}" failed to like song ${songId}. "${err}"`
  748. );
  749. return cb({ status: "failure", message: err });
  750. }
  751. let oldSongId = songId;
  752. songId = song._id;
  753. userModel.findOne({ _id: session.userId }, (err, user) => {
  754. if (user.disliked.indexOf(songId) === -1)
  755. return cb({
  756. status: "failure",
  757. message: "You have not disliked this song.",
  758. });
  759. userModel.updateOne(
  760. { _id: session.userId },
  761. { $pull: { liked: songId, disliked: songId } },
  762. (err) => {
  763. if (!err) {
  764. userModel.countDocuments(
  765. { liked: songId },
  766. (err, likes) => {
  767. if (err)
  768. return cb({
  769. status: "failure",
  770. message:
  771. "Something went wrong while undisliking this song.",
  772. });
  773. userModel.countDocuments(
  774. { disliked: songId },
  775. (err, dislikes) => {
  776. if (err)
  777. return cb({
  778. status: "failure",
  779. message:
  780. "Something went wrong while undisliking this song.",
  781. });
  782. songModel.update(
  783. { _id: songId },
  784. {
  785. $set: {
  786. likes: likes,
  787. dislikes: dislikes,
  788. },
  789. },
  790. (err) => {
  791. if (err)
  792. return cb({
  793. status:
  794. "failure",
  795. message:
  796. "Something went wrong while undisliking this song.",
  797. });
  798. songs.runJob(
  799. "UPDATE_SONG",
  800. { songId }
  801. );
  802. cache.runJob("PUB", {
  803. channel:
  804. "song.undislike",
  805. value: JSON.stringify(
  806. {
  807. songId: oldSongId,
  808. userId:
  809. session.userId,
  810. likes: likes,
  811. dislikes: dislikes,
  812. }
  813. ),
  814. });
  815. return cb({
  816. status: "success",
  817. message:
  818. "You have successfully undisliked this song.",
  819. });
  820. }
  821. );
  822. }
  823. );
  824. }
  825. );
  826. } else
  827. return cb({
  828. status: "failure",
  829. message:
  830. "Something went wrong while undisliking this song.",
  831. });
  832. }
  833. );
  834. });
  835. }
  836. );
  837. }),
  838. /**
  839. * Unlikes a song
  840. *
  841. * @param session
  842. * @param songId - the song id
  843. * @param cb
  844. */
  845. unlike: hooks.loginRequired(async (session, songId, cb) => {
  846. const userModel = await db.runJob("GET_MODEL", { modelName: "user" });
  847. const songModel = await db.runJob("GET_MODEL", { modelName: "song" });
  848. async.waterfall(
  849. [
  850. (next) => {
  851. songModel.findOne({ songId }, next);
  852. },
  853. (song, next) => {
  854. if (!song) return next("No song found with that id.");
  855. next(null, song);
  856. },
  857. ],
  858. async (err, song) => {
  859. if (err) {
  860. err = await utils.runJob("GET_ERROR", { error: err });
  861. console.log(
  862. "ERROR",
  863. "SONGS_UNLIKE",
  864. `User "${session.userId}" failed to like song ${songId}. "${err}"`
  865. );
  866. return cb({ status: "failure", message: err });
  867. }
  868. let oldSongId = songId;
  869. songId = song._id;
  870. userModel.findOne({ _id: session.userId }, (err, user) => {
  871. if (user.liked.indexOf(songId) === -1)
  872. return cb({
  873. status: "failure",
  874. message: "You have not liked this song.",
  875. });
  876. userModel.updateOne(
  877. { _id: session.userId },
  878. { $pull: { liked: songId, disliked: songId } },
  879. (err) => {
  880. if (!err) {
  881. userModel.countDocuments(
  882. { liked: songId },
  883. (err, likes) => {
  884. if (err)
  885. return cb({
  886. status: "failure",
  887. message:
  888. "Something went wrong while unliking this song.",
  889. });
  890. userModel.countDocuments(
  891. { disliked: songId },
  892. (err, dislikes) => {
  893. if (err)
  894. return cb({
  895. status: "failure",
  896. message:
  897. "Something went wrong while undiking this song.",
  898. });
  899. songModel.updateOne(
  900. { _id: songId },
  901. {
  902. $set: {
  903. likes: likes,
  904. dislikes: dislikes,
  905. },
  906. },
  907. (err) => {
  908. if (err)
  909. return cb({
  910. status:
  911. "failure",
  912. message:
  913. "Something went wrong while unliking this song.",
  914. });
  915. songs.runJob(
  916. "UPDATE_SONG",
  917. { songId }
  918. );
  919. cache.runJob("PUB", {
  920. channel:
  921. "song.unlike",
  922. value: JSON.stringify(
  923. {
  924. songId: oldSongId,
  925. userId:
  926. session.userId,
  927. likes: likes,
  928. dislikes: dislikes,
  929. }
  930. ),
  931. });
  932. return cb({
  933. status: "success",
  934. message:
  935. "You have successfully unliked this song.",
  936. });
  937. }
  938. );
  939. }
  940. );
  941. }
  942. );
  943. } else
  944. return cb({
  945. status: "failure",
  946. message:
  947. "Something went wrong while unliking this song.",
  948. });
  949. }
  950. );
  951. });
  952. }
  953. );
  954. }),
  955. /**
  956. * Gets user's own song ratings
  957. *
  958. * @param session
  959. * @param songId - the song id
  960. * @param cb
  961. */
  962. getOwnSongRatings: hooks.loginRequired(async (session, songId, cb) => {
  963. const userModel = await db.runJob("GET_MODEL", { modelName: "user" });
  964. const songModel = await db.runJob("GET_MODEL", { modelName: "song" });
  965. async.waterfall(
  966. [
  967. (next) => {
  968. songModel.findOne({ songId }, next);
  969. },
  970. (song, next) => {
  971. if (!song) return next("No song found with that id.");
  972. next(null, song);
  973. },
  974. ],
  975. async (err, song) => {
  976. if (err) {
  977. err = await utils.runJob("GET_ERROR", { error: err });
  978. console.log(
  979. "ERROR",
  980. "SONGS_GET_OWN_RATINGS",
  981. `User "${session.userId}" failed to get ratings for ${songId}. "${err}"`
  982. );
  983. return cb({ status: "failure", message: err });
  984. }
  985. let newSongId = song._id;
  986. userModel.findOne(
  987. { _id: session.userId },
  988. async (err, user) => {
  989. if (!err && user) {
  990. return cb({
  991. status: "success",
  992. songId: songId,
  993. liked: user.liked.indexOf(newSongId) !== -1,
  994. disliked:
  995. user.disliked.indexOf(newSongId) !== -1,
  996. });
  997. } else {
  998. return cb({
  999. status: "failure",
  1000. message: await utils.runJob("GET_ERROR", {
  1001. error: err,
  1002. }),
  1003. });
  1004. }
  1005. }
  1006. );
  1007. }
  1008. );
  1009. }),
  1010. };