songs.js 38 KB

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