songs.js 38 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493
  1. import async from "async";
  2. import config from "config";
  3. import { isAdminRequired, isLoginRequired } from "./hooks";
  4. import moduleManager from "../../index";
  5. const DBModule = moduleManager.modules.db;
  6. const UtilsModule = moduleManager.modules.utils;
  7. const WSModule = moduleManager.modules.ws;
  8. const CacheModule = moduleManager.modules.cache;
  9. const SongsModule = moduleManager.modules.songs;
  10. const ActivitiesModule = moduleManager.modules.activities;
  11. const YouTubeModule = moduleManager.modules.youtube;
  12. const PlaylistsModule = moduleManager.modules.playlists;
  13. CacheModule.runJob("SUB", {
  14. channel: "song.newUnverifiedSong",
  15. cb: async songId => {
  16. const songModel = await DBModule.runJob("GET_MODEL", {
  17. modelName: "song"
  18. });
  19. songModel.findOne({ _id: songId }, (err, song) => {
  20. WSModule.runJob("EMIT_TO_ROOM", {
  21. room: "admin.unverifiedSongs",
  22. args: ["event:admin.unverifiedSong.added", song]
  23. });
  24. });
  25. }
  26. });
  27. CacheModule.runJob("SUB", {
  28. channel: "song.removedUnverifiedSong",
  29. cb: songId => {
  30. WSModule.runJob("EMIT_TO_ROOM", {
  31. room: "admin.unverifiedSongs",
  32. args: ["event:admin.unverifiedSong.removed", songId]
  33. });
  34. }
  35. });
  36. CacheModule.runJob("SUB", {
  37. channel: "song.updatedUnverifiedSong",
  38. cb: async songId => {
  39. const songModel = await DBModule.runJob("GET_MODEL", {
  40. modelName: "song"
  41. });
  42. songModel.findOne({ _id: songId }, (err, song) => {
  43. WSModule.runJob("EMIT_TO_ROOM", {
  44. room: "admin.unverifiedSongs",
  45. args: ["event:admin.unverifiedSong.updated", song]
  46. });
  47. });
  48. }
  49. });
  50. CacheModule.runJob("SUB", {
  51. channel: "song.newVerifiedSong",
  52. cb: async songId => {
  53. const songModel = await DBModule.runJob("GET_MODEL", { modelName: "song" });
  54. songModel.findOne({ songId }, (err, song) => {
  55. WSModule.runJob("EMIT_TO_ROOM", {
  56. room: "admin.songs",
  57. args: ["event:admin.verifiedSong.added", song]
  58. });
  59. });
  60. }
  61. });
  62. CacheModule.runJob("SUB", {
  63. channel: "song.removedVerifiedSong",
  64. cb: songId => {
  65. WSModule.runJob("EMIT_TO_ROOM", {
  66. room: "admin.songs",
  67. args: ["event:admin.verifiedSong.removed", songId]
  68. });
  69. }
  70. });
  71. CacheModule.runJob("SUB", {
  72. channel: "song.updatedVerifiedSong",
  73. cb: async songId => {
  74. const songModel = await DBModule.runJob("GET_MODEL", { modelName: "song" });
  75. songModel.findOne({ songId }, (err, song) => {
  76. WSModule.runJob("EMIT_TO_ROOM", {
  77. room: "admin.songs",
  78. args: ["event:admin.verifiedSong.updated", song]
  79. });
  80. });
  81. }
  82. });
  83. CacheModule.runJob("SUB", {
  84. channel: "song.newHiddenSong",
  85. cb: async songId => {
  86. const songModel = await DBModule.runJob("GET_MODEL", {
  87. modelName: "song"
  88. });
  89. songModel.findOne({ _id: songId }, (err, song) => {
  90. WSModule.runJob("EMIT_TO_ROOM", {
  91. room: "admin.hiddenSongs",
  92. args: ["event:admin.hiddenSong.added", song]
  93. });
  94. });
  95. }
  96. });
  97. CacheModule.runJob("SUB", {
  98. channel: "song.removedHiddenSong",
  99. cb: songId => {
  100. WSModule.runJob("EMIT_TO_ROOM", {
  101. room: "admin.hiddenSongs",
  102. args: ["event:admin.hiddenSong.removed", songId]
  103. });
  104. }
  105. });
  106. CacheModule.runJob("SUB", {
  107. channel: "song.updatedHiddenSong",
  108. cb: async songId => {
  109. const songModel = await DBModule.runJob("GET_MODEL", {
  110. modelName: "song"
  111. });
  112. songModel.findOne({ _id: songId }, (err, song) => {
  113. WSModule.runJob("EMIT_TO_ROOM", {
  114. room: "admin.hiddenSongs",
  115. args: ["event:admin.hiddenSong.updated", song]
  116. });
  117. });
  118. }
  119. });
  120. CacheModule.runJob("SUB", {
  121. channel: "song.like",
  122. cb: data => {
  123. WSModule.runJob("EMIT_TO_ROOM", {
  124. room: `song.${data.songId}`,
  125. args: [
  126. "event:song.like",
  127. {
  128. songId: data.songId,
  129. likes: data.likes,
  130. dislikes: data.dislikes
  131. }
  132. ]
  133. });
  134. WSModule.runJob("SOCKETS_FROM_USER", { userId: data.userId }).then(sockets => {
  135. sockets.forEach(socket => {
  136. socket.dispatch("event:song.newRatings", {
  137. songId: data.songId,
  138. liked: true,
  139. disliked: false
  140. });
  141. });
  142. });
  143. }
  144. });
  145. CacheModule.runJob("SUB", {
  146. channel: "song.dislike",
  147. cb: data => {
  148. WSModule.runJob("EMIT_TO_ROOM", {
  149. room: `song.${data.songId}`,
  150. args: [
  151. "event:song.dislike",
  152. {
  153. songId: data.songId,
  154. likes: data.likes,
  155. dislikes: data.dislikes
  156. }
  157. ]
  158. });
  159. WSModule.runJob("SOCKETS_FROM_USER", { userId: data.userId }).then(sockets => {
  160. sockets.forEach(socket => {
  161. socket.dispatch("event:song.newRatings", {
  162. songId: data.songId,
  163. liked: false,
  164. disliked: true
  165. });
  166. });
  167. });
  168. }
  169. });
  170. CacheModule.runJob("SUB", {
  171. channel: "song.unlike",
  172. cb: data => {
  173. WSModule.runJob("EMIT_TO_ROOM", {
  174. room: `song.${data.songId}`,
  175. args: [
  176. "event:song.unlike",
  177. {
  178. songId: data.songId,
  179. likes: data.likes,
  180. dislikes: data.dislikes
  181. }
  182. ]
  183. });
  184. WSModule.runJob("SOCKETS_FROM_USER", { userId: data.userId }).then(sockets => {
  185. sockets.forEach(socket => {
  186. socket.dispatch("event:song.newRatings", {
  187. songId: data.songId,
  188. liked: false,
  189. disliked: false
  190. });
  191. });
  192. });
  193. }
  194. });
  195. CacheModule.runJob("SUB", {
  196. channel: "song.undislike",
  197. cb: data => {
  198. WSModule.runJob("EMIT_TO_ROOM", {
  199. room: `song.${data.songId}`,
  200. args: [
  201. "event:song.undislike",
  202. {
  203. songId: data.songId,
  204. likes: data.likes,
  205. dislikes: data.dislikes
  206. }
  207. ]
  208. });
  209. WSModule.runJob("SOCKETS_FROM_USER", { userId: data.userId }).then(sockets => {
  210. sockets.forEach(socket => {
  211. socket.dispatch("event:song.newRatings", {
  212. songId: data.songId,
  213. liked: false,
  214. disliked: false
  215. });
  216. });
  217. });
  218. }
  219. });
  220. export default {
  221. /**
  222. * Returns the length of the songs list
  223. *
  224. * @param {object} session - the session object automatically added by the websocket
  225. * @param cb
  226. */
  227. length: isAdminRequired(async function length(session, status, cb) {
  228. const songModel = await DBModule.runJob("GET_MODEL", { modelName: "song" }, this);
  229. async.waterfall(
  230. [
  231. next => {
  232. songModel.countDocuments({ status }, next);
  233. }
  234. ],
  235. async (err, count) => {
  236. if (err) {
  237. err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
  238. this.log(
  239. "ERROR",
  240. "SONGS_LENGTH",
  241. `Failed to get length from songs that have the status ${status}. "${err}"`
  242. );
  243. return cb({ status: "failure", message: err });
  244. }
  245. this.log(
  246. "SUCCESS",
  247. "SONGS_LENGTH",
  248. `Got length from songs that have the status ${status} successfully.`
  249. );
  250. return cb(count);
  251. }
  252. );
  253. }),
  254. /**
  255. * Gets a set of songs
  256. *
  257. * @param {object} session - the session object automatically added by the websocket
  258. * @param set - the set number to return
  259. * @param cb
  260. */
  261. getSet: isAdminRequired(async function getSet(session, set, status, cb) {
  262. const songModel = await DBModule.runJob("GET_MODEL", { modelName: "song" }, this);
  263. async.waterfall(
  264. [
  265. next => {
  266. songModel
  267. .find({ status })
  268. .skip(15 * (set - 1))
  269. .limit(15)
  270. .exec(next);
  271. }
  272. ],
  273. async (err, songs) => {
  274. if (err) {
  275. err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
  276. this.log(
  277. "ERROR",
  278. "SONGS_GET_SET",
  279. `Failed to get set from songs that have the status ${status}. "${err}"`
  280. );
  281. return cb({ status: "failure", message: err });
  282. }
  283. this.log("SUCCESS", "SONGS_GET_SET", `Got set from songs that have the status ${status} successfully.`);
  284. return cb(songs);
  285. }
  286. );
  287. }),
  288. /**
  289. * Gets a song from the YouTube song id
  290. *
  291. * @param {object} session - the session object automatically added by the websocket
  292. * @param {string} songId - the YouTube song id
  293. * @param {Function} cb
  294. */
  295. getSong: isAdminRequired(function getSong(session, songId, cb) {
  296. async.waterfall(
  297. [
  298. next => {
  299. SongsModule.runJob("GET_SONG_FROM_ID", { songId }, this)
  300. .then(song => {
  301. next(null, song);
  302. })
  303. .catch(err => {
  304. next(err);
  305. });
  306. }
  307. ],
  308. async (err, song) => {
  309. if (err) {
  310. err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
  311. this.log("ERROR", "SONGS_GET_SONG", `Failed to get song ${songId}. "${err}"`);
  312. return cb({ status: "failure", message: err });
  313. }
  314. this.log("SUCCESS", "SONGS_GET_SONG", `Got song ${songId} successfully.`);
  315. return cb({ status: "success", data: song });
  316. }
  317. );
  318. }),
  319. /**
  320. * Gets a song from the Musare song id
  321. *
  322. * @param {object} session - the session object automatically added by the websocket
  323. * @param {string} songId - the Musare song id
  324. * @param {Function} cb
  325. */
  326. getSongFromMusareId: isAdminRequired(function getSong(session, songId, cb) {
  327. async.waterfall(
  328. [
  329. next => {
  330. SongsModule.runJob("GET_SONG", { id: songId }, this)
  331. .then(response => next(null, response.song))
  332. .catch(err => next(err));
  333. }
  334. ],
  335. async (err, song) => {
  336. if (err) {
  337. err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
  338. this.log("ERROR", "SONGS_GET_SONG_FROM_MUSARE_ID", `Failed to get song ${songId}. "${err}"`);
  339. return cb({ status: "failure", message: err });
  340. }
  341. this.log("SUCCESS", "SONGS_GET_SONG_FROM_MUSARE_ID", `Got song ${songId} successfully.`);
  342. return cb({ status: "success", data: { song } });
  343. }
  344. );
  345. }),
  346. /**
  347. * Obtains basic metadata of a song in order to format an activity
  348. *
  349. * @param {object} session - the session object automatically added by the websocket
  350. * @param {string} songId - the song id
  351. * @param {Function} cb - callback
  352. */
  353. getSongForActivity(session, songId, cb) {
  354. async.waterfall(
  355. [
  356. next => {
  357. SongsModule.runJob("GET_SONG_FROM_ID", { songId }, this)
  358. .then(response => next(null, response.song))
  359. .catch(next);
  360. }
  361. ],
  362. async (err, song) => {
  363. if (err) {
  364. err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
  365. this.log(
  366. "ERROR",
  367. "SONGS_GET_SONG_FOR_ACTIVITY",
  368. `Failed to obtain metadata of song ${songId} for activity formatting. "${err}"`
  369. );
  370. return cb({ status: "failure", message: err });
  371. }
  372. if (song) {
  373. this.log(
  374. "SUCCESS",
  375. "SONGS_GET_SONG_FOR_ACTIVITY",
  376. `Obtained metadata of song ${songId} for activity formatting successfully.`
  377. );
  378. return cb({
  379. status: "success",
  380. data: {
  381. title: song.title,
  382. thumbnail: song.thumbnail
  383. }
  384. });
  385. }
  386. this.log(
  387. "ERROR",
  388. "SONGS_GET_SONG_FOR_ACTIVITY",
  389. `Song ${songId} does not exist so failed to obtain for activity formatting.`
  390. );
  391. return cb({ status: "failure" });
  392. }
  393. );
  394. },
  395. /**
  396. * Updates a song
  397. *
  398. * @param {object} session - the session object automatically added by the websocket
  399. * @param {string} songId - the song id
  400. * @param {object} song - the updated song object
  401. * @param {Function} cb
  402. */
  403. update: isAdminRequired(async function update(session, songId, song, cb) {
  404. const songModel = await DBModule.runJob("GET_MODEL", { modelName: "song" }, this);
  405. let existingSong = null;
  406. async.waterfall(
  407. [
  408. next => {
  409. songModel.findOne({ _id: songId }, next);
  410. },
  411. (_existingSong, next) => {
  412. existingSong = _existingSong;
  413. songModel.updateOne({ _id: songId }, song, { runValidators: true }, next);
  414. },
  415. (res, next) => {
  416. SongsModule.runJob("UPDATE_SONG", { songId }, this)
  417. .then(song => {
  418. existingSong.genres
  419. .concat(song.genres)
  420. .filter((value, index, self) => self.indexOf(value) === index)
  421. .forEach(genre => {
  422. PlaylistsModule.runJob("AUTOFILL_GENRE_PLAYLIST", { genre })
  423. .then(() => {})
  424. .catch(() => {});
  425. });
  426. next(null, song);
  427. })
  428. .catch(next);
  429. }
  430. ],
  431. async (err, song) => {
  432. if (err) {
  433. err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
  434. this.log("ERROR", "SONGS_UPDATE", `Failed to update song "${songId}". "${err}"`);
  435. return cb({ status: "failure", message: err });
  436. }
  437. this.log("SUCCESS", "SONGS_UPDATE", `Successfully updated song "${songId}".`);
  438. if (song.status === "verified") {
  439. CacheModule.runJob("PUB", {
  440. channel: "song.updatedVerifiedSong",
  441. value: song.songId
  442. });
  443. } else if (song.status === "unverified") {
  444. CacheModule.runJob("PUB", {
  445. channel: "song.updatedUnverifiedSong",
  446. value: song.songId
  447. });
  448. } else if (song.status === "hidden") {
  449. CacheModule.runJob("PUB", {
  450. channel: "song.updatedHiddenSong",
  451. value: song.songId
  452. });
  453. }
  454. return cb({
  455. status: "success",
  456. message: "Song has been successfully updated",
  457. data: song
  458. });
  459. }
  460. );
  461. }),
  462. // /**
  463. // * Removes a song
  464. // *
  465. // * @param session
  466. // * @param songId - the song id
  467. // * @param cb
  468. // */
  469. // remove: isAdminRequired(async function remove(session, songId, cb) {
  470. // const songModel = await DBModule.runJob("GET_MODEL", { modelName: "song" }, this);
  471. // let song = null;
  472. // async.waterfall(
  473. // [
  474. // next => {
  475. // songModel.findOne({ _id: songId }, next);
  476. // },
  477. // (_song, next) => {
  478. // song = _song;
  479. // songModel.deleteOne({ _id: songId }, next);
  480. // },
  481. // (res, next) => {
  482. // // TODO Check if res gets returned from above
  483. // CacheModule.runJob("HDEL", { table: "songs", key: songId }, this)
  484. // .then(() => {
  485. // next();
  486. // })
  487. // .catch(next)
  488. // .finally(() => {
  489. // song.genres.forEach(genre => {
  490. // PlaylistsModule.runJob("AUTOFILL_GENRE_PLAYLIST", { genre })
  491. // .then(() => {})
  492. // .catch(() => {});
  493. // });
  494. // });
  495. // }
  496. // ],
  497. // async err => {
  498. // if (err) {
  499. // err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
  500. // this.log("ERROR", "SONGS_REMOVE", `Failed to remove song "${songId}". "${err}"`);
  501. // return cb({ status: "failure", message: err });
  502. // }
  503. // this.log("SUCCESS", "SONGS_REMOVE", `Successfully remove song "${songId}".`);
  504. // if (song.verified) {
  505. // CacheModule.runJob("PUB", {
  506. // channel: "song.removedVerifiedSong",
  507. // value: songId
  508. // });
  509. // } else {
  510. // CacheModule.runJob("PUB", {
  511. // channel: "song.removedUnverifiedSong",
  512. // value: songId
  513. // });
  514. // }
  515. // return cb({
  516. // status: "success",
  517. // message: "Song has been successfully removed"
  518. // });
  519. // }
  520. // );
  521. // }),
  522. /**
  523. * Requests a song
  524. *
  525. * @param {object} session - the session object automatically added by the websocket
  526. * @param {string} songId - the id of the song that gets requested
  527. * @param {Function} cb - gets called with the result
  528. */
  529. request: isLoginRequired(async function add(session, songId, cb) {
  530. SongsModule.runJob("REQUEST_SONG", { songId, userId: session.userId }, this)
  531. .then(() => {
  532. this.log(
  533. "SUCCESS",
  534. "SONGS_REQUEST",
  535. `User "${session.userId}" successfully requested song "${songId}".`
  536. );
  537. return cb({
  538. status: "success",
  539. message: "Successfully requested that song"
  540. });
  541. })
  542. .catch(async err => {
  543. err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
  544. this.log(
  545. "ERROR",
  546. "SONGS_REQUEST",
  547. `Requesting song "${songId}" failed for user ${session.userId}. "${err}"`
  548. );
  549. return cb({ status: "failure", message: err });
  550. });
  551. }),
  552. /**
  553. * Hides a song
  554. *
  555. * @param {object} session - the session object automatically added by the websocket
  556. * @param {string} songId - the Musare id of the song that gets hidden
  557. * @param {Function} cb - gets called with the result
  558. */
  559. hide: isLoginRequired(async function add(session, songId, cb) {
  560. SongsModule.runJob("HIDE_SONG", { songId }, this)
  561. .then(() => {
  562. this.log("SUCCESS", "SONGS_HIDE", `User "${session.userId}" successfully hid song "${songId}".`);
  563. return cb({
  564. status: "success",
  565. message: "Successfully hid that song"
  566. });
  567. })
  568. .catch(async err => {
  569. err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
  570. this.log("ERROR", "SONGS_HIDE", `Hiding song "${songId}" failed for user ${session.userId}. "${err}"`);
  571. return cb({ status: "failure", message: err });
  572. });
  573. }),
  574. /**
  575. * Unhides a song
  576. *
  577. * @param {object} session - the session object automatically added by the websocket
  578. * @param {string} songId - the Musare id of the song that gets hidden
  579. * @param {Function} cb - gets called with the result
  580. */
  581. unhide: isLoginRequired(async function add(session, songId, cb) {
  582. SongsModule.runJob("UNHIDE_SONG", { songId }, this)
  583. .then(() => {
  584. this.log("SUCCESS", "SONGS_UNHIDE", `User "${session.userId}" successfully unhid song "${songId}".`);
  585. return cb({
  586. status: "success",
  587. message: "Successfully unhid that song"
  588. });
  589. })
  590. .catch(async err => {
  591. err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
  592. this.log(
  593. "ERROR",
  594. "SONGS_UNHIDE",
  595. `Unhiding song "${songId}" failed for user ${session.userId}. "${err}"`
  596. );
  597. return cb({ status: "failure", message: err });
  598. });
  599. }),
  600. /**
  601. * Verifies a song
  602. *
  603. * @param session
  604. * @param song - the song object
  605. * @param cb
  606. */
  607. verify: isAdminRequired(async function add(session, songId, cb) {
  608. const SongModel = await DBModule.runJob("GET_MODEL", { modelName: "song" }, this);
  609. async.waterfall(
  610. [
  611. next => {
  612. SongModel.findOne({ songId }, next);
  613. },
  614. (song, next) => {
  615. if (!song) return next("This song is not in the database.");
  616. return next(null, song);
  617. },
  618. (song, next) => {
  619. song.acceptedBy = session.userId;
  620. song.acceptedAt = Date.now();
  621. song.status = "verified";
  622. song.save(err => {
  623. next(err, song);
  624. });
  625. },
  626. (song, next) => {
  627. song.genres.forEach(genre => {
  628. PlaylistsModule.runJob("AUTOFILL_GENRE_PLAYLIST", { genre })
  629. .then(() => {})
  630. .catch(() => {});
  631. });
  632. SongsModule.runJob("UPDATE_SONG", { songId: song._id });
  633. next(null, song);
  634. }
  635. ],
  636. async (err, song) => {
  637. if (err) {
  638. err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
  639. this.log("ERROR", "SONGS_VERIFY", `User "${session.userId}" failed to verify song. "${err}"`);
  640. return cb({ status: "failure", message: err });
  641. }
  642. this.log("SUCCESS", "SONGS_VERIFY", `User "${session.userId}" successfully verified song "${songId}".`);
  643. CacheModule.runJob("PUB", {
  644. channel: "song.newVerifiedSong",
  645. value: song._id
  646. });
  647. CacheModule.runJob("PUB", {
  648. channel: "song.removedUnverifiedSong",
  649. value: song._id
  650. });
  651. return cb({
  652. status: "success",
  653. message: "Song has been verified successfully."
  654. });
  655. }
  656. );
  657. // TODO Check if video is in queue and Add the song to the appropriate stations
  658. }),
  659. /**
  660. * Un-verifies a song
  661. *
  662. * @param session
  663. * @param songId - the song id
  664. * @param cb
  665. */
  666. unverify: isAdminRequired(async function add(session, songId, cb) {
  667. const SongModel = await DBModule.runJob("GET_MODEL", { modelName: "song" }, this);
  668. async.waterfall(
  669. [
  670. next => {
  671. SongModel.findOne({ _id: songId }, next);
  672. },
  673. (song, next) => {
  674. if (!song) return next("This song is not in the database.");
  675. return next(null, song);
  676. },
  677. (song, next) => {
  678. song.status = "unverified";
  679. song.save(err => {
  680. next(err, song);
  681. });
  682. },
  683. (song, next) => {
  684. song.genres.forEach(genre => {
  685. PlaylistsModule.runJob("AUTOFILL_GENRE_PLAYLIST", { genre })
  686. .then(() => {})
  687. .catch(() => {});
  688. });
  689. SongsModule.runJob("UPDATE_SONG", { songId: song._id });
  690. next(null);
  691. }
  692. ],
  693. async err => {
  694. if (err) {
  695. err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
  696. this.log("ERROR", "SONGS_UNVERIFY", `User "${session.userId}" failed to verify song. "${err}"`);
  697. return cb({ status: "failure", message: err });
  698. }
  699. this.log(
  700. "SUCCESS",
  701. "SONGS_UNVERIFY",
  702. `User "${session.userId}" successfully unverified song "${songId}".`
  703. );
  704. CacheModule.runJob("PUB", {
  705. channel: "song.newUnverifiedSong",
  706. value: songId
  707. });
  708. CacheModule.runJob("PUB", {
  709. channel: "song.removedVerifiedSong",
  710. value: songId
  711. });
  712. return cb({
  713. status: "success",
  714. message: "Song has been unverified successfully."
  715. });
  716. }
  717. );
  718. // TODO Check if video is in queue and Add the song to the appropriate stations
  719. }),
  720. /**
  721. * Requests a set of songs
  722. *
  723. * @param {object} session - the session object automatically added by the websocket
  724. * @param {string} url - the url of the the YouTube playlist
  725. * @param {boolean} musicOnly - whether to only get music from the playlist
  726. * @param {Function} cb - gets called with the result
  727. */
  728. requestSet: isLoginRequired(function requestSet(session, url, musicOnly, cb) {
  729. async.waterfall(
  730. [
  731. next => {
  732. YouTubeModule.runJob(
  733. "GET_PLAYLIST",
  734. {
  735. url,
  736. musicOnly
  737. },
  738. this
  739. )
  740. .then(res => {
  741. next(null, res.songs);
  742. })
  743. .catch(next);
  744. },
  745. (songIds, next) => {
  746. let successful = 0;
  747. let failed = 0;
  748. let alreadyInDatabase = 0;
  749. if (songIds.length === 0) next();
  750. async.eachLimit(
  751. songIds,
  752. 1,
  753. (songId, next) => {
  754. WSModule.runJob(
  755. "RUN_ACTION2",
  756. {
  757. session,
  758. namespace: "songs",
  759. action: "request",
  760. args: [songId]
  761. },
  762. this
  763. )
  764. .then(res => {
  765. if (res.status === "success") successful += 1;
  766. else failed += 1;
  767. if (res.message === "This song is already in the database.") alreadyInDatabase += 1;
  768. })
  769. .catch(() => {
  770. failed += 1;
  771. })
  772. .finally(() => {
  773. next();
  774. });
  775. },
  776. () => {
  777. next(null, { successful, failed, alreadyInDatabase });
  778. }
  779. );
  780. }
  781. ],
  782. async (err, response) => {
  783. if (err) {
  784. err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
  785. this.log(
  786. "ERROR",
  787. "REQUEST_SET",
  788. `Importing a YouTube playlist to be requested failed for user "${session.userId}". "${err}"`
  789. );
  790. return cb({ status: "failure", message: err });
  791. }
  792. this.log(
  793. "SUCCESS",
  794. "REQUEST_SET",
  795. `Successfully imported a YouTube playlist to be requested for user "${session.userId}".`
  796. );
  797. return cb({
  798. status: "success",
  799. message: `Playlist is done importing. ${response.successful} were added succesfully, ${response.failed} failed (${response.alreadyInDatabase} were already in database)`
  800. });
  801. }
  802. );
  803. }),
  804. // /**
  805. // * Adds a song
  806. // *
  807. // * @param session
  808. // * @param song - the song object
  809. // * @param cb
  810. // */
  811. // add: isAdminRequired(async function add(session, song, cb) {
  812. // const SongModel = await DBModule.runJob("GET_MODEL", { modelName: "song" }, this);
  813. // async.waterfall(
  814. // [
  815. // next => {
  816. // SongModel.findOne({ songId: song.songId }, next);
  817. // },
  818. // (existingSong, next) => {
  819. // if (existingSong) return next("Song is already in rotation.");
  820. // return next();
  821. // },
  822. // next => {
  823. // const newSong = new SongModel(song);
  824. // newSong.acceptedBy = session.userId;
  825. // newSong.acceptedAt = Date.now();
  826. // newSong.save(next);
  827. // },
  828. // (res, next) => {
  829. // this.module
  830. // .runJob(
  831. // "RUN_ACTION2",
  832. // {
  833. // session,
  834. // namespace: "queueSongs",
  835. // action: "remove",
  836. // args: [song._id]
  837. // },
  838. // this
  839. // )
  840. // .finally(() => {
  841. // song.genres.forEach(genre => {
  842. // PlaylistsModule.runJob("AUTOFILL_GENRE_PLAYLIST", { genre })
  843. // .then(() => {})
  844. // .catch(() => {});
  845. // });
  846. // next();
  847. // });
  848. // }
  849. // ],
  850. // async err => {
  851. // if (err) {
  852. // err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
  853. // this.log("ERROR", "SONGS_ADD", `User "${session.userId}" failed to add song. "${err}"`);
  854. // return cb({ status: "failure", message: err });
  855. // }
  856. // this.log("SUCCESS", "SONGS_ADD", `User "${session.userId}" successfully added song "${song.songId}".`);
  857. // CacheModule.runJob("PUB", {
  858. // channel: "song.added",
  859. // value: song.songId
  860. // });
  861. // return cb({
  862. // status: "success",
  863. // message: "Song has been moved from the queue successfully."
  864. // });
  865. // }
  866. // );
  867. // // TODO Check if video is in queue and Add the song to the appropriate stations
  868. // }),
  869. /**
  870. * Likes a song
  871. *
  872. * @param session
  873. * @param musareSongId - the song id
  874. * @param cb
  875. */
  876. like: isLoginRequired(async function like(session, musareSongId, cb) {
  877. const userModel = await DBModule.runJob("GET_MODEL", { modelName: "user" }, this);
  878. const songModel = await DBModule.runJob("GET_MODEL", { modelName: "song" }, this);
  879. async.waterfall(
  880. [
  881. next => songModel.findOne({ songId: musareSongId }, next),
  882. (song, next) => {
  883. if (!song) return next("No song found with that id.");
  884. return next(null, song);
  885. },
  886. (song, next) => userModel.findOne({ _id: session.userId }, (err, user) => next(err, song, user)),
  887. (song, user, next) => {
  888. if (!user) return next("User does not exist.");
  889. return this.module
  890. .runJob(
  891. "RUN_ACTION2",
  892. {
  893. session,
  894. namespace: "playlists",
  895. action: "addSongToPlaylist",
  896. args: [false, musareSongId, user.likedSongsPlaylist]
  897. },
  898. this
  899. )
  900. .then(res => {
  901. if (res.status === "failure")
  902. return next("Unable to add song to the 'Liked Songs' playlist.");
  903. return next(null, song, user.dislikedSongsPlaylist);
  904. })
  905. .catch(err => next(err));
  906. },
  907. (song, dislikedSongsPlaylist, next) => {
  908. this.module
  909. .runJob(
  910. "RUN_ACTION2",
  911. {
  912. session,
  913. namespace: "playlists",
  914. action: "removeSongFromPlaylist",
  915. args: [musareSongId, dislikedSongsPlaylist]
  916. },
  917. this
  918. )
  919. .then(res => {
  920. if (res.status === "failure")
  921. return next("Unable to remove song from the 'Disliked Songs' playlist.");
  922. return next(null, song);
  923. })
  924. .catch(err => next(err));
  925. },
  926. (song, next) => {
  927. SongsModule.runJob("RECALCULATE_SONG_RATINGS", { songId: song._id, musareSongId })
  928. .then(ratings => next(null, song, ratings))
  929. .catch(err => next(err));
  930. }
  931. ],
  932. async (err, song, { likes, dislikes }) => {
  933. if (err) {
  934. err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
  935. this.log(
  936. "ERROR",
  937. "SONGS_LIKE",
  938. `User "${session.userId}" failed to like song ${musareSongId}. "${err}"`
  939. );
  940. return cb({ status: "failure", message: err });
  941. }
  942. SongsModule.runJob("UPDATE_SONG", { songId: song._id });
  943. CacheModule.runJob("PUB", {
  944. channel: "song.like",
  945. value: JSON.stringify({
  946. songId: musareSongId,
  947. userId: session.userId,
  948. likes,
  949. dislikes
  950. })
  951. });
  952. ActivitiesModule.runJob("ADD_ACTIVITY", {
  953. userId: session.userId,
  954. type: "song__like",
  955. payload: {
  956. message: `Liked song <songId>${song.title} by ${song.artists.join(", ")}</songId>`,
  957. songId: song._id,
  958. thumbnail: song.thumbnail
  959. }
  960. });
  961. return cb({
  962. status: "success",
  963. message: "You have successfully liked this song."
  964. });
  965. }
  966. );
  967. }),
  968. // TODO: ALready liked/disliked etc.
  969. /**
  970. * Dislikes a song
  971. *
  972. * @param session
  973. * @param musareSongId - the song id
  974. * @param cb
  975. */
  976. dislike: isLoginRequired(async function dislike(session, musareSongId, cb) {
  977. const userModel = await DBModule.runJob("GET_MODEL", { modelName: "user" }, this);
  978. const songModel = await DBModule.runJob("GET_MODEL", { modelName: "song" }, this);
  979. async.waterfall(
  980. [
  981. next => {
  982. songModel.findOne({ songId: musareSongId }, next);
  983. },
  984. (song, next) => {
  985. if (!song) return next("No song found with that id.");
  986. return next(null, song);
  987. },
  988. (song, next) => userModel.findOne({ _id: session.userId }, (err, user) => next(err, song, user)),
  989. (song, user, next) => {
  990. if (!user) return next("User does not exist.");
  991. return this.module
  992. .runJob(
  993. "RUN_ACTION2",
  994. {
  995. session,
  996. namespace: "playlists",
  997. action: "addSongToPlaylist",
  998. args: [false, musareSongId, user.dislikedSongsPlaylist]
  999. },
  1000. this
  1001. )
  1002. .then(res => {
  1003. if (res.status === "failure")
  1004. return next("Unable to add song to the 'Disliked Songs' playlist.");
  1005. return next(null, song, user.likedSongsPlaylist);
  1006. })
  1007. .catch(err => next(err));
  1008. },
  1009. (song, likedSongsPlaylist, next) => {
  1010. this.module
  1011. .runJob(
  1012. "RUN_ACTION2",
  1013. {
  1014. session,
  1015. namespace: "playlists",
  1016. action: "removeSongFromPlaylist",
  1017. args: [musareSongId, likedSongsPlaylist]
  1018. },
  1019. this
  1020. )
  1021. .then(res => {
  1022. if (res.status === "failure")
  1023. return next("Unable to remove song from the 'Liked Songs' playlist.");
  1024. return next(null, song);
  1025. })
  1026. .catch(err => next(err));
  1027. },
  1028. (song, next) => {
  1029. SongsModule.runJob("RECALCULATE_SONG_RATINGS", { songId: song._id, musareSongId })
  1030. .then(ratings => next(null, song, ratings))
  1031. .catch(err => next(err));
  1032. }
  1033. ],
  1034. async (err, song, { likes, dislikes }) => {
  1035. if (err) {
  1036. err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
  1037. this.log(
  1038. "ERROR",
  1039. "SONGS_DISLIKE",
  1040. `User "${session.userId}" failed to dislike song ${musareSongId}. "${err}"`
  1041. );
  1042. return cb({ status: "failure", message: err });
  1043. }
  1044. SongsModule.runJob("UPDATE_SONG", { songId: song._id });
  1045. CacheModule.runJob("PUB", {
  1046. channel: "song.dislike",
  1047. value: JSON.stringify({
  1048. songId: musareSongId,
  1049. userId: session.userId,
  1050. likes,
  1051. dislikes
  1052. })
  1053. });
  1054. ActivitiesModule.runJob("ADD_ACTIVITY", {
  1055. userId: session.userId,
  1056. type: "song__dislike",
  1057. payload: {
  1058. message: `Disliked song <songId>${song.title} by ${song.artists.join(", ")}</songId>`,
  1059. songId: song._id,
  1060. thumbnail: song.thumbnail
  1061. }
  1062. });
  1063. return cb({
  1064. status: "success",
  1065. message: "You have successfully disliked this song."
  1066. });
  1067. }
  1068. );
  1069. }),
  1070. /**
  1071. * Undislikes a song
  1072. *
  1073. * @param session
  1074. * @param musareSongId - the song id
  1075. * @param cb
  1076. */
  1077. undislike: isLoginRequired(async function undislike(session, musareSongId, cb) {
  1078. const userModel = await DBModule.runJob("GET_MODEL", { modelName: "user" }, this);
  1079. const songModel = await DBModule.runJob("GET_MODEL", { modelName: "song" }, this);
  1080. async.waterfall(
  1081. [
  1082. next => {
  1083. songModel.findOne({ songId: musareSongId }, next);
  1084. },
  1085. (song, next) => {
  1086. if (!song) return next("No song found with that id.");
  1087. return next(null, song);
  1088. },
  1089. (song, next) => userModel.findOne({ _id: session.userId }, (err, user) => next(err, song, user)),
  1090. (song, user, next) => {
  1091. if (!user) return next("User does not exist.");
  1092. return this.module
  1093. .runJob(
  1094. "RUN_ACTION2",
  1095. {
  1096. session,
  1097. namespace: "playlists",
  1098. action: "removeSongFromPlaylist",
  1099. args: [musareSongId, user.dislikedSongsPlaylist]
  1100. },
  1101. this
  1102. )
  1103. .then(res => {
  1104. if (res.status === "failure")
  1105. return next("Unable to remove song from the 'Disliked Songs' playlist.");
  1106. return next(null, song, user.likedSongsPlaylist);
  1107. })
  1108. .catch(err => next(err));
  1109. },
  1110. (song, likedSongsPlaylist, next) => {
  1111. this.module
  1112. .runJob(
  1113. "RUN_ACTION2",
  1114. {
  1115. session,
  1116. namespace: "playlists",
  1117. action: "removeSongFromPlaylist",
  1118. args: [musareSongId, likedSongsPlaylist]
  1119. },
  1120. this
  1121. )
  1122. .then(res => {
  1123. if (res.status === "failure")
  1124. return next("Unable to remove song from the 'Liked Songs' playlist.");
  1125. return next(null, song);
  1126. })
  1127. .catch(err => next(err));
  1128. },
  1129. (song, next) => {
  1130. SongsModule.runJob("RECALCULATE_SONG_RATINGS", { songId: song._id, musareSongId })
  1131. .then(ratings => next(null, song, ratings))
  1132. .catch(err => next(err));
  1133. }
  1134. ],
  1135. async (err, song, { likes, dislikes }) => {
  1136. if (err) {
  1137. err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
  1138. this.log(
  1139. "ERROR",
  1140. "SONGS_UNDISLIKE",
  1141. `User "${session.userId}" failed to undislike song ${musareSongId}. "${err}"`
  1142. );
  1143. return cb({ status: "failure", message: err });
  1144. }
  1145. SongsModule.runJob("UPDATE_SONG", { songId: song._id });
  1146. CacheModule.runJob("PUB", {
  1147. channel: "song.undislike",
  1148. value: JSON.stringify({
  1149. songId: musareSongId,
  1150. userId: session.userId,
  1151. likes,
  1152. dislikes
  1153. })
  1154. });
  1155. ActivitiesModule.runJob("ADD_ACTIVITY", {
  1156. userId: session.userId,
  1157. type: "song__undislike",
  1158. payload: {
  1159. message: `Removed <songId>${song.title} by ${song.artists.join(
  1160. ", "
  1161. )}</songId> from your Disliked Songs`,
  1162. songId: song._id,
  1163. thumbnail: song.thumbnail
  1164. }
  1165. });
  1166. return cb({
  1167. status: "success",
  1168. message: "You have successfully undisliked this song."
  1169. });
  1170. }
  1171. );
  1172. }),
  1173. /**
  1174. * Unlikes a song
  1175. *
  1176. * @param session
  1177. * @param musareSongId - the song id
  1178. * @param cb
  1179. */
  1180. unlike: isLoginRequired(async function unlike(session, musareSongId, cb) {
  1181. const userModel = await DBModule.runJob("GET_MODEL", { modelName: "user" }, this);
  1182. const songModel = await DBModule.runJob("GET_MODEL", { modelName: "song" }, this);
  1183. async.waterfall(
  1184. [
  1185. next => {
  1186. songModel.findOne({ songId: musareSongId }, next);
  1187. },
  1188. (song, next) => {
  1189. if (!song) return next("No song found with that id.");
  1190. return next(null, song);
  1191. },
  1192. (song, next) => userModel.findOne({ _id: session.userId }, (err, user) => next(err, song, user)),
  1193. (song, user, next) => {
  1194. if (!user) return next("User does not exist.");
  1195. return this.module
  1196. .runJob(
  1197. "RUN_ACTION2",
  1198. {
  1199. session,
  1200. namespace: "playlists",
  1201. action: "removeSongFromPlaylist",
  1202. args: [musareSongId, user.dislikedSongsPlaylist]
  1203. },
  1204. this
  1205. )
  1206. .then(res => {
  1207. if (res.status === "failure")
  1208. return next("Unable to remove song from the 'Disliked Songs' playlist.");
  1209. return next(null, song, user.likedSongsPlaylist);
  1210. })
  1211. .catch(err => next(err));
  1212. },
  1213. (song, likedSongsPlaylist, next) => {
  1214. this.module
  1215. .runJob(
  1216. "RUN_ACTION2",
  1217. {
  1218. session,
  1219. namespace: "playlists",
  1220. action: "removeSongFromPlaylist",
  1221. args: [musareSongId, likedSongsPlaylist]
  1222. },
  1223. this
  1224. )
  1225. .then(res => {
  1226. if (res.status === "failure")
  1227. return next("Unable to remove song from the 'Liked Songs' playlist.");
  1228. return next(null, song);
  1229. })
  1230. .catch(err => next(err));
  1231. },
  1232. (song, next) => {
  1233. SongsModule.runJob("RECALCULATE_SONG_RATINGS", { songId: song._id, musareSongId })
  1234. .then(ratings => next(null, song, ratings))
  1235. .catch(err => next(err));
  1236. }
  1237. ],
  1238. async (err, song, { likes, dislikes }) => {
  1239. if (err) {
  1240. err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
  1241. this.log(
  1242. "ERROR",
  1243. "SONGS_UNLIKE",
  1244. `User "${session.userId}" failed to unlike song ${musareSongId}. "${err}"`
  1245. );
  1246. return cb({ status: "failure", message: err });
  1247. }
  1248. SongsModule.runJob("UPDATE_SONG", { songId: song._id });
  1249. CacheModule.runJob("PUB", {
  1250. channel: "song.unlike",
  1251. value: JSON.stringify({
  1252. songId: musareSongId,
  1253. userId: session.userId,
  1254. likes,
  1255. dislikes
  1256. })
  1257. });
  1258. ActivitiesModule.runJob("ADD_ACTIVITY", {
  1259. userId: session.userId,
  1260. type: "song__unlike",
  1261. payload: {
  1262. message: `Removed <songId>${song.title} by ${song.artists.join(
  1263. ", "
  1264. )}</songId> from your Liked Songs`,
  1265. songId: song._id,
  1266. thumbnail: song.thumbnail
  1267. }
  1268. });
  1269. return cb({
  1270. status: "success",
  1271. message: "You have successfully unliked this song."
  1272. });
  1273. }
  1274. );
  1275. }),
  1276. /**
  1277. * Gets user's own song ratings
  1278. *
  1279. * @param session
  1280. * @param musareSongId - the song id
  1281. * @param cb
  1282. */
  1283. getOwnSongRatings: isLoginRequired(async function getOwnSongRatings(session, musareSongId, cb) {
  1284. const playlistModel = await DBModule.runJob("GET_MODEL", { modelName: "playlist" }, this);
  1285. const songModel = await DBModule.runJob("GET_MODEL", { modelName: "song" }, this);
  1286. async.waterfall(
  1287. [
  1288. next => {
  1289. songModel.findOne({ songId: musareSongId }, next);
  1290. },
  1291. (song, next) => {
  1292. if (!song) return next("No song found with that id.");
  1293. return next(null);
  1294. },
  1295. next =>
  1296. playlistModel.findOne(
  1297. { createdBy: session.userId, displayName: "Liked Songs" },
  1298. (err, playlist) => {
  1299. if (err) return next(err);
  1300. if (!playlist) return next("'Liked Songs' playlist does not exist.");
  1301. let isLiked = false;
  1302. Object.values(playlist.songs).forEach(song => {
  1303. // song is found in 'liked songs' playlist
  1304. if (song.songId === musareSongId) isLiked = true;
  1305. });
  1306. return next(null, isLiked);
  1307. }
  1308. ),
  1309. (isLiked, next) =>
  1310. playlistModel.findOne(
  1311. { createdBy: session.userId, displayName: "Disliked Songs" },
  1312. (err, playlist) => {
  1313. if (err) return next(err);
  1314. if (!playlist) return next("'Disliked Songs' playlist does not exist.");
  1315. const ratings = { isLiked, isDisliked: false };
  1316. Object.values(playlist.songs).forEach(song => {
  1317. // song is found in 'disliked songs' playlist
  1318. if (song.songId === musareSongId) ratings.isDisliked = true;
  1319. });
  1320. return next(null, ratings);
  1321. }
  1322. )
  1323. ],
  1324. async (err, ratings) => {
  1325. if (err) {
  1326. err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
  1327. this.log(
  1328. "ERROR",
  1329. "SONGS_GET_OWN_RATINGS",
  1330. `User "${session.userId}" failed to get ratings for ${musareSongId}. "${err}"`
  1331. );
  1332. return cb({ status: "failure", message: err });
  1333. }
  1334. const { isLiked, isDisliked } = ratings;
  1335. return cb({
  1336. status: "success",
  1337. songId: musareSongId,
  1338. liked: isLiked,
  1339. disliked: isDisliked
  1340. });
  1341. }
  1342. );
  1343. })
  1344. };