stations.js 69 KB

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