stations.js 65 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660
  1. import async from "async";
  2. import mongoose from "mongoose";
  3. import { isLoginRequired, isOwnerRequired } from "./hooks";
  4. import moduleManager from "../../index";
  5. const DBModule = moduleManager.modules.db;
  6. const UtilsModule = moduleManager.modules.utils;
  7. const IOModule = moduleManager.modules.io;
  8. const SongsModule = moduleManager.modules.songs;
  9. const CacheModule = moduleManager.modules.cache;
  10. const NotificationsModule = moduleManager.modules.notifications;
  11. const StationsModule = moduleManager.modules.stations;
  12. const ActivitiesModule = moduleManager.modules.activities;
  13. const YouTubeModule = moduleManager.modules.youtube;
  14. CacheModule.runJob("SUB", {
  15. channel: "station.updateUsers",
  16. cb: ({ stationId, usersPerStation }) => {
  17. IOModule.runJob("EMIT_TO_ROOM", {
  18. room: `station.${stationId}`,
  19. args: ["event:users.updated", usersPerStation]
  20. });
  21. }
  22. });
  23. CacheModule.runJob("SUB", {
  24. channel: "station.updateUserCount",
  25. cb: ({ stationId, usersPerStationCount }) => {
  26. const count = usersPerStationCount || 0;
  27. IOModule.runJob("EMIT_TO_ROOM", {
  28. room: `station.${stationId}`,
  29. args: ["event:userCount.updated", count]
  30. });
  31. StationsModule.runJob("GET_STATION", { stationId }).then(async station => {
  32. if (station.privacy === "public")
  33. IOModule.runJob("EMIT_TO_ROOM", {
  34. room: "home",
  35. args: ["event:userCount.updated", stationId, count]
  36. });
  37. else {
  38. const sockets = await IOModule.runJob("GET_ROOM_SOCKETS", {
  39. room: "home"
  40. });
  41. Object.keys(sockets).forEach(socketKey => {
  42. const socket = sockets[socketKey];
  43. const { session } = socket;
  44. if (session.sessionId) {
  45. CacheModule.runJob("HGET", {
  46. table: "sessions",
  47. key: session.sessionId
  48. }).then(session => {
  49. if (session)
  50. DBModule.runJob("GET_MODEL", {
  51. modelName: "user"
  52. }).then(userModel =>
  53. userModel.findOne({ _id: session.userId }, (err, user) => {
  54. if (user.role === "admin")
  55. socket.emit("event:userCount.updated", stationId, count);
  56. else if (station.type === "community" && station.owner === session.userId)
  57. socket.emit("event:userCount.updated", stationId, count);
  58. })
  59. );
  60. });
  61. }
  62. });
  63. }
  64. });
  65. }
  66. });
  67. CacheModule.runJob("SUB", {
  68. channel: "station.updateTheme",
  69. cb: data => {
  70. IOModule.runJob("EMIT_TO_ROOM", {
  71. room: `station.${data.stationId}`,
  72. args: ["event:theme.updated", data.theme]
  73. });
  74. }
  75. });
  76. CacheModule.runJob("SUB", {
  77. channel: "station.queueLockToggled",
  78. cb: data => {
  79. IOModule.runJob("EMIT_TO_ROOM", {
  80. room: `station.${data.stationId}`,
  81. args: ["event:queueLockToggled", data.locked]
  82. });
  83. }
  84. });
  85. CacheModule.runJob("SUB", {
  86. channel: "station.updatePartyMode",
  87. cb: data => {
  88. IOModule.runJob("EMIT_TO_ROOM", {
  89. room: `station.${data.stationId}`,
  90. args: ["event:partyMode.updated", data.partyMode]
  91. });
  92. }
  93. });
  94. CacheModule.runJob("SUB", {
  95. channel: "privatePlaylist.selected",
  96. cb: data => {
  97. IOModule.runJob("EMIT_TO_ROOM", {
  98. room: `station.${data.stationId}`,
  99. args: ["event:privatePlaylist.selected", data.playlistId]
  100. });
  101. }
  102. });
  103. CacheModule.runJob("SUB", {
  104. channel: "privatePlaylist.deselected",
  105. cb: data => {
  106. IOModule.runJob("EMIT_TO_ROOM", {
  107. room: `station.${data.stationId}`,
  108. args: ["event:privatePlaylist.deselected"]
  109. });
  110. }
  111. });
  112. CacheModule.runJob("SUB", {
  113. channel: "station.pause",
  114. cb: stationId => {
  115. StationsModule.runJob("GET_STATION", { stationId }).then(station => {
  116. IOModule.runJob("EMIT_TO_ROOM", {
  117. room: `station.${stationId}`,
  118. args: ["event:stations.pause", { pausedAt: station.pausedAt }]
  119. });
  120. StationsModule.runJob("GET_SOCKETS_THAT_CAN_KNOW_ABOUT_STATION", {
  121. room: `home`,
  122. station
  123. }).then(response => {
  124. const { socketsThatCan } = response;
  125. socketsThatCan.forEach(socket => {
  126. socket.emit("event:station.pause", { stationId });
  127. });
  128. });
  129. });
  130. }
  131. });
  132. CacheModule.runJob("SUB", {
  133. channel: "station.resume",
  134. cb: stationId => {
  135. StationsModule.runJob("GET_STATION", { stationId }).then(station => {
  136. IOModule.runJob("EMIT_TO_ROOM", {
  137. room: `station.${stationId}`,
  138. args: ["event:stations.resume", { timePaused: station.timePaused }]
  139. });
  140. StationsModule.runJob("GET_SOCKETS_THAT_CAN_KNOW_ABOUT_STATION", {
  141. room: `home`,
  142. station
  143. })
  144. .then(response => {
  145. const { socketsThatCan } = response;
  146. socketsThatCan.forEach(socket => {
  147. socket.emit("event:station.resume", { stationId });
  148. });
  149. })
  150. .catch(console.log);
  151. });
  152. }
  153. });
  154. CacheModule.runJob("SUB", {
  155. channel: "station.privacyUpdate",
  156. cb: response => {
  157. const { stationId, previousPrivacy } = response;
  158. StationsModule.runJob("GET_STATION", { stationId }).then(station => {
  159. if (previousPrivacy !== station.privacy) {
  160. if (station.privacy === "public") {
  161. // Station became public
  162. IOModule.runJob("EMIT_TO_ROOM", {
  163. room: "home",
  164. args: ["event:stations.created", station]
  165. });
  166. } else if (previousPrivacy === "public") {
  167. // Station became hidden
  168. StationsModule.runJob("GET_SOCKETS_THAT_CAN_KNOW_ABOUT_STATION", {
  169. room: `home`,
  170. station
  171. }).then(response => {
  172. const { socketsThatCan, socketsThatCannot } = response;
  173. socketsThatCan.forEach(socket => {
  174. socket.emit("event:station.updatePrivacy", { stationId, privacy: station.privacy });
  175. });
  176. socketsThatCannot.forEach(socket => {
  177. socket.emit("event:station.removed", { stationId });
  178. });
  179. });
  180. } else {
  181. // Station was hidden and is still hidden
  182. StationsModule.runJob("GET_SOCKETS_THAT_CAN_KNOW_ABOUT_STATION", {
  183. room: `home`,
  184. station
  185. }).then(response => {
  186. const { socketsThatCan } = response;
  187. socketsThatCan.forEach(socket => {
  188. socket.emit("event:station.updatePrivacy", { stationId, privacy: station.privacy });
  189. });
  190. });
  191. }
  192. }
  193. });
  194. }
  195. });
  196. CacheModule.runJob("SUB", {
  197. channel: "station.nameUpdate",
  198. cb: response => {
  199. const { stationId } = response;
  200. StationsModule.runJob("GET_STATION", { stationId }).then(station => {
  201. StationsModule.runJob("GET_SOCKETS_THAT_CAN_KNOW_ABOUT_STATION", {
  202. room: `home`,
  203. station
  204. }).then(response => {
  205. const { socketsThatCan } = response;
  206. socketsThatCan.forEach(socket => {
  207. socket.emit("event:station.updateName", { stationId, name: station.name });
  208. });
  209. });
  210. });
  211. }
  212. });
  213. CacheModule.runJob("SUB", {
  214. channel: "station.displayNameUpdate",
  215. cb: response => {
  216. const { stationId } = response;
  217. StationsModule.runJob("GET_STATION", { stationId }).then(station => {
  218. StationsModule.runJob("GET_SOCKETS_THAT_CAN_KNOW_ABOUT_STATION", {
  219. room: `home`,
  220. station
  221. }).then(response => {
  222. const { socketsThatCan } = response;
  223. socketsThatCan.forEach(socket => {
  224. socket.emit("event:station.updateDisplayName", { stationId, displayName: station.displayName });
  225. });
  226. });
  227. });
  228. }
  229. });
  230. CacheModule.runJob("SUB", {
  231. channel: "station.descriptionUpdate",
  232. cb: response => {
  233. const { stationId } = response;
  234. StationsModule.runJob("GET_STATION", { stationId }).then(station => {
  235. StationsModule.runJob("GET_SOCKETS_THAT_CAN_KNOW_ABOUT_STATION", {
  236. room: `home`,
  237. station
  238. }).then(response => {
  239. const { socketsThatCan } = response;
  240. socketsThatCan.forEach(socket => {
  241. socket.emit("event:station.updateDescription", { stationId, description: station.description });
  242. });
  243. });
  244. });
  245. }
  246. });
  247. CacheModule.runJob("SUB", {
  248. channel: "station.themeUpdate",
  249. cb: response => {
  250. const { stationId } = response;
  251. StationsModule.runJob("GET_STATION", { stationId }).then(station => {
  252. IOModule.runJob("EMIT_TO_ROOM", {
  253. room: `station.${stationId}`,
  254. args: ["event:theme.updated", station.theme]
  255. });
  256. StationsModule.runJob("GET_SOCKETS_THAT_CAN_KNOW_ABOUT_STATION", {
  257. room: `home`,
  258. station
  259. }).then(response => {
  260. const { socketsThatCan } = response;
  261. socketsThatCan.forEach(socket => {
  262. socket.emit("event:station.updateTheme", { stationId, theme: station.theme });
  263. });
  264. });
  265. });
  266. }
  267. });
  268. CacheModule.runJob("SUB", {
  269. channel: "station.queueUpdate",
  270. cb: stationId => {
  271. StationsModule.runJob("GET_STATION", { stationId }).then(station => {
  272. IOModule.runJob("EMIT_TO_ROOM", {
  273. room: `station.${stationId}`,
  274. args: ["event:queue.update", station.queue]
  275. });
  276. });
  277. }
  278. });
  279. CacheModule.runJob("SUB", {
  280. channel: "station.voteSkipSong",
  281. cb: stationId => {
  282. IOModule.runJob("EMIT_TO_ROOM", {
  283. room: `station.${stationId}`,
  284. args: ["event:song.voteSkipSong"]
  285. });
  286. }
  287. });
  288. CacheModule.runJob("SUB", {
  289. channel: "station.remove",
  290. cb: stationId => {
  291. IOModule.runJob("EMIT_TO_ROOM", {
  292. room: `station.${stationId}`,
  293. args: ["event:stations.remove"]
  294. });
  295. console.log(111, "REMOVED");
  296. IOModule.runJob("EMIT_TO_ROOM", {
  297. room: `home`,
  298. args: ["event:station.removed", { stationId }]
  299. });
  300. IOModule.runJob("EMIT_TO_ROOM", {
  301. room: "admin.stations",
  302. args: ["event:admin.station.removed", stationId]
  303. });
  304. }
  305. });
  306. CacheModule.runJob("SUB", {
  307. channel: "station.create",
  308. cb: async stationId => {
  309. const userModel = await DBModule.runJob("GET_MODEL", { modelName: "user" });
  310. StationsModule.runJob("INITIALIZE_STATION", { stationId }).then(async response => {
  311. const { station } = response;
  312. station.userCount = StationsModule.usersPerStationCount[stationId] || 0;
  313. IOModule.runJob("EMIT_TO_ROOM", {
  314. room: "admin.stations",
  315. args: ["event:admin.station.added", station]
  316. });
  317. // TODO If community, check if on whitelist
  318. if (station.privacy === "public")
  319. IOModule.runJob("EMIT_TO_ROOM", {
  320. room: "home",
  321. args: ["event:stations.created", station]
  322. });
  323. else {
  324. const sockets = await IOModule.runJob("GET_ROOM_SOCKETS", {
  325. room: "home"
  326. });
  327. Object.keys(sockets).forEach(socketKey => {
  328. const socket = sockets[socketKey];
  329. const { session } = socket;
  330. if (session.sessionId) {
  331. CacheModule.runJob("HGET", {
  332. table: "sessions",
  333. key: session.sessionId
  334. }).then(session => {
  335. if (session) {
  336. userModel.findOne({ _id: session.userId }, (err, user) => {
  337. if (user.role === "admin") socket.emit("event:stations.created", station);
  338. else if (station.type === "community" && station.owner === session.userId)
  339. socket.emit("event:stations.created", station);
  340. });
  341. }
  342. });
  343. }
  344. });
  345. }
  346. });
  347. }
  348. });
  349. export default {
  350. /**
  351. * Get a list of all the stations
  352. *
  353. * @param {object} session - user session
  354. * @param {Function} cb - callback
  355. */
  356. index(session, cb) {
  357. async.waterfall(
  358. [
  359. next => {
  360. CacheModule.runJob("HGETALL", { table: "stations" }, this).then(stations => {
  361. next(null, stations);
  362. });
  363. },
  364. (items, next) => {
  365. const filteredStations = [];
  366. async.each(
  367. items,
  368. (station, nextStation) => {
  369. async.waterfall(
  370. [
  371. callback => {
  372. // only relevant if user logged in
  373. if (session.userId) {
  374. return StationsModule.runJob(
  375. "HAS_USER_FAVORITED_STATION",
  376. {
  377. userId: session.userId,
  378. stationId: station._id
  379. },
  380. this
  381. )
  382. .then(isStationFavorited => {
  383. station.isFavorited = isStationFavorited;
  384. return callback();
  385. })
  386. .catch(err => callback(err));
  387. }
  388. return callback();
  389. },
  390. callback => {
  391. StationsModule.runJob(
  392. "CAN_USER_VIEW_STATION",
  393. {
  394. station,
  395. userId: session.userId,
  396. hideUnlisted: true
  397. },
  398. this
  399. )
  400. .then(exists => callback(null, exists))
  401. .catch(callback);
  402. }
  403. ],
  404. (err, exists) => {
  405. if (err) return this.log("ERROR", "STATIONS_INDEX", err);
  406. station.userCount = StationsModule.usersPerStationCount[station._id] || 0;
  407. if (exists) filteredStations.push(station);
  408. return nextStation();
  409. }
  410. );
  411. },
  412. () => next(null, filteredStations)
  413. );
  414. }
  415. ],
  416. async (err, stations) => {
  417. if (err) {
  418. err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
  419. this.log("ERROR", "STATIONS_INDEX", `Indexing stations failed. "${err}"`);
  420. return cb({ status: "failure", message: err });
  421. }
  422. this.log("SUCCESS", "STATIONS_INDEX", `Indexing stations successful.`, false);
  423. return cb({ status: "success", stations });
  424. }
  425. );
  426. },
  427. /**
  428. * Obtains basic metadata of a station in order to format an activity
  429. *
  430. * @param {object} session - user session
  431. * @param {string} stationId - the station id
  432. * @param {Function} cb - callback
  433. */
  434. getStationForActivity(session, stationId, cb) {
  435. async.waterfall(
  436. [
  437. next => {
  438. StationsModule.runJob("GET_STATION", { stationId }, this)
  439. .then(station => {
  440. next(null, station);
  441. })
  442. .catch(next);
  443. }
  444. ],
  445. async (err, station) => {
  446. if (err) {
  447. err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
  448. this.log(
  449. "ERROR",
  450. "STATIONS_GET_STATION_FOR_ACTIVITY",
  451. `Failed to obtain metadata of station ${stationId} for activity formatting. "${err}"`
  452. );
  453. return cb({ status: "failure", message: err });
  454. }
  455. this.log(
  456. "SUCCESS",
  457. "STATIONS_GET_STATION_FOR_ACTIVITY",
  458. `Obtained metadata of station ${stationId} for activity formatting successfully.`
  459. );
  460. return cb({
  461. status: "success",
  462. data: {
  463. title: station.displayName,
  464. thumbnail: station.currentSong ? station.currentSong.thumbnail : ""
  465. }
  466. });
  467. }
  468. );
  469. },
  470. /**
  471. * Verifies that a station exists
  472. *
  473. * @param {object} session - user session
  474. * @param {string} stationName - the station name
  475. * @param {Function} cb - callback
  476. */
  477. existsByName(session, stationName, cb) {
  478. async.waterfall(
  479. [
  480. next => {
  481. StationsModule.runJob("GET_STATION_BY_NAME", { stationName }, this)
  482. .then(station => {
  483. next(null, station);
  484. })
  485. .catch(next);
  486. },
  487. (station, next) => {
  488. if (!station) return next(null, false);
  489. return StationsModule.runJob(
  490. "CAN_USER_VIEW_STATION",
  491. {
  492. station,
  493. userId: session.userId
  494. },
  495. this
  496. )
  497. .then(exists => {
  498. next(null, exists);
  499. })
  500. .catch(next);
  501. }
  502. ],
  503. async (err, exists) => {
  504. if (err) {
  505. err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
  506. this.log(
  507. "ERROR",
  508. "STATION_EXISTS_BY_NAME",
  509. `Checking if station "${stationName}" exists failed. "${err}"`
  510. );
  511. return cb({ status: "failure", message: err });
  512. }
  513. this.log(
  514. "SUCCESS",
  515. "STATION_EXISTS_BY_NAME",
  516. `Station "${stationName}" exists successfully.` /* , false */
  517. );
  518. return cb({ status: "success", exists });
  519. }
  520. );
  521. },
  522. /**
  523. * Gets the official playlist for a station
  524. *
  525. * @param {object} session - user session
  526. * @param {string} stationId - the station id
  527. * @param {Function} cb - callback
  528. */
  529. getPlaylist(session, stationId, cb) {
  530. async.waterfall(
  531. [
  532. next => {
  533. StationsModule.runJob("GET_STATION", { stationId }, this)
  534. .then(station => {
  535. next(null, station);
  536. })
  537. .catch(next);
  538. },
  539. (station, next) => {
  540. StationsModule.runJob(
  541. "CAN_USER_VIEW_STATION",
  542. {
  543. station,
  544. userId: session.userId
  545. },
  546. this
  547. )
  548. .then(canView => {
  549. if (canView) return next(null, station);
  550. return next("Insufficient permissions.");
  551. })
  552. .catch(err => next(err));
  553. },
  554. (station, next) => {
  555. if (!station) return next("Station not found.");
  556. if (station.type !== "official") return next("This is not an official station.");
  557. return next();
  558. },
  559. next => {
  560. CacheModule.runJob(
  561. "HGET",
  562. {
  563. table: "officialPlaylists",
  564. key: stationId
  565. },
  566. this
  567. )
  568. .then(playlist => {
  569. next(null, playlist);
  570. })
  571. .catch(next);
  572. },
  573. (playlist, next) => {
  574. if (!playlist) return next("Playlist not found.");
  575. return next(null, playlist);
  576. }
  577. ],
  578. async (err, playlist) => {
  579. if (err) {
  580. err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
  581. this.log(
  582. "ERROR",
  583. "STATIONS_GET_PLAYLIST",
  584. `Getting playlist for station "${stationId}" failed. "${err}"`
  585. );
  586. return cb({ status: "failure", message: err });
  587. }
  588. this.log(
  589. "SUCCESS",
  590. "STATIONS_GET_PLAYLIST",
  591. `Got playlist for station "${stationId}" successfully.`,
  592. false
  593. );
  594. return cb({ status: "success", data: playlist.songs });
  595. }
  596. );
  597. },
  598. /**
  599. * Joins the station by its name
  600. *
  601. * @param {object} session - user session
  602. * @param {string} stationName - the station name
  603. * @param {Function} cb - callback
  604. */
  605. join(session, stationName, cb) {
  606. async.waterfall(
  607. [
  608. next => {
  609. StationsModule.runJob("GET_STATION_BY_NAME", { stationName }, this)
  610. .then(station => {
  611. next(null, station);
  612. })
  613. .catch(next);
  614. },
  615. (station, next) => {
  616. if (!station) return next("Station not found.");
  617. return StationsModule.runJob(
  618. "CAN_USER_VIEW_STATION",
  619. {
  620. station,
  621. userId: session.userId
  622. },
  623. this
  624. )
  625. .then(canView => {
  626. if (!canView) next("Not allowed to join station.");
  627. else next(null, station);
  628. })
  629. .catch(err => next(err));
  630. },
  631. (station, next) => {
  632. IOModule.runJob("SOCKET_JOIN_ROOM", {
  633. socketId: session.socketId,
  634. room: `station.${station._id}`
  635. });
  636. const data = {
  637. _id: station._id,
  638. type: station.type,
  639. currentSong: station.currentSong,
  640. startedAt: station.startedAt,
  641. paused: station.paused,
  642. timePaused: station.timePaused,
  643. pausedAt: station.pausedAt,
  644. description: station.description,
  645. displayName: station.displayName,
  646. privacy: station.privacy,
  647. locked: station.locked,
  648. partyMode: station.partyMode,
  649. owner: station.owner,
  650. privatePlaylist: station.privatePlaylist,
  651. genres: station.genres,
  652. blacklistedGenres: station.blacklistedGenres,
  653. theme: station.theme
  654. };
  655. StationsModule.userList[session.socketId] = station._id;
  656. next(null, data);
  657. },
  658. (data, next) => {
  659. data = JSON.parse(JSON.stringify(data));
  660. data.userCount = StationsModule.usersPerStationCount[data._id] || 0;
  661. data.users = StationsModule.usersPerStation[data._id] || [];
  662. if (!data.currentSong || !data.currentSong.title) return next(null, data);
  663. IOModule.runJob("SOCKET_JOIN_SONG_ROOM", {
  664. socketId: session.socketId,
  665. room: `song.${data.currentSong.songId}`
  666. });
  667. data.currentSong.skipVotes = data.currentSong.skipVotes.length;
  668. return SongsModule.runJob(
  669. "GET_SONG_FROM_ID",
  670. {
  671. songId: data.currentSong.songId
  672. },
  673. this
  674. )
  675. .then(response => {
  676. const { song } = response;
  677. if (song) {
  678. data.currentSong.likes = song.likes;
  679. data.currentSong.dislikes = song.dislikes;
  680. } else {
  681. data.currentSong.likes = -1;
  682. data.currentSong.dislikes = -1;
  683. }
  684. })
  685. .catch(() => {
  686. data.currentSong.likes = -1;
  687. data.currentSong.dislikes = -1;
  688. })
  689. .finally(() => next(null, data));
  690. },
  691. (data, next) => {
  692. // only relevant if user logged in
  693. if (session.userId) {
  694. return StationsModule.runJob(
  695. "HAS_USER_FAVORITED_STATION",
  696. {
  697. userId: session.userId,
  698. stationId: data._id
  699. },
  700. this
  701. )
  702. .then(isStationFavorited => {
  703. data.isFavorited = isStationFavorited;
  704. return next(null, data);
  705. })
  706. .catch(err => next(err));
  707. }
  708. return next(null, data);
  709. }
  710. ],
  711. async (err, data) => {
  712. if (err) {
  713. err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
  714. this.log("ERROR", "STATIONS_JOIN", `Joining station "${stationName}" failed. "${err}"`);
  715. return cb({ status: "failure", message: err });
  716. }
  717. this.log("SUCCESS", "STATIONS_JOIN", `Joined station "${data._id}" successfully.`);
  718. return cb({ status: "success", data });
  719. }
  720. );
  721. },
  722. /**
  723. * Gets a station by id
  724. *
  725. * @param {object} session - user session
  726. * @param {string} stationId - the station id
  727. * @param {Function} cb - callback
  728. */
  729. getStationById(session, stationId, cb) {
  730. async.waterfall(
  731. [
  732. next => {
  733. StationsModule.runJob("GET_STATION", { stationId }, this)
  734. .then(station => {
  735. next(null, station);
  736. })
  737. .catch(next);
  738. },
  739. (station, next) => {
  740. if (!station) return next("Station not found.");
  741. return StationsModule.runJob(
  742. "CAN_USER_VIEW_STATION",
  743. {
  744. station,
  745. userId: session.userId
  746. },
  747. this
  748. )
  749. .then(canView => {
  750. if (!canView) next("Not allowed to get station.");
  751. else next(null, station);
  752. })
  753. .catch(err => next(err));
  754. },
  755. (station, next) => {
  756. const data = {
  757. _id: station._id,
  758. type: station.type,
  759. description: station.description,
  760. displayName: station.displayName,
  761. name: station.name,
  762. privacy: station.privacy,
  763. locked: station.locked,
  764. partyMode: station.partyMode,
  765. owner: station.owner,
  766. privatePlaylist: station.privatePlaylist,
  767. genres: station.genres,
  768. blacklistedGenres: station.blacklistedGenres,
  769. theme: station.theme
  770. };
  771. next(null, data);
  772. }
  773. ],
  774. async (err, data) => {
  775. if (err) {
  776. err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
  777. this.log("ERROR", "GET_STATION_BY_ID", `Getting station "${stationId}" failed. "${err}"`);
  778. return cb({ status: "failure", message: err });
  779. }
  780. this.log("SUCCESS", "GET_STATION_BY_ID", `Got station "${stationId}" successfully.`);
  781. return cb({ status: "success", station: data });
  782. }
  783. );
  784. },
  785. /**
  786. * Toggles if a station is locked
  787. *
  788. * @param session
  789. * @param stationId - the station id
  790. * @param cb
  791. */
  792. toggleLock: isOwnerRequired(async function toggleLock(session, stationId, cb) {
  793. const stationModel = await DBModule.runJob(
  794. "GET_MODEL",
  795. {
  796. modelName: "station"
  797. },
  798. this
  799. );
  800. async.waterfall(
  801. [
  802. next => {
  803. StationsModule.runJob("GET_STATION", { stationId }, this)
  804. .then(station => {
  805. next(null, station);
  806. })
  807. .catch(next);
  808. },
  809. (station, next) => {
  810. stationModel.updateOne({ _id: stationId }, { $set: { locked: !station.locked } }, next);
  811. },
  812. (res, next) => {
  813. StationsModule.runJob("UPDATE_STATION", { stationId }, this)
  814. .then(station => {
  815. next(null, station);
  816. })
  817. .catch(next);
  818. }
  819. ],
  820. async (err, station) => {
  821. if (err) {
  822. err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
  823. this.log(
  824. "ERROR",
  825. "STATIONS_UPDATE_LOCKED_STATUS",
  826. `Toggling the queue lock for station "${stationId}" failed. "${err}"`
  827. );
  828. return cb({ status: "failure", message: err });
  829. }
  830. this.log(
  831. "SUCCESS",
  832. "STATIONS_UPDATE_LOCKED_STATUS",
  833. `Toggled the queue lock for station "${stationId}" successfully to "${station.locked}".`
  834. );
  835. CacheModule.runJob("PUB", {
  836. channel: "station.queueLockToggled",
  837. value: {
  838. stationId,
  839. locked: station.locked
  840. }
  841. });
  842. return cb({ status: "success", data: station.locked });
  843. }
  844. );
  845. }),
  846. /**
  847. * Votes to skip a station
  848. *
  849. * @param session
  850. * @param stationId - the station id
  851. * @param cb
  852. */
  853. voteSkip: isLoginRequired(async function voteSkip(session, stationId, cb) {
  854. const stationModel = await DBModule.runJob(
  855. "GET_MODEL",
  856. {
  857. modelName: "station"
  858. },
  859. this
  860. );
  861. let skipVotes = 0;
  862. let shouldSkip = false;
  863. async.waterfall(
  864. [
  865. next => {
  866. StationsModule.runJob("GET_STATION", { stationId }, this)
  867. .then(station => {
  868. next(null, station);
  869. })
  870. .catch(next);
  871. },
  872. (station, next) => {
  873. if (!station) return next("Station not found.");
  874. return StationsModule.runJob(
  875. "CAN_USER_VIEW_STATION",
  876. {
  877. station,
  878. userId: session.userId
  879. },
  880. this
  881. )
  882. .then(canView => {
  883. if (canView) return next(null, station);
  884. return next("Insufficient permissions.");
  885. })
  886. .catch(err => next(err));
  887. },
  888. (station, next) => {
  889. if (!station.currentSong) return next("There is currently no song to skip.");
  890. if (station.currentSong.skipVotes.indexOf(session.userId) !== -1)
  891. return next("You have already voted to skip this song.");
  892. return next(null, station);
  893. },
  894. (station, next) => {
  895. stationModel.updateOne(
  896. { _id: stationId },
  897. { $push: { "currentSong.skipVotes": session.userId } },
  898. next
  899. );
  900. },
  901. (res, next) => {
  902. StationsModule.runJob("UPDATE_STATION", { stationId }, this)
  903. .then(station => {
  904. next(null, station);
  905. })
  906. .catch(next);
  907. },
  908. (station, next) => {
  909. if (!station) return next("Station not found.");
  910. return next(null, station);
  911. },
  912. (station, next) => {
  913. skipVotes = station.currentSong.skipVotes.length;
  914. IOModule.runJob(
  915. "GET_ROOM_SOCKETS",
  916. {
  917. room: `station.${stationId}`
  918. },
  919. this
  920. )
  921. .then(sockets => {
  922. next(null, sockets);
  923. })
  924. .catch(next);
  925. },
  926. (sockets, next) => {
  927. if (sockets.length <= skipVotes) shouldSkip = true;
  928. next();
  929. }
  930. ],
  931. async err => {
  932. if (err) {
  933. err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
  934. this.log("ERROR", "STATIONS_VOTE_SKIP", `Vote skipping station "${stationId}" failed. "${err}"`);
  935. return cb({ status: "failure", message: err });
  936. }
  937. this.log("SUCCESS", "STATIONS_VOTE_SKIP", `Vote skipping "${stationId}" successful.`);
  938. CacheModule.runJob("PUB", {
  939. channel: "station.voteSkipSong",
  940. value: stationId
  941. });
  942. if (shouldSkip) StationsModule.runJob("SKIP_STATION", { stationId });
  943. return cb({
  944. status: "success",
  945. message: "Successfully voted to skip the song."
  946. });
  947. }
  948. );
  949. }),
  950. /**
  951. * Force skips a station
  952. *
  953. * @param session
  954. * @param stationId - the station id
  955. * @param cb
  956. */
  957. forceSkip: isOwnerRequired(function forceSkip(session, stationId, cb) {
  958. async.waterfall(
  959. [
  960. next => {
  961. StationsModule.runJob("GET_STATION", { stationId }, this)
  962. .then(station => {
  963. next(null, station);
  964. })
  965. .catch(next);
  966. },
  967. (station, next) => {
  968. if (!station) return next("Station not found.");
  969. return next();
  970. }
  971. ],
  972. async err => {
  973. if (err) {
  974. err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
  975. this.log("ERROR", "STATIONS_FORCE_SKIP", `Force skipping station "${stationId}" failed. "${err}"`);
  976. return cb({ status: "failure", message: err });
  977. }
  978. StationsModule.runJob("SKIP_STATION", { stationId });
  979. this.log("SUCCESS", "STATIONS_FORCE_SKIP", `Force skipped station "${stationId}" successfully.`);
  980. return cb({
  981. status: "success",
  982. message: "Successfully skipped station."
  983. });
  984. }
  985. );
  986. }),
  987. /**
  988. * Leaves the user's current station
  989. *
  990. * @param {object} session - user session
  991. * @param {string} stationId - id of station to leave
  992. * @param {Function} cb - callback
  993. */
  994. leave(session, stationId, cb) {
  995. async.waterfall(
  996. [
  997. next => {
  998. StationsModule.runJob("GET_STATION", { stationId }, this)
  999. .then(station => {
  1000. next(null, station);
  1001. })
  1002. .catch(next);
  1003. },
  1004. (station, next) => {
  1005. if (!station) return next("Station not found.");
  1006. return next();
  1007. }
  1008. ],
  1009. async (err, userCount) => {
  1010. if (err) {
  1011. err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
  1012. this.log("ERROR", "STATIONS_LEAVE", `Leaving station "${stationId}" failed. "${err}"`);
  1013. return cb({ status: "failure", message: err });
  1014. }
  1015. this.log("SUCCESS", "STATIONS_LEAVE", `Left station "${stationId}" successfully.`);
  1016. IOModule.runJob("SOCKET_LEAVE_ROOMS", { socketId: session });
  1017. delete StationsModule.userList[session.socketId];
  1018. return cb({
  1019. status: "success",
  1020. message: "Successfully left station.",
  1021. userCount
  1022. });
  1023. }
  1024. );
  1025. },
  1026. /**
  1027. * Updates a station's name
  1028. *
  1029. * @param session
  1030. * @param stationId - the station id
  1031. * @param newName - the new station name
  1032. * @param cb
  1033. */
  1034. updateName: isOwnerRequired(async function updateName(session, stationId, newName, cb) {
  1035. const stationModel = await DBModule.runJob(
  1036. "GET_MODEL",
  1037. {
  1038. modelName: "station"
  1039. },
  1040. this
  1041. );
  1042. async.waterfall(
  1043. [
  1044. next => {
  1045. stationModel.updateOne(
  1046. { _id: stationId },
  1047. { $set: { name: newName } },
  1048. { runValidators: true },
  1049. next
  1050. );
  1051. },
  1052. (res, next) => {
  1053. StationsModule.runJob("UPDATE_STATION", { stationId }, this)
  1054. .then(station => {
  1055. next(null, station);
  1056. })
  1057. .catch(next);
  1058. }
  1059. ],
  1060. async err => {
  1061. if (err) {
  1062. err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
  1063. this.log(
  1064. "ERROR",
  1065. "STATIONS_UPDATE_NAME",
  1066. `Updating station "${stationId}" name to "${newName}" failed. "${err}"`
  1067. );
  1068. return cb({ status: "failure", message: err });
  1069. }
  1070. this.log(
  1071. "SUCCESS",
  1072. "STATIONS_UPDATE_NAME",
  1073. `Updated station "${stationId}" name to "${newName}" successfully.`
  1074. );
  1075. CacheModule.runJob("PUB", {
  1076. channel: "station.nameUpdate",
  1077. value: { stationId }
  1078. });
  1079. return cb({
  1080. status: "success",
  1081. message: "Successfully updated the name."
  1082. });
  1083. }
  1084. );
  1085. }),
  1086. /**
  1087. * Updates a station's display name
  1088. *
  1089. * @param session
  1090. * @param stationId - the station id
  1091. * @param newDisplayName - the new station display name
  1092. * @param cb
  1093. */
  1094. updateDisplayName: isOwnerRequired(async function updateDisplayName(session, stationId, newDisplayName, cb) {
  1095. const stationModel = await DBModule.runJob(
  1096. "GET_MODEL",
  1097. {
  1098. modelName: "station"
  1099. },
  1100. this
  1101. );
  1102. async.waterfall(
  1103. [
  1104. next => {
  1105. stationModel.updateOne(
  1106. { _id: stationId },
  1107. { $set: { displayName: newDisplayName } },
  1108. { runValidators: true },
  1109. next
  1110. );
  1111. },
  1112. (res, next) => {
  1113. StationsModule.runJob("UPDATE_STATION", { stationId }, this)
  1114. .then(station => {
  1115. next(null, station);
  1116. })
  1117. .catch(next);
  1118. }
  1119. ],
  1120. async err => {
  1121. if (err) {
  1122. err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
  1123. this.log(
  1124. "ERROR",
  1125. "STATIONS_UPDATE_DISPLAY_NAME",
  1126. `Updating station "${stationId}" displayName to "${newDisplayName}" failed. "${err}"`
  1127. );
  1128. return cb({ status: "failure", message: err });
  1129. }
  1130. this.log(
  1131. "SUCCESS",
  1132. "STATIONS_UPDATE_DISPLAY_NAME",
  1133. `Updated station "${stationId}" displayName to "${newDisplayName}" successfully.`
  1134. );
  1135. CacheModule.runJob("PUB", {
  1136. channel: "station.displayNameUpdate",
  1137. value: { stationId }
  1138. });
  1139. return cb({
  1140. status: "success",
  1141. message: "Successfully updated the display name."
  1142. });
  1143. }
  1144. );
  1145. }),
  1146. /**
  1147. * Updates a station's description
  1148. *
  1149. * @param session
  1150. * @param stationId - the station id
  1151. * @param newDescription - the new station description
  1152. * @param cb
  1153. */
  1154. updateDescription: isOwnerRequired(async function updateDescription(session, stationId, newDescription, cb) {
  1155. const stationModel = await DBModule.runJob(
  1156. "GET_MODEL",
  1157. {
  1158. modelName: "station"
  1159. },
  1160. this
  1161. );
  1162. async.waterfall(
  1163. [
  1164. next => {
  1165. stationModel.updateOne(
  1166. { _id: stationId },
  1167. { $set: { description: newDescription } },
  1168. { runValidators: true },
  1169. next
  1170. );
  1171. },
  1172. (res, next) => {
  1173. StationsModule.runJob("UPDATE_STATION", { stationId }, this)
  1174. .then(station => {
  1175. next(null, station);
  1176. })
  1177. .catch(next);
  1178. }
  1179. ],
  1180. async err => {
  1181. if (err) {
  1182. err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
  1183. this.log(
  1184. "ERROR",
  1185. "STATIONS_UPDATE_DESCRIPTION",
  1186. `Updating station "${stationId}" description to "${newDescription}" failed. "${err}"`
  1187. );
  1188. return cb({ status: "failure", message: err });
  1189. }
  1190. this.log(
  1191. "SUCCESS",
  1192. "STATIONS_UPDATE_DESCRIPTION",
  1193. `Updated station "${stationId}" description to "${newDescription}" successfully.`
  1194. );
  1195. CacheModule.runJob("PUB", {
  1196. channel: "station.descriptionUpdate",
  1197. value: { stationId }
  1198. });
  1199. return cb({
  1200. status: "success",
  1201. message: "Successfully updated the description."
  1202. });
  1203. }
  1204. );
  1205. }),
  1206. /**
  1207. * Updates a station's privacy
  1208. *
  1209. * @param session
  1210. * @param stationId - the station id
  1211. * @param newPrivacy - the new station privacy
  1212. * @param cb
  1213. */
  1214. updatePrivacy: isOwnerRequired(async function updatePrivacy(session, stationId, newPrivacy, cb) {
  1215. const stationModel = await DBModule.runJob(
  1216. "GET_MODEL",
  1217. {
  1218. modelName: "station"
  1219. },
  1220. this
  1221. );
  1222. let previousPrivacy = null;
  1223. async.waterfall(
  1224. [
  1225. next => {
  1226. stationModel.findOne({ _id: stationId }, next);
  1227. },
  1228. (station, next) => {
  1229. if (!station) next("No station found.");
  1230. else {
  1231. previousPrivacy = station.privacy;
  1232. next();
  1233. }
  1234. },
  1235. next => {
  1236. stationModel.updateOne(
  1237. { _id: stationId },
  1238. { $set: { privacy: newPrivacy } },
  1239. { runValidators: true },
  1240. next
  1241. );
  1242. },
  1243. (res, next) => {
  1244. StationsModule.runJob("UPDATE_STATION", { stationId }, this)
  1245. .then(station => {
  1246. next(null, station);
  1247. })
  1248. .catch(next);
  1249. }
  1250. ],
  1251. async err => {
  1252. if (err) {
  1253. err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
  1254. this.log(
  1255. "ERROR",
  1256. "STATIONS_UPDATE_PRIVACY",
  1257. `Updating station "${stationId}" privacy to "${newPrivacy}" failed. "${err}"`
  1258. );
  1259. return cb({ status: "failure", message: err });
  1260. }
  1261. this.log(
  1262. "SUCCESS",
  1263. "STATIONS_UPDATE_PRIVACY",
  1264. `Updated station "${stationId}" privacy to "${newPrivacy}" successfully.`
  1265. );
  1266. CacheModule.runJob("PUB", {
  1267. channel: "station.privacyUpdate",
  1268. value: { stationId, previousPrivacy }
  1269. });
  1270. return cb({
  1271. status: "success",
  1272. message: "Successfully updated the privacy."
  1273. });
  1274. }
  1275. );
  1276. }),
  1277. /**
  1278. * Updates a station's genres
  1279. *
  1280. * @param session
  1281. * @param stationId - the station id
  1282. * @param newGenres - the new station genres
  1283. * @param cb
  1284. */
  1285. updateGenres: isOwnerRequired(async function updateGenres(session, stationId, newGenres, cb) {
  1286. const stationModel = await DBModule.runJob(
  1287. "GET_MODEL",
  1288. {
  1289. modelName: "station"
  1290. },
  1291. this
  1292. );
  1293. async.waterfall(
  1294. [
  1295. next => {
  1296. stationModel.updateOne(
  1297. { _id: stationId },
  1298. { $set: { genres: newGenres } },
  1299. { runValidators: true },
  1300. next
  1301. );
  1302. },
  1303. (res, next) => {
  1304. StationsModule.runJob("UPDATE_STATION", { stationId }, this)
  1305. .then(station => {
  1306. next(null, station);
  1307. })
  1308. .catch(next);
  1309. }
  1310. ],
  1311. async err => {
  1312. if (err) {
  1313. err = await UtilsModule.runJob("GET_ERROR", { error: err });
  1314. this.log(
  1315. "ERROR",
  1316. "STATIONS_UPDATE_GENRES",
  1317. `Updating station "${stationId}" genres to "${newGenres}" failed. "${err}"`
  1318. );
  1319. return cb({ status: "failure", message: err });
  1320. }
  1321. this.log(
  1322. "SUCCESS",
  1323. "STATIONS_UPDATE_GENRES",
  1324. `Updated station "${stationId}" genres to "${newGenres}" successfully.`
  1325. );
  1326. return cb({
  1327. status: "success",
  1328. message: "Successfully updated the genres."
  1329. });
  1330. }
  1331. );
  1332. }),
  1333. /**
  1334. * Updates a station's blacklisted genres
  1335. *
  1336. * @param session
  1337. * @param stationId - the station id
  1338. * @param newBlacklistedGenres - the new station blacklisted genres
  1339. * @param cb
  1340. */
  1341. updateBlacklistedGenres: isOwnerRequired(async function updateBlacklistedGenres(
  1342. session,
  1343. stationId,
  1344. newBlacklistedGenres,
  1345. cb
  1346. ) {
  1347. const stationModel = await DBModule.runJob(
  1348. "GET_MODEL",
  1349. {
  1350. modelName: "station"
  1351. },
  1352. this
  1353. );
  1354. async.waterfall(
  1355. [
  1356. next => {
  1357. stationModel.updateOne(
  1358. { _id: stationId },
  1359. {
  1360. $set: {
  1361. blacklistedGenres: newBlacklistedGenres
  1362. }
  1363. },
  1364. { runValidators: true },
  1365. next
  1366. );
  1367. },
  1368. (res, next) => {
  1369. StationsModule.runJob("UPDATE_STATION", { stationId }, this)
  1370. .then(station => {
  1371. next(null, station);
  1372. })
  1373. .catch(next);
  1374. }
  1375. ],
  1376. async err => {
  1377. if (err) {
  1378. err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
  1379. this.log(
  1380. "ERROR",
  1381. "STATIONS_UPDATE_BLACKLISTED_GENRES",
  1382. `Updating station "${stationId}" blacklisted genres to "${newBlacklistedGenres}" failed. "${err}"`
  1383. );
  1384. return cb({ status: "failure", message: err });
  1385. }
  1386. this.log(
  1387. "SUCCESS",
  1388. "STATIONS_UPDATE_BLACKLISTED_GENRES",
  1389. `Updated station "${stationId}" blacklisted genres to "${newBlacklistedGenres}" successfully.`
  1390. );
  1391. return cb({
  1392. status: "success",
  1393. message: "Successfully updated the blacklisted genres."
  1394. });
  1395. }
  1396. );
  1397. }),
  1398. /**
  1399. * Updates a station's party mode
  1400. *
  1401. * @param session
  1402. * @param stationId - the station id
  1403. * @param newPartyMode - the new station party mode
  1404. * @param cb
  1405. */
  1406. updatePartyMode: isOwnerRequired(async function updatePartyMode(session, stationId, newPartyMode, cb) {
  1407. const stationModel = await DBModule.runJob(
  1408. "GET_MODEL",
  1409. {
  1410. modelName: "station"
  1411. },
  1412. this
  1413. );
  1414. async.waterfall(
  1415. [
  1416. next => {
  1417. StationsModule.runJob("GET_STATION", { stationId }, this)
  1418. .then(station => {
  1419. next(null, station);
  1420. })
  1421. .catch(next);
  1422. },
  1423. (station, next) => {
  1424. if (!station) return next("Station not found.");
  1425. if (station.partyMode === newPartyMode)
  1426. return next(`The party mode was already ${newPartyMode ? "enabled." : "disabled."}`);
  1427. return stationModel.updateOne(
  1428. { _id: stationId },
  1429. { $set: { partyMode: newPartyMode } },
  1430. { runValidators: true },
  1431. next
  1432. );
  1433. },
  1434. (res, next) => {
  1435. StationsModule.runJob("UPDATE_STATION", { stationId }, this)
  1436. .then(station => {
  1437. next(null, station);
  1438. })
  1439. .catch(next);
  1440. }
  1441. ],
  1442. async err => {
  1443. if (err) {
  1444. err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
  1445. this.log(
  1446. "ERROR",
  1447. "STATIONS_UPDATE_PARTY_MODE",
  1448. `Updating station "${stationId}" party mode to "${newPartyMode}" failed. "${err}"`
  1449. );
  1450. return cb({ status: "failure", message: err });
  1451. }
  1452. this.log(
  1453. "SUCCESS",
  1454. "STATIONS_UPDATE_PARTY_MODE",
  1455. `Updated station "${stationId}" party mode to "${newPartyMode}" successfully.`
  1456. );
  1457. CacheModule.runJob("PUB", {
  1458. channel: "station.updatePartyMode",
  1459. value: {
  1460. stationId,
  1461. partyMode: newPartyMode
  1462. }
  1463. });
  1464. StationsModule.runJob("SKIP_STATION", { stationId });
  1465. return cb({
  1466. status: "success",
  1467. message: "Successfully updated the party mode."
  1468. });
  1469. }
  1470. );
  1471. }),
  1472. /**
  1473. * Updates a station's theme
  1474. *
  1475. * @param session
  1476. * @param stationId - the station id
  1477. * @param newTheme - the new station theme
  1478. * @param cb
  1479. */
  1480. updateTheme: isOwnerRequired(async function updateTheme(session, stationId, newTheme, cb) {
  1481. const stationModel = await DBModule.runJob(
  1482. "GET_MODEL",
  1483. {
  1484. modelName: "station"
  1485. },
  1486. this
  1487. );
  1488. async.waterfall(
  1489. [
  1490. next => {
  1491. StationsModule.runJob("GET_STATION", { stationId }, this)
  1492. .then(station => {
  1493. next(null, station);
  1494. })
  1495. .catch(next);
  1496. },
  1497. (station, next) => {
  1498. if (!station) return next("Station not found.");
  1499. if (station.theme === newTheme) return next("No change in theme.");
  1500. return stationModel.updateOne(
  1501. { _id: stationId },
  1502. { $set: { theme: newTheme } },
  1503. { runValidators: true },
  1504. next
  1505. );
  1506. },
  1507. (res, next) => {
  1508. StationsModule.runJob("UPDATE_STATION", { stationId }, this)
  1509. .then(station => {
  1510. next(null, station);
  1511. })
  1512. .catch(next);
  1513. }
  1514. ],
  1515. async err => {
  1516. if (err) {
  1517. err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
  1518. this.log(
  1519. "ERROR",
  1520. "STATIONS_UPDATE_THEME",
  1521. `Updating station "${stationId}" theme to "${newTheme}" failed. "${err}"`
  1522. );
  1523. return cb({ status: "failure", message: err });
  1524. }
  1525. this.log(
  1526. "SUCCESS",
  1527. "STATIONS_UPDATE_THEME",
  1528. `Updated station "${stationId}" theme to "${newTheme}" successfully.`
  1529. );
  1530. CacheModule.runJob("PUB", {
  1531. channel: "station.themeUpdate",
  1532. value: { stationId }
  1533. });
  1534. return cb({
  1535. status: "success",
  1536. message: "Successfully updated the theme."
  1537. });
  1538. }
  1539. );
  1540. }),
  1541. /**
  1542. * Pauses a station
  1543. *
  1544. * @param session
  1545. * @param stationId - the station id
  1546. * @param cb
  1547. */
  1548. pause: isOwnerRequired(async function pause(session, stationId, cb) {
  1549. const stationModel = await DBModule.runJob(
  1550. "GET_MODEL",
  1551. {
  1552. modelName: "station"
  1553. },
  1554. this
  1555. );
  1556. async.waterfall(
  1557. [
  1558. next => {
  1559. StationsModule.runJob("GET_STATION", { stationId }, this)
  1560. .then(station => {
  1561. next(null, station);
  1562. })
  1563. .catch(next);
  1564. },
  1565. (station, next) => {
  1566. if (!station) return next("Station not found.");
  1567. if (station.paused) return next("That station was already paused.");
  1568. return stationModel.updateOne(
  1569. { _id: stationId },
  1570. { $set: { paused: true, pausedAt: Date.now() } },
  1571. next
  1572. );
  1573. },
  1574. (res, next) => {
  1575. StationsModule.runJob("UPDATE_STATION", { stationId }, this)
  1576. .then(station => {
  1577. next(null, station);
  1578. })
  1579. .catch(next);
  1580. }
  1581. ],
  1582. async err => {
  1583. if (err) {
  1584. err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
  1585. this.log("ERROR", "STATIONS_PAUSE", `Pausing station "${stationId}" failed. "${err}"`);
  1586. return cb({ status: "failure", message: err });
  1587. }
  1588. this.log("SUCCESS", "STATIONS_PAUSE", `Paused station "${stationId}" successfully.`);
  1589. CacheModule.runJob("PUB", {
  1590. channel: "station.pause",
  1591. value: stationId
  1592. });
  1593. NotificationsModule.runJob("UNSCHEDULE", {
  1594. name: `stations.nextSong?id=${stationId}`
  1595. });
  1596. return cb({
  1597. status: "success",
  1598. message: "Successfully paused."
  1599. });
  1600. }
  1601. );
  1602. }),
  1603. /**
  1604. * Resumes a station
  1605. *
  1606. * @param session
  1607. * @param stationId - the station id
  1608. * @param cb
  1609. */
  1610. resume: isOwnerRequired(async function resume(session, stationId, cb) {
  1611. const stationModel = await DBModule.runJob(
  1612. "GET_MODEL",
  1613. {
  1614. modelName: "station"
  1615. },
  1616. this
  1617. );
  1618. async.waterfall(
  1619. [
  1620. next => {
  1621. StationsModule.runJob("GET_STATION", { stationId }, this)
  1622. .then(station => {
  1623. next(null, station);
  1624. })
  1625. .catch(next);
  1626. },
  1627. (station, next) => {
  1628. if (!station) return next("Station not found.");
  1629. if (!station.paused) return next("That station is not paused.");
  1630. station.timePaused += Date.now() - station.pausedAt;
  1631. return stationModel.updateOne(
  1632. { _id: stationId },
  1633. {
  1634. $set: { paused: false },
  1635. $inc: { timePaused: Date.now() - station.pausedAt }
  1636. },
  1637. next
  1638. );
  1639. },
  1640. (res, next) => {
  1641. StationsModule.runJob("UPDATE_STATION", { stationId }, this)
  1642. .then(station => {
  1643. next(null, station);
  1644. })
  1645. .catch(next);
  1646. }
  1647. ],
  1648. async err => {
  1649. if (err) {
  1650. err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
  1651. this.log("ERROR", "STATIONS_RESUME", `Resuming station "${stationId}" failed. "${err}"`);
  1652. return cb({ status: "failure", message: err });
  1653. }
  1654. this.log("SUCCESS", "STATIONS_RESUME", `Resuming station "${stationId}" successfully.`);
  1655. CacheModule.runJob("PUB", {
  1656. channel: "station.resume",
  1657. value: stationId
  1658. });
  1659. return cb({
  1660. status: "success",
  1661. message: "Successfully resumed."
  1662. });
  1663. }
  1664. );
  1665. }),
  1666. /**
  1667. * Removes a station
  1668. *
  1669. * @param session
  1670. * @param stationId - the station id
  1671. * @param cb
  1672. */
  1673. remove: isOwnerRequired(async function remove(session, stationId, cb) {
  1674. const stationModel = await DBModule.runJob(
  1675. "GET_MODEL",
  1676. {
  1677. modelName: "station"
  1678. },
  1679. this
  1680. );
  1681. async.waterfall(
  1682. [
  1683. next => {
  1684. stationModel.deleteOne({ _id: stationId }, err => next(err));
  1685. },
  1686. next => {
  1687. CacheModule.runJob("HDEL", { table: "stations", key: stationId }, this).then(next).catch(next);
  1688. }
  1689. ],
  1690. async err => {
  1691. if (err) {
  1692. err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
  1693. this.log("ERROR", "STATIONS_REMOVE", `Removing station "${stationId}" failed. "${err}"`);
  1694. return cb({ status: "failure", message: err });
  1695. }
  1696. this.log("SUCCESS", "STATIONS_REMOVE", `Removing station "${stationId}" successfully.`);
  1697. CacheModule.runJob("PUB", {
  1698. channel: "station.remove",
  1699. value: stationId
  1700. });
  1701. ActivitiesModule.runJob("ADD_ACTIVITY", {
  1702. userId: session.userId,
  1703. activityType: "deleted_station",
  1704. payload: [stationId]
  1705. });
  1706. return cb({
  1707. status: "success",
  1708. message: "Successfully removed."
  1709. });
  1710. }
  1711. );
  1712. }),
  1713. /**
  1714. * Create a station
  1715. *
  1716. * @param session
  1717. * @param data - the station data
  1718. * @param cb
  1719. */
  1720. create: isLoginRequired(async function create(session, data, cb) {
  1721. const userModel = await DBModule.runJob("GET_MODEL", { modelName: "user" }, this);
  1722. const stationModel = await DBModule.runJob(
  1723. "GET_MODEL",
  1724. {
  1725. modelName: "station"
  1726. },
  1727. this
  1728. );
  1729. const playlistModel = await DBModule.runJob(
  1730. "GET_MODEL",
  1731. {
  1732. modelName: "playlist"
  1733. },
  1734. this
  1735. );
  1736. data.name = data.name.toLowerCase();
  1737. const blacklist = [
  1738. "country",
  1739. "edm",
  1740. "musare",
  1741. "hip-hop",
  1742. "rap",
  1743. "top-hits",
  1744. "todays-hits",
  1745. "old-school",
  1746. "christmas",
  1747. "about",
  1748. "support",
  1749. "staff",
  1750. "help",
  1751. "news",
  1752. "terms",
  1753. "privacy",
  1754. "profile",
  1755. "c",
  1756. "community",
  1757. "tos",
  1758. "login",
  1759. "register",
  1760. "p",
  1761. "official",
  1762. "o",
  1763. "trap",
  1764. "faq",
  1765. "team",
  1766. "donate",
  1767. "buy",
  1768. "shop",
  1769. "forums",
  1770. "explore",
  1771. "settings",
  1772. "admin",
  1773. "auth",
  1774. "reset_password"
  1775. ];
  1776. async.waterfall(
  1777. [
  1778. next => {
  1779. if (!data) return next("Invalid data.");
  1780. return next();
  1781. },
  1782. next => {
  1783. stationModel.findOne(
  1784. {
  1785. $or: [
  1786. { name: data.name },
  1787. {
  1788. displayName: new RegExp(`^${data.displayName}$`, "i")
  1789. }
  1790. ]
  1791. },
  1792. next
  1793. );
  1794. },
  1795. // eslint-disable-next-line consistent-return
  1796. (station, next) => {
  1797. this.log(station);
  1798. if (station) return next("A station with that name or display name already exists.");
  1799. const { name, displayName, description, genres, playlist, type, blacklistedGenres } = data;
  1800. const stationId = mongoose.Types.ObjectId();
  1801. if (type === "official") {
  1802. return userModel.findOne({ _id: session.userId }, (err, user) => {
  1803. if (err) return next(err);
  1804. if (!user) return next("User not found.");
  1805. if (user.role !== "admin") return next("Admin required.");
  1806. return playlistModel.create(
  1807. {
  1808. isUserModifiable: false,
  1809. displayName: `Station - ${name}`,
  1810. songs: [],
  1811. createdBy: "Musare",
  1812. createdFor: `${stationId}`,
  1813. createdAt: Date.now(),
  1814. type: "station"
  1815. },
  1816. (err, playlist2) => {
  1817. if (err) next(err);
  1818. else {
  1819. stationModel.create(
  1820. {
  1821. _id: stationId,
  1822. name,
  1823. displayName,
  1824. description,
  1825. type,
  1826. privacy: "private",
  1827. playlist2: playlist2._id,
  1828. playlist,
  1829. genres,
  1830. blacklistedGenres,
  1831. currentSong: StationsModule.defaultSong
  1832. },
  1833. next
  1834. );
  1835. }
  1836. }
  1837. );
  1838. });
  1839. }
  1840. if (type === "community") {
  1841. if (blacklist.indexOf(name) !== -1)
  1842. return next("That name is blacklisted. Please use a different name.");
  1843. return playlistModel.create(
  1844. {
  1845. isUserModifiable: false,
  1846. displayName: `Station - ${name}`,
  1847. songs: [],
  1848. createdBy: session.userId,
  1849. createdFor: `${stationId}`,
  1850. createdAt: Date.now(),
  1851. type: "station"
  1852. },
  1853. (err, playlist2) => {
  1854. if (err) next(err);
  1855. else {
  1856. stationModel.create(
  1857. {
  1858. name,
  1859. displayName,
  1860. description,
  1861. playlist2: playlist2._id,
  1862. type,
  1863. privacy: "private",
  1864. owner: session.userId,
  1865. queue: [],
  1866. currentSong: null
  1867. },
  1868. next
  1869. );
  1870. }
  1871. }
  1872. );
  1873. }
  1874. }
  1875. ],
  1876. async (err, station) => {
  1877. if (err) {
  1878. err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
  1879. this.log("ERROR", "STATIONS_CREATE", `Creating station failed. "${err}"`);
  1880. return cb({ status: "failure", message: err });
  1881. }
  1882. this.log("SUCCESS", "STATIONS_CREATE", `Created station "${station._id}" successfully.`);
  1883. CacheModule.runJob("PUB", {
  1884. channel: "station.create",
  1885. value: station._id
  1886. });
  1887. ActivitiesModule.runJob("ADD_ACTIVITY", {
  1888. userId: session.userId,
  1889. activityType: "created_station",
  1890. payload: [station._id]
  1891. });
  1892. return cb({
  1893. status: "success",
  1894. message: "Successfully created station."
  1895. });
  1896. }
  1897. );
  1898. }),
  1899. /**
  1900. * Adds song to station queue
  1901. *
  1902. * @param session
  1903. * @param stationId - the station id
  1904. * @param songId - the song id
  1905. * @param cb
  1906. */
  1907. addToQueue: isLoginRequired(async function addToQueue(session, stationId, songId, cb) {
  1908. const userModel = await DBModule.runJob("GET_MODEL", { modelName: "user" }, this);
  1909. const stationModel = await DBModule.runJob(
  1910. "GET_MODEL",
  1911. {
  1912. modelName: "station"
  1913. },
  1914. this
  1915. );
  1916. const playlistModel = await DBModule.runJob(
  1917. "GET_MODEL",
  1918. {
  1919. modelName: "playlist"
  1920. },
  1921. this
  1922. );
  1923. async.waterfall(
  1924. [
  1925. next => {
  1926. StationsModule.runJob("GET_STATION", { stationId }, this)
  1927. .then(station => {
  1928. next(null, station);
  1929. })
  1930. .catch(next);
  1931. },
  1932. (station, next) => {
  1933. if (!station) return next("Station not found.");
  1934. if (!station.partyMode) return next("Station is not in party mode.");
  1935. if (station.locked) {
  1936. return userModel.findOne({ _id: session.userId }, (err, user) => {
  1937. if (user.role !== "admin" && station.owner !== session.userId)
  1938. return next("Only owners and admins can add songs to a locked queue.");
  1939. return next(null, station);
  1940. });
  1941. }
  1942. return next(null, station);
  1943. },
  1944. (station, next) => {
  1945. if (station.type !== "community") return next("That station is not a community station.");
  1946. return StationsModule.runJob(
  1947. "CAN_USER_VIEW_STATION",
  1948. {
  1949. station,
  1950. userId: session.userId
  1951. },
  1952. this
  1953. )
  1954. .then(canView => {
  1955. if (canView) return next(null, station);
  1956. return next("Insufficient permissions.");
  1957. })
  1958. .catch(err => next(err));
  1959. },
  1960. (station, next) => {
  1961. if (station.currentSong && station.currentSong.songId === songId)
  1962. return next("That song is currently playing.");
  1963. return playlistModel.findOne({ _id: station.playlist2 }, (err, playlist) => {
  1964. console.log(111, station, err, playlist);
  1965. next(err, station, playlist);
  1966. });
  1967. },
  1968. (station, playlist, next) => {
  1969. async.each(
  1970. playlist.songs,
  1971. (song, next) => {
  1972. if (song.songId === songId) return next("That song is already in the queue.");
  1973. return next();
  1974. },
  1975. err => next(err, station, playlist)
  1976. );
  1977. },
  1978. (station, playlist, next) => {
  1979. SongsModule.runJob("GET_SONG_FROM_ID", { songId }, this)
  1980. .then(res => {
  1981. if (res.song) return next(null, res.song, station, playlist);
  1982. return YouTubeModule.runJob("GET_SONG", { songId }, this)
  1983. .then(response => {
  1984. const { song } = response;
  1985. song.artists = [];
  1986. song.skipDuration = 0;
  1987. song.likes = -1;
  1988. song.dislikes = -1;
  1989. song.thumbnail = "empty";
  1990. song.explicit = false;
  1991. return next(null, song, station, playlist);
  1992. })
  1993. .catch(err => {
  1994. console.log(11111, err);
  1995. next(err);
  1996. });
  1997. })
  1998. .catch(err => {
  1999. console.log(11122, err);
  2000. next(err);
  2001. });
  2002. },
  2003. (song, station, playlist, next) => {
  2004. song.requestedBy = session.userId;
  2005. song.requestedAt = Date.now();
  2006. let totalDuration = 0;
  2007. playlist.songs.forEach(song => {
  2008. totalDuration += song.duration;
  2009. });
  2010. if (totalDuration >= 3600 * 3) return next("The max length of the queue is 3 hours.");
  2011. return next(null, song, station, playlist);
  2012. },
  2013. (song, station, playlist, next) => {
  2014. console.log(333, song, station, playlist);
  2015. if (playlist.songs.length === 0) return next(null, song, station, playlist);
  2016. let totalDuration = 0;
  2017. const userId = playlist.songs[playlist.songs.length - 1].requestedBy;
  2018. playlist.songs.forEach(song => {
  2019. if (userId === song.requestedBy) {
  2020. totalDuration += song.duration;
  2021. }
  2022. });
  2023. if (totalDuration >= 900) return next("The max length of songs per user is 15 minutes.");
  2024. return next(null, song, station, playlist);
  2025. },
  2026. (song, station, playlist, next) => {
  2027. console.log(444, song, station, playlist);
  2028. if (playlist.songs.length === 0) return next(null, song, station);
  2029. let totalSongs = 0;
  2030. const userId = playlist.songs[playlist.songs.length - 1].requestedBy;
  2031. playlist.songs.forEach(song => {
  2032. if (userId === song.requestedBy) {
  2033. totalSongs += 1;
  2034. }
  2035. });
  2036. if (totalSongs <= 2) return next(null, song, station);
  2037. if (totalSongs > 3)
  2038. return next("The max amount of songs per user is 3, and only 2 in a row is allowed.");
  2039. if (
  2040. playlist.songs[playlist.songs.length - 2].requestedBy !== userId ||
  2041. playlist.songs[playlist.songs.length - 3] !== userId
  2042. )
  2043. return next("The max amount of songs per user is 3, and only 2 in a row is allowed.");
  2044. return next(null, song, station);
  2045. },
  2046. (song, station, next) => {
  2047. playlistModel.updateOne(
  2048. { _id: station.playlist2 },
  2049. { $push: { songs: song } },
  2050. { runValidators: true },
  2051. next
  2052. );
  2053. }
  2054. // (res, next) => {
  2055. // StationsModule.runJob("UPDATE_STATION", { stationId }, this)
  2056. // .then(station => {
  2057. // next(null, station);
  2058. // })
  2059. // .catch(next);
  2060. // }
  2061. ],
  2062. async err => {
  2063. if (err) {
  2064. err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
  2065. this.log(
  2066. "ERROR",
  2067. "STATIONS_ADD_SONG_TO_QUEUE",
  2068. `Adding song "${songId}" to station "${stationId}" queue failed. "${err}"`
  2069. );
  2070. return cb({ status: "failure", message: err });
  2071. }
  2072. this.log(
  2073. "SUCCESS",
  2074. "STATIONS_ADD_SONG_TO_QUEUE",
  2075. `Added song "${songId}" to station "${stationId}" successfully.`
  2076. );
  2077. CacheModule.runJob("PUB", {
  2078. channel: "station.queueUpdate",
  2079. value: stationId
  2080. });
  2081. return cb({
  2082. status: "success",
  2083. message: "Successfully added song to queue."
  2084. });
  2085. }
  2086. );
  2087. }),
  2088. /**
  2089. * Removes song from station queue
  2090. *
  2091. * @param session
  2092. * @param stationId - the station id
  2093. * @param songId - the song id
  2094. * @param cb
  2095. */
  2096. removeFromQueue: isOwnerRequired(async function removeFromQueue(session, stationId, songId, cb) {
  2097. const stationModel = await DBModule.runJob(
  2098. "GET_MODEL",
  2099. {
  2100. modelName: "station"
  2101. },
  2102. this
  2103. );
  2104. async.waterfall(
  2105. [
  2106. next => {
  2107. if (!songId) return next("Invalid song id.");
  2108. return StationsModule.runJob("GET_STATION", { stationId }, this)
  2109. .then(station => {
  2110. next(null, station);
  2111. })
  2112. .catch(next);
  2113. },
  2114. (station, next) => {
  2115. if (!station) return next("Station not found.");
  2116. if (station.type !== "community") return next("Station is not a community station.");
  2117. return async.each(
  2118. station.queue,
  2119. (queueSong, next) => {
  2120. if (queueSong.songId === songId) return next(true);
  2121. return next();
  2122. },
  2123. err => {
  2124. if (err === true) return next();
  2125. return next("Song is not currently in the queue.");
  2126. }
  2127. );
  2128. },
  2129. next => {
  2130. stationModel.updateOne({ _id: stationId }, { $pull: { queue: { songId } } }, next);
  2131. },
  2132. (res, next) => {
  2133. StationsModule.runJob("UPDATE_STATION", { stationId }, this)
  2134. .then(station => {
  2135. next(null, station);
  2136. })
  2137. .catch(next);
  2138. }
  2139. ],
  2140. async err => {
  2141. if (err) {
  2142. err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
  2143. this.log(
  2144. "ERROR",
  2145. "STATIONS_REMOVE_SONG_TO_QUEUE",
  2146. `Removing song "${songId}" from station "${stationId}" queue failed. "${err}"`
  2147. );
  2148. return cb({ status: "failure", message: err });
  2149. }
  2150. this.log(
  2151. "SUCCESS",
  2152. "STATIONS_REMOVE_SONG_TO_QUEUE",
  2153. `Removed song "${songId}" from station "${stationId}" successfully.`
  2154. );
  2155. CacheModule.runJob("PUB", {
  2156. channel: "station.queueUpdate",
  2157. value: stationId
  2158. });
  2159. return cb({
  2160. status: "success",
  2161. message: "Successfully removed song from queue."
  2162. });
  2163. }
  2164. );
  2165. }),
  2166. /**
  2167. * Gets the queue from a station
  2168. *
  2169. * @param {object} session - user session
  2170. * @param {string} stationId - the station id
  2171. * @param {Function} cb - callback
  2172. */
  2173. getQueue(session, stationId, cb) {
  2174. async.waterfall(
  2175. [
  2176. next => {
  2177. StationsModule.runJob("GET_STATION", { stationId }, this)
  2178. .then(station => {
  2179. next(null, station);
  2180. })
  2181. .catch(next);
  2182. },
  2183. (station, next) => {
  2184. if (!station) return next("Station not found.");
  2185. if (station.type !== "community") return next("Station is not a community station.");
  2186. return next(null, station);
  2187. },
  2188. (station, next) => {
  2189. StationsModule.runJob(
  2190. "CAN_USER_VIEW_STATION",
  2191. {
  2192. station,
  2193. userId: session.userId
  2194. },
  2195. this
  2196. )
  2197. .then(canView => {
  2198. if (canView) return next(null, station);
  2199. return next("Insufficient permissions.");
  2200. })
  2201. .catch(err => next(err));
  2202. }
  2203. ],
  2204. async (err, station) => {
  2205. if (err) {
  2206. err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
  2207. this.log(
  2208. "ERROR",
  2209. "STATIONS_GET_QUEUE",
  2210. `Getting queue for station "${stationId}" failed. "${err}"`
  2211. );
  2212. return cb({ status: "failure", message: err });
  2213. }
  2214. this.log("SUCCESS", "STATIONS_GET_QUEUE", `Got queue for station "${stationId}" successfully.`);
  2215. return cb({
  2216. status: "success",
  2217. message: "Successfully got queue.",
  2218. queue: station.queue
  2219. });
  2220. }
  2221. );
  2222. },
  2223. /**
  2224. * Selects a private playlist for a station
  2225. *
  2226. * @param session
  2227. * @param stationId - the station id
  2228. * @param playlistId - the private playlist id
  2229. * @param cb
  2230. */
  2231. selectPrivatePlaylist: isOwnerRequired(async function selectPrivatePlaylist(session, stationId, playlistId, cb) {
  2232. const stationModel = await DBModule.runJob(
  2233. "GET_MODEL",
  2234. {
  2235. modelName: "station"
  2236. },
  2237. this
  2238. );
  2239. const playlistModel = await DBModule.runJob(
  2240. "GET_MODEL",
  2241. {
  2242. modelName: "playlist"
  2243. },
  2244. this
  2245. );
  2246. async.waterfall(
  2247. [
  2248. next => {
  2249. StationsModule.runJob("GET_STATION", { stationId }, this)
  2250. .then(station => {
  2251. next(null, station);
  2252. })
  2253. .catch(next);
  2254. },
  2255. (station, next) => {
  2256. if (!station) return next("Station not found.");
  2257. if (station.type !== "community") return next("Station is not a community station.");
  2258. if (station.privatePlaylist === playlistId)
  2259. return next("That private playlist is already selected.");
  2260. return playlistModel.findOne({ _id: playlistId }, next);
  2261. },
  2262. (playlist, next) => {
  2263. if (!playlist) return next("Playlist not found.");
  2264. const currentSongIndex = playlist.songs.length > 0 ? playlist.songs.length - 1 : 0;
  2265. return stationModel.updateOne(
  2266. { _id: stationId },
  2267. {
  2268. $set: {
  2269. privatePlaylist: playlistId,
  2270. currentSongIndex
  2271. }
  2272. },
  2273. { runValidators: true },
  2274. next
  2275. );
  2276. },
  2277. (res, next) => {
  2278. StationsModule.runJob("UPDATE_STATION", { stationId }, this)
  2279. .then(station => {
  2280. next(null, station);
  2281. })
  2282. .catch(next);
  2283. }
  2284. ],
  2285. async (err, station) => {
  2286. if (err) {
  2287. err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
  2288. this.log(
  2289. "ERROR",
  2290. "STATIONS_SELECT_PRIVATE_PLAYLIST",
  2291. `Selecting private playlist "${playlistId}" for station "${stationId}" failed. "${err}"`
  2292. );
  2293. return cb({ status: "failure", message: err });
  2294. }
  2295. this.log(
  2296. "SUCCESS",
  2297. "STATIONS_SELECT_PRIVATE_PLAYLIST",
  2298. `Selected private playlist "${playlistId}" for station "${stationId}" successfully.`
  2299. );
  2300. NotificationsModule.runJob("UNSCHEDULE", {
  2301. name: `stations.nextSong?id${stationId}`
  2302. });
  2303. if (!station.partyMode) StationsModule.runJob("SKIP_STATION", { stationId });
  2304. CacheModule.runJob("PUB", {
  2305. channel: "privatePlaylist.selected",
  2306. value: {
  2307. playlistId,
  2308. stationId
  2309. }
  2310. });
  2311. return cb({
  2312. status: "success",
  2313. message: "Successfully selected playlist."
  2314. });
  2315. }
  2316. );
  2317. }),
  2318. /**
  2319. * Deselects the private playlist selected in a station
  2320. *
  2321. * @param session
  2322. * @param stationId - the station id
  2323. * @param cb
  2324. */
  2325. deselectPrivatePlaylist: isOwnerRequired(async function deselectPrivatePlaylist(session, stationId, cb) {
  2326. const stationModel = await DBModule.runJob(
  2327. "GET_MODEL",
  2328. {
  2329. modelName: "station"
  2330. },
  2331. this
  2332. );
  2333. async.waterfall(
  2334. [
  2335. next => {
  2336. StationsModule.runJob("GET_STATION", { stationId }, this)
  2337. .then(station => {
  2338. next(null, station);
  2339. })
  2340. .catch(next);
  2341. },
  2342. (station, next) => {
  2343. if (!station) return next("Station not found.");
  2344. if (station.type !== "community") return next("Station is not a community station.");
  2345. if (!station.privatePlaylist) return next("No private playlist is currently selected.");
  2346. return stationModel.updateOne(
  2347. { _id: stationId },
  2348. {
  2349. $set: {
  2350. privatePlaylist: null,
  2351. currentSongIndex: 0
  2352. }
  2353. },
  2354. { runValidators: true },
  2355. next
  2356. );
  2357. },
  2358. (res, next) => {
  2359. StationsModule.runJob("UPDATE_STATION", { stationId }, this)
  2360. .then(station => {
  2361. next(null, station);
  2362. })
  2363. .catch(next);
  2364. }
  2365. ],
  2366. async (err, station) => {
  2367. if (err) {
  2368. err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
  2369. this.log(
  2370. "ERROR",
  2371. "STATIONS_DESELECT_PRIVATE_PLAYLIST",
  2372. `Deselecting private playlist for station "${stationId}" failed. "${err}"`
  2373. );
  2374. return cb({ status: "failure", message: err });
  2375. }
  2376. this.log(
  2377. "SUCCESS",
  2378. "STATIONS_DESELECT_PRIVATE_PLAYLIST",
  2379. `Deselected private playlist for station "${stationId}" successfully.`
  2380. );
  2381. NotificationsModule.runJob("UNSCHEDULE", {
  2382. name: `stations.nextSong?id${stationId}`
  2383. });
  2384. if (!station.partyMode) StationsModule.runJob("SKIP_STATION", { stationId });
  2385. CacheModule.runJob("PUB", {
  2386. channel: "privatePlaylist.deselected",
  2387. value: {
  2388. stationId
  2389. }
  2390. });
  2391. return cb({
  2392. status: "success",
  2393. message: "Successfully deselected playlist."
  2394. });
  2395. }
  2396. );
  2397. }),
  2398. favoriteStation: isLoginRequired(async function favoriteStation(session, stationId, cb) {
  2399. const userModel = await DBModule.runJob("GET_MODEL", { modelName: "user" }, this);
  2400. async.waterfall(
  2401. [
  2402. next => {
  2403. StationsModule.runJob("GET_STATION", { stationId }, this)
  2404. .then(station => {
  2405. next(null, station);
  2406. })
  2407. .catch(next);
  2408. },
  2409. (station, next) => {
  2410. if (!station) return next("Station not found.");
  2411. return StationsModule.runJob(
  2412. "CAN_USER_VIEW_STATION",
  2413. {
  2414. station,
  2415. userId: session.userId
  2416. },
  2417. this
  2418. )
  2419. .then(canView => {
  2420. if (canView) return next();
  2421. return next("Insufficient permissions.");
  2422. })
  2423. .catch(err => next(err));
  2424. },
  2425. next => {
  2426. userModel.updateOne({ _id: session.userId }, { $addToSet: { favoriteStations: stationId } }, next);
  2427. },
  2428. (res, next) => {
  2429. if (res.nModified === 0) return next("The station was already favorited.");
  2430. return next();
  2431. }
  2432. ],
  2433. async err => {
  2434. if (err) {
  2435. err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
  2436. this.log("ERROR", "FAVORITE_STATION", `Favoriting station "${stationId}" failed. "${err}"`);
  2437. return cb({ status: "failure", message: err });
  2438. }
  2439. this.log("SUCCESS", "FAVORITE_STATION", `Favorited station "${stationId}" successfully.`);
  2440. CacheModule.runJob("PUB", {
  2441. channel: "user.favoritedStation",
  2442. value: {
  2443. userId: session.userId,
  2444. stationId
  2445. }
  2446. });
  2447. return cb({
  2448. status: "success",
  2449. message: "Succesfully favorited station."
  2450. });
  2451. }
  2452. );
  2453. }),
  2454. unfavoriteStation: isLoginRequired(async function unfavoriteStation(session, stationId, cb) {
  2455. const userModel = await DBModule.runJob("GET_MODEL", { modelName: "user" }, this);
  2456. async.waterfall(
  2457. [
  2458. next => {
  2459. userModel.updateOne({ _id: session.userId }, { $pull: { favoriteStations: stationId } }, next);
  2460. },
  2461. (res, next) => {
  2462. if (res.nModified === 0) return next("The station wasn't favorited.");
  2463. return next();
  2464. }
  2465. ],
  2466. async err => {
  2467. if (err) {
  2468. err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
  2469. this.log("ERROR", "UNFAVORITE_STATION", `Unfavoriting station "${stationId}" failed. "${err}"`);
  2470. return cb({ status: "failure", message: err });
  2471. }
  2472. this.log("SUCCESS", "UNFAVORITE_STATION", `Unfavorited station "${stationId}" successfully.`);
  2473. CacheModule.runJob("PUB", {
  2474. channel: "user.unfavoritedStation",
  2475. value: {
  2476. userId: session.userId,
  2477. stationId
  2478. }
  2479. });
  2480. return cb({
  2481. status: "success",
  2482. message: "Succesfully unfavorited station."
  2483. });
  2484. }
  2485. );
  2486. })
  2487. };