songs.js 44 KB

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