artists.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453
  1. import async from "async";
  2. import { useHasPermission } from "../hooks/hasPermission";
  3. // eslint-disable-next-line
  4. import moduleManager from "../../index";
  5. const DBModule = moduleManager.modules.db;
  6. const UtilsModule = moduleManager.modules.utils;
  7. const WSModule = moduleManager.modules.ws;
  8. const CacheModule = moduleManager.modules.cache;
  9. const MusicBrainzModule = moduleManager.modules.musicbrainz;
  10. const YouTubeModule = moduleManager.modules.youtube;
  11. CacheModule.runJob("SUB", {
  12. channel: "artists.create",
  13. cb: artist => {
  14. WSModule.runJob("EMIT_TO_ROOM", {
  15. room: "admin.artists",
  16. args: ["event:admin.artists.created", { data: { artist } }]
  17. });
  18. }
  19. });
  20. CacheModule.runJob("SUB", {
  21. channel: "artists.remove",
  22. cb: artistId => {
  23. WSModule.runJob("EMIT_TO_ROOM", {
  24. room: "admin.artists",
  25. args: ["event:admin.artists.deleted", { data: { artistId } }]
  26. });
  27. }
  28. });
  29. CacheModule.runJob("SUB", {
  30. channel: "artists.update",
  31. cb: artist => {
  32. WSModule.runJob("EMIT_TO_ROOM", {
  33. room: "admin.artists",
  34. args: ["event:admin.artists.updated", { data: { artist } }]
  35. });
  36. }
  37. });
  38. export default {
  39. /**
  40. * Gets artist items, used in the admin artist page by the AdvancedTable component
  41. * @param {object} session - the session object automatically added by the websocket
  42. * @param page - the page
  43. * @param pageSize - the size per page
  44. * @param properties - the properties to return for each artist item
  45. * @param sort - the sort object
  46. * @param queries - the queries array
  47. * @param operator - the operator for queries
  48. * @param cb
  49. */
  50. getData: useHasPermission(
  51. "admin.view.artists",
  52. async function getSet(session, page, pageSize, properties, sort, queries, operator, cb) {
  53. async.waterfall(
  54. [
  55. next => {
  56. DBModule.runJob(
  57. "GET_DATA",
  58. {
  59. page,
  60. pageSize,
  61. properties,
  62. sort,
  63. queries,
  64. operator,
  65. modelName: "artist",
  66. blacklistedProperties: [],
  67. specialProperties: {
  68. createdBy: [
  69. {
  70. $addFields: {
  71. createdByOID: {
  72. $convert: {
  73. input: "$createdBy",
  74. to: "objectId",
  75. onError: "unknown",
  76. onNull: "unknown"
  77. }
  78. }
  79. }
  80. },
  81. {
  82. $lookup: {
  83. from: "users",
  84. localField: "createdByOID",
  85. foreignField: "_id",
  86. as: "createdByUser"
  87. }
  88. },
  89. {
  90. $unwind: {
  91. path: "$createdByUser",
  92. preserveNullAndEmptyArrays: true
  93. }
  94. },
  95. {
  96. $addFields: {
  97. createdByUsername: {
  98. $ifNull: ["$createdByUser.username", "unknown"]
  99. }
  100. }
  101. },
  102. {
  103. $project: {
  104. createdByOID: 0,
  105. createdByUser: 0
  106. }
  107. }
  108. ]
  109. },
  110. specialQueries: {
  111. createdBy: newQuery => ({
  112. $or: [newQuery, { createdByUsername: newQuery.createdBy }]
  113. })
  114. }
  115. },
  116. this
  117. )
  118. .then(response => {
  119. next(null, response);
  120. })
  121. .catch(err => {
  122. next(err);
  123. });
  124. }
  125. ],
  126. async (err, response) => {
  127. if (err && err !== true) {
  128. err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
  129. this.log("ERROR", "ARTISTS_GET_DATA", `Failed to get data from artists. "${err}"`);
  130. return cb({ status: "error", message: err });
  131. }
  132. this.log("SUCCESS", "ARTISTS_GET_DATA", `Got data from artists successfully.`);
  133. return cb({
  134. status: "success",
  135. message: "Successfully got data from artists.",
  136. data: response
  137. });
  138. }
  139. );
  140. }
  141. ),
  142. /**
  143. * Creates an artist item
  144. * @param {object} session - the session object automatically added by the websocket
  145. * @param {object} data - the object of the artist data
  146. * @param {Function} cb - gets called with the result
  147. */
  148. create: useHasPermission("artists.create", async function create(session, data, cb) {
  149. const artistModel = await DBModule.runJob("GET_MODEL", { modelName: "artist" }, this);
  150. async.waterfall(
  151. [
  152. next => {
  153. if (data?.musicbrainzData?.id !== data?.musicbrainzIdentifier)
  154. return next("MusicBrainz data must match the provided identifier.");
  155. return next();
  156. },
  157. next => {
  158. data.createdBy = session.userId;
  159. data.createdAt = Date.now();
  160. artistModel.create(data, next);
  161. }
  162. ],
  163. async (err, artist) => {
  164. if (err) {
  165. err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
  166. this.log("ERROR", "ARTIST_CREATE", `Creating artist failed. "${err}"`);
  167. return cb({ status: "error", message: err });
  168. }
  169. CacheModule.runJob("PUB", { channel: "artists.create", value: artist });
  170. this.log("SUCCESS", "ARTIST_CREATE", `Created artist successful.`);
  171. return cb({
  172. status: "success",
  173. message: "Successfully created artist",
  174. data: {
  175. artistId: artist._id
  176. }
  177. });
  178. }
  179. );
  180. }),
  181. /**
  182. * Gets a artist item by id
  183. * @param {object} session - the session object automatically added by the websocket
  184. * @param {string} artistId - the artist item id
  185. * @param {Function} cb - gets called with the result
  186. */
  187. async getArtistFromId(session, artistId, cb) {
  188. const artistModel = await DBModule.runJob("GET_MODEL", { modelName: "artist" }, this);
  189. async.waterfall(
  190. [
  191. next => {
  192. artistModel.findById(artistId, next);
  193. }
  194. ],
  195. async (err, artist) => {
  196. if (err) {
  197. err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
  198. this.log("ERROR", "GET_ARTIST_FROM_ID", `Getting artist item ${artistId} failed. "${err}"`);
  199. return cb({ status: "error", message: err });
  200. }
  201. this.log("SUCCESS", "GET_ARTIST_FROM_ID", `Got artist item ${artistId} successfully.`, false);
  202. return cb({ status: "success", data: { artist } });
  203. }
  204. );
  205. },
  206. /**
  207. * Updates an artist item
  208. * @param {object} session - the session object automatically added by the websocket
  209. * @param {string} artistId - the id of the artist item
  210. * @param {object} item - the artist item object
  211. * @param {Function} cb - gets called with the result
  212. */
  213. update: useHasPermission("artists.update", async function update(session, artistId, item, cb) {
  214. const artistModel = await DBModule.runJob("GET_MODEL", { modelName: "artist" }, this);
  215. async.waterfall(
  216. [
  217. next => {
  218. if (!artistId) return next("Please provide an artist item id to update.");
  219. if (item?.musicbrainzData?.id !== item?.musicbrainzIdentifier)
  220. return next("MusicBrainz data must match the provided identifier.");
  221. return next();
  222. },
  223. next => {
  224. artistModel.updateOne({ _id: artistId }, item, { upsert: true }, err => next(err));
  225. }
  226. ],
  227. async err => {
  228. if (err) {
  229. err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
  230. this.log(
  231. "ERROR",
  232. "ARTIST_UPDATE",
  233. `Updating artist item "${artistId}" failed for user "${session.userId}". "${err}"`
  234. );
  235. return cb({ status: "error", message: err });
  236. }
  237. CacheModule.runJob("PUB", { channel: "artists.update", value: { ...item, _id: artistId } });
  238. this.log(
  239. "SUCCESS",
  240. "ARTIST_UPDATE",
  241. `Updated artist item "${artistId}" successful for user "${session.userId}".`
  242. );
  243. return cb({
  244. status: "success",
  245. message: "Successfully updated artist item"
  246. });
  247. }
  248. );
  249. }),
  250. /**
  251. * Deletes an artist - shouldn't be used outside of testing
  252. * @param {object} session - the session object automatically added by the websocket
  253. * @param {string} artistId - the id of the artist item
  254. * @param {Function} cb - gets called with the result
  255. */
  256. remove: useHasPermission("artists.update", async function remove(session, artistId, cb) {
  257. const artistModel = await DBModule.runJob("GET_MODEL", { modelName: "artist" }, this);
  258. async.waterfall(
  259. [
  260. next => {
  261. if (!artistId) return next("Please provide an artist item id to remove.");
  262. return next();
  263. },
  264. next => {
  265. artistModel.remove({ _id: artistId }, err => next(err));
  266. }
  267. ],
  268. async err => {
  269. if (err) {
  270. err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
  271. this.log(
  272. "ERROR",
  273. "ARTIST_REMOVE",
  274. `Removing artist item "${artistId}" failed for user "${session.userId}". "${err}"`
  275. );
  276. return cb({ status: "error", message: err });
  277. }
  278. CacheModule.runJob("PUB", { channel: "artists.remove", value: artistId });
  279. this.log(
  280. "SUCCESS",
  281. "ARTIST_REMOVE",
  282. `Removing artist item "${artistId}" successful for user "${session.userId}".`
  283. );
  284. return cb({
  285. status: "success",
  286. message: "Successfully removed artist item"
  287. });
  288. }
  289. );
  290. }),
  291. getMusicbrainzArtist: useHasPermission(
  292. "artists.update",
  293. async function getMusicbrainzArtist(session, musicbrainzIdentifier, cb) {
  294. const ArtistApiResponse = await MusicBrainzModule.runJob(
  295. "API_CALL",
  296. {
  297. url: `https://musicbrainz.org/ws/2/artist/${musicbrainzIdentifier}`,
  298. params: {
  299. fmt: "json",
  300. inc: "aliases"
  301. }
  302. },
  303. this
  304. );
  305. return cb({
  306. data: ArtistApiResponse
  307. });
  308. }
  309. ),
  310. getMusicbrainzRelatedUrls: useHasPermission(
  311. "artists.update",
  312. async function getMusicbrainzRelatedUrls(session, musicbrainzIdentifier, cb) {
  313. const ArtistApiResponse = await MusicBrainzModule.runJob(
  314. "API_CALL",
  315. {
  316. url: `https://musicbrainz.org/ws/2/artist/${musicbrainzIdentifier}/`,
  317. params: {
  318. fmt: "json",
  319. inc: "url-rels"
  320. }
  321. },
  322. this
  323. );
  324. return cb({
  325. data: ArtistApiResponse
  326. });
  327. }
  328. ),
  329. getIdFromUrl: useHasPermission("artists.update", async function getIdFromUrl(session, type, url, cb) {
  330. if (type === "youtube") {
  331. YouTubeModule.runJob("GET_CHANNEL_ID", {
  332. url,
  333. disableSearch: false
  334. })
  335. .then(({ channelId }) => {
  336. if (channelId) {
  337. cb({
  338. status: "success",
  339. channelId
  340. });
  341. } else {
  342. cb({
  343. status: "error",
  344. message: "Playlist id not found"
  345. });
  346. }
  347. })
  348. .catch(async err => {
  349. err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
  350. cb({ status: "error", message: err });
  351. });
  352. } else
  353. cb({
  354. status: "error",
  355. message: "Invalid type"
  356. });
  357. }),
  358. saveLinkingData: useHasPermission("artists.update", async function saveLinkingData(session, artistId, data, cb) {
  359. const artistModel = await DBModule.runJob("GET_MODEL", { modelName: "artist" }, this);
  360. async.waterfall(
  361. [
  362. next => {
  363. if (!artistId) return next("Please provide an artist item id to update.");
  364. return next();
  365. },
  366. next => {
  367. artistModel.updateOne({ _id: artistId }, { $set: { linkingData: data } }, err => next(err));
  368. },
  369. next => {
  370. artistModel.findOne({ _id: artistId }, next);
  371. }
  372. ],
  373. async (err, artist) => {
  374. if (err) {
  375. err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
  376. this.log(
  377. "ERROR",
  378. "ARTIST_SAVE_LINKING_DATA",
  379. `Saving linking data for artist "${artistId}" failed for user "${session.userId}". "${err}"`
  380. );
  381. return cb({ status: "error", message: err });
  382. }
  383. CacheModule.runJob("PUB", { channel: "artists.update", value: artist });
  384. this.log(
  385. "SUCCESS",
  386. "ARTIST_SAVE_LINKING_DATA",
  387. `Saving linking data for artist "${artistId}" was successful for user "${session.userId}".`
  388. );
  389. return cb({
  390. status: "success",
  391. message: "Successfully saved linking data"
  392. });
  393. }
  394. );
  395. }),
  396. searchMusicbrainzArtists: useHasPermission(
  397. "artists.update",
  398. async function searchMusicbrainzArtists(session, query, cb) {
  399. MusicBrainzModule.runJob("SEARCH_MUSICBRAINZ_ARTISTS", {
  400. query
  401. })
  402. .then(({ musicbrainzArtists }) => {
  403. cb({
  404. status: "success",
  405. data: {
  406. musicbrainzArtists
  407. }
  408. });
  409. })
  410. .catch(async err => {
  411. err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
  412. cb({ status: "error", message: err });
  413. });
  414. }
  415. )
  416. };