diff --git a/src/cache/redis.js b/src/cache/redis.js index 9318a31..fdee990 100644 --- a/src/cache/redis.js +++ b/src/cache/redis.js @@ -3,29 +3,29 @@ const configuration = require("../config/configuration").getInstance(); let client; try { - const redis = require("redis"); - console.log("Trying to connect with redis.."); + const redis = require("redis"); // eslint-disable-line global-require + console.log("Trying to connect with redis.."); // eslint-disable-line no-console const host = configuration.get("redis", "host"); const port = configuration.get("redis", "port"); - console.log(`redis://${host}:${port}`); + console.log(`redis://${host}:${port}`); // eslint-disable-line no-console client = redis.createClient({ url: `redis://${host}:${port}` }); - client.on("connect", () => console.log("Redis connection established!")); + client.on("connect", () => console.log("Redis connection established!")); // eslint-disable-line no-console client.on("error", () => { client.quit(); - console.error("Unable to connect to redis, setting up redis-mock."); + console.error("Unable to connect to redis, setting up redis-mock."); // eslint-disable-line no-console client = { - get() { - console.log("redis-dummy get", arguments[0]); + get(command) { + console.log(`redis-dummy get: ${command}`); // eslint-disable-line no-console return Promise.resolve(); }, - set() { - console.log("redis-dummy set", arguments[0]); + set(command) { + console.log(`redis-dummy set: ${command}`); // eslint-disable-line no-console return Promise.resolve(); } }; @@ -41,6 +41,7 @@ function set(key, value, TTL = 10800) { // successfully set value with key, now set TTL for key client.expire(key, TTL, e => { if (e) + // eslint-disable-next-line no-console console.error( "Unexpected error while setting expiration for key:", key, diff --git a/src/config/configuration.js b/src/config/configuration.js index 9b9a192..175a0a7 100644 --- a/src/config/configuration.js +++ b/src/config/configuration.js @@ -6,6 +6,7 @@ let instance = null; class Config { constructor() { this.location = Config.determineLocation(); + // eslint-disable-next-line import/no-dynamic-require, global-require this.fields = require(`${this.location}`); } diff --git a/src/config/field.js b/src/config/field.js index 4b86c80..ef4b3d1 100644 --- a/src/config/field.js +++ b/src/config/field.js @@ -46,7 +46,7 @@ class Field { } static base64Decode(string) { - return new Buffer(string, "base64").toString("utf-8"); + return Buffer.from(string, "base64").toString("utf-8"); } } diff --git a/src/notifications/sms.js b/src/notifications/sms.js index a8c80d7..b9e6f41 100644 --- a/src/notifications/sms.js +++ b/src/notifications/sms.js @@ -14,8 +14,8 @@ const sendSMS = message => { const apiKey = configuration.get("sms", "apikey"); if (!apiKey) { - console.warning("api key for sms not set, cannot send sms."); - return null; + console.warning("api key for sms not set, cannot send sms."); // eslint-disable-line no-console + return Promise.resolve(null); } const sender = configuration.get("sms", "sender"); @@ -32,10 +32,9 @@ const sendSMS = message => { recipients: [{ msisdn: `47${recipient}` }] } }, - function (err, r, body) { - const smsError = new SMSUnexpectedError(err || body); - console.error(smsError.message); - resolve(); + (err, r, body) => { + if (err) reject(new SMSUnexpectedError(err || body)); + resolve(body); } ); }); diff --git a/src/pirate/pirateRepository.js b/src/pirate/pirateRepository.js index 455ad0a..36849b2 100644 --- a/src/pirate/pirateRepository.js +++ b/src/pirate/pirateRepository.js @@ -46,8 +46,10 @@ async function callPythonAddMagnet(url, callback) { }); } -async function SearchPiratebay(query) { - if (query && query.includes("+")) { +async function SearchPiratebay(_query) { + let query = String(_query); + + if (query?.includes("+")) { query = query.replace("+", "%20"); } @@ -60,7 +62,7 @@ async function SearchPiratebay(query) { .catch(() => find(query, (err, results) => { if (err) { - console.log("THERE WAS A FUCKING ERROR!\n", err); + console.log("THERE WAS A FUCKING ERROR!\n", err); // eslint-disable-line no-console reject(Error("There was a error when searching for torrents")); } @@ -74,8 +76,8 @@ async function SearchPiratebay(query) { ); } -async function AddMagnet(magnet, name, tmdbId) { - return await new Promise((resolve, reject) => +function AddMagnet(magnet, name, tmdbId) { + return new Promise((resolve, reject) => callPythonAddMagnet(magnet, (err, results) => { if (err) { /* eslint-disable no-console */ @@ -86,11 +88,10 @@ async function AddMagnet(magnet, name, tmdbId) { console.log("result/error:", err, results); const database = establishedDatabase; - const insert_query = - "INSERT INTO requested_torrent(magnet,torrent_name,tmdb_id) \ - VALUES (?,?,?)"; + const insertQuery = + "INSERT INTO requested_torrent(magnet,torrent_name,tmdb_id) VALUES (?,?,?)"; - const response = database.run(insert_query, [magnet, name, tmdbId]); + const response = database.run(insertQuery, [magnet, name, tmdbId]); console.log(`Response from requsted_torrent insert: ${response}`); resolve({ success: true }); diff --git a/src/plex/plex.js b/src/plex/plex.js index 628c862..01b9cee 100644 --- a/src/plex/plex.js +++ b/src/plex/plex.js @@ -2,17 +2,28 @@ const fetch = require("node-fetch"); const convertPlexToMovie = require("./convertPlexToMovie"); const convertPlexToShow = require("./convertPlexToShow"); const convertPlexToEpisode = require("./convertPlexToEpisode"); - const redisCache = require("../cache/redis"); -const sanitize = string => string.toLowerCase().replace(/[^\w]/gi, ""); +class PlexRequestTimeoutError extends Error { + constructor() { + const message = "Timeout: Plex did not respond."; -function fixedEncodeURIComponent(str) { - return encodeURIComponent(str).replace(/[!'()*]/g, function (c) { - return `%${c.charCodeAt(0).toString(16).toUpperCase()}`; - }); + super(message); + this.statusCode = 408; + } } +class PlexUnexpectedError extends Error { + constructor(plexError = null) { + const message = "Unexpected plex error occured."; + + super(message); + this.statusCode = 500; + this.plexError = plexError; + } +} + +const sanitize = string => string.toLowerCase().replace(/[^\w]/gi, ""); const matchingTitleAndYear = (plex, tmdb) => { let matchingTitle; let matchingYear; @@ -30,25 +41,62 @@ const matchingTitleAndYear = (plex, tmdb) => { return matchingTitle && matchingYear; }; +function fixedEncodeURIComponent(str) { + return encodeURIComponent(str).replace(/[!'()*]/g, c => { + return `%${c.charCodeAt(0).toString(16).toUpperCase()}`; + }); +} + +function matchTmdbAndPlexMedia(plex, tmdb) { + let match; + + if (plex === null || tmdb === null) return false; + + if (plex instanceof Array) { + const possibleMatches = plex.map(plexItem => + matchingTitleAndYear(plexItem, tmdb) + ); + match = possibleMatches.includes(true); + } else { + match = matchingTitleAndYear(plex, tmdb); + } + + return match; +} + const successfullResponse = response => { - if (response && response.MediaContainer) return response; - - if ( - response === null || - response.status === null || - response.statusText === null - ) { - throw Error("Unable to decode response"); - } - const { status, statusText } = response; - - if (status === 200) { - return response.json(); + if (status !== 200) { + throw new PlexUnexpectedError(statusText); } - throw { message: statusText, status }; + + if (response?.MediaContainer) return response; + + return response.json(); }; +function mapResults(response) { + if (response?.MediaContainer?.Hub === null) { + return []; + } + + return response.MediaContainer.Hub.filter(category => category.size > 0) + .map(category => { + if (category.type === "movie") { + return category.Metadata.map(convertPlexToMovie); + } + if (category.type === "show") { + return category.Metadata.map(convertPlexToShow); + } + if (category.type === "episode") { + return category.Metadata.map(convertPlexToEpisode); + } + + return null; + }) + .filter(result => result !== null); +} + class Plex { constructor(ip, port = 32400, cache = null) { this.plexIP = ip; @@ -72,44 +120,23 @@ class Plex { return new Promise((resolve, reject) => this.cache .get(cacheKey) - .then(machineInfo => resolve(machineInfo.machineIdentifier)) + .then(machineInfo => resolve(machineInfo?.machineIdentifier)) .catch(() => fetch(url, options)) .then(response => response.json()) .then(machineInfo => this.cache.set(cacheKey, machineInfo.MediaContainer, 2628000) ) - .then(machineInfo => resolve(machineInfo.machineIdentifier)) + .then(machineInfo => resolve(machineInfo?.machineIdentifier)) .catch(error => { - if (error !== undefined && error.type === "request-timeout") { - reject({ - message: "Plex did not respond", - status: 408, - success: false - }); + if (error?.type === "request-timeout") { + reject(new PlexRequestTimeoutError()); } - reject(error); + reject(new PlexUnexpectedError()); }) ); } - matchTmdbAndPlexMedia(plex, tmdb) { - let match; - - if (plex === null || tmdb === null) return false; - - if (plex instanceof Array) { - const possibleMatches = plex.map(plexItem => - matchingTitleAndYear(plexItem, tmdb) - ); - match = possibleMatches.includes(true); - } else { - match = matchingTitleAndYear(plex, tmdb); - } - - return match; - } - async existsInPlex(tmdb) { const plexMatch = await this.findPlexItemByTitleAndYear( tmdb.title, @@ -123,7 +150,7 @@ class Plex { return this.search(title).then(plexResults => { const matchesInPlex = plexResults.map(plex => - this.matchTmdbAndPlexMedia(plex, query) + matchTmdbAndPlexMedia(plex, query) ); const matchesIndex = matchesInPlex.findIndex(el => el === true); return matchesInPlex !== -1 ? plexResults[matchesIndex] : null; @@ -171,18 +198,14 @@ class Plex { .catch(() => fetch(url, options)) // else fetch fresh data .then(successfullResponse) .then(results => this.cache.set(cacheKey, results, 21600)) // 6 hours - .then(this.mapResults) + .then(mapResults) .then(resolve) .catch(error => { - if (error !== undefined && error.type === "request-timeout") { - reject({ - message: "Plex did not respond", - status: 408, - success: false - }); + if (error?.type === "request-timeout") { + reject(new PlexRequestTimeoutError()); } - reject(error); + reject(new PlexUnexpectedError()); }) ); } @@ -195,40 +218,13 @@ class Plex { const query = title; const cacheKey = `${this.cacheTags.search}/${query}*`; - this.cache.del( - cacheKey, - (error, - response => { - if (response === 1) return true; - - // TODO improve cache key matching by lowercasing it on the backend. - // what do we actually need to check for if the key was deleted or not - // it might be an error or another response code. - console.log("Unable to delete, key might not exists"); - }) - ); - } - - mapResults(response) { - if (response?.MediaContainer?.Hub === null) { - return []; - } - - return response.MediaContainer.Hub.filter(category => category.size > 0) - .map(category => { - if (category.type === "movie") { - return category.Metadata; - } - if (category.type === "show") { - return category.Metadata.map(convertPlexToShow); - } - if (category.type === "episode") { - return category.Metadata.map(convertPlexToEpisode); - } - - return null; - }) - .filter(result => result !== null); + this.cache.del(cacheKey, (error, response) => { + // TODO improve cache key matching by lowercasing it on the backend. + // what do we actually need to check for if the key was deleted or not + // it might be an error or another response code. + console.log("Unable to delete, key might not exists"); + return response === 1; + }); } } diff --git a/src/plex/plexRepository.js b/src/plex/plexRepository.js index 3bc29ac..472111f 100644 --- a/src/plex/plexRepository.js +++ b/src/plex/plexRepository.js @@ -2,17 +2,54 @@ const rp = require("request-promise"); const convertPlexToSeasoned = require("./convertPlexToSeasoned"); const convertPlexToStream = require("./convertPlexToStream"); +// eslint-disable-next-line +function addAttributeIfTmdbInPlex(_tmdb, plexResult) { + const tmdb = { ..._tmdb }; + + if (plexResult?.results?.length > 0) { + plexResult.results.map(plexItem => { + tmdb.matchedInPlex = + tmdb.title === plexItem.title && tmdb.year === plexItem.year; + return tmdb; + }); + } else { + tmdb.matchedInPlex = false; + } + + return Promise.resolve(tmdb); +} + +function mapResults(response) { + return Promise.resolve() + .then(() => { + if (!response?.MediaContainer?.Metadata) return [[], 0]; + + const mappedResults = response.MediaContainer.Metadata.filter(element => { + return element.type === "movie" || element.type === "show"; + }).map(element => convertPlexToSeasoned(element)); + return [mappedResults, mappedResults.length]; + }) + .catch(error => { + throw new Error(error); + }); +} + class PlexRepository { constructor(plexIP) { this.plexIP = plexIP; } - inPlex(tmdbResult) { - return Promise.resolve() - .then(() => this.search(tmdbResult.title)) - .then(plexResult => this.compareTmdbToPlex(tmdbResult, plexResult)) - .catch(error => { - console.log(error); + inPlex(_tmdbResult) { + const tmdbResult = { ..._tmdbResult }; + this.search(tmdbResult.title) + .then(plexResult => addAttributeIfTmdbInPlex(tmdbResult, plexResult)) + .catch(() => { + /** + * If something crashes with search from this function it probably + * fine to set the `matchedInPlex` attribute to false and return + * original tmdb object + * */ + tmdbResult.matchedInPlex = false; return tmdbResult; }); @@ -32,50 +69,13 @@ class PlexRepository { }; return rp(options) - .catch(error => { - console.log(error); - throw new Error("Unable to search plex."); - }) - .then(result => this.mapResults(result)) + .then(result => mapResults(result)) .then(([mappedResults, resultCount]) => ({ results: mappedResults, total_results: resultCount })); } - compareTmdbToPlex(tmdb, plexResult) { - return Promise.resolve().then(() => { - if (plexResult.results.length === 0) { - tmdb.matchedInPlex = false; - } else { - // console.log('plex and tmdb:', plexResult, '\n', tmdb) - plexResult.results.map(plexItem => { - if (tmdb.title === plexItem.title && tmdb.year === plexItem.year) - tmdb.matchedInPlex = true; - return tmdb; - }); - } - return tmdb; - }); - } - - mapResults(response) { - return Promise.resolve() - .then(() => { - if (!response.MediaContainer.hasOwnProperty("Metadata")) return [[], 0]; - - const mappedResults = response.MediaContainer.Metadata.filter( - element => { - return element.type === "movie" || element.type === "show"; - } - ).map(element => convertPlexToSeasoned(element)); - return [mappedResults, mappedResults.length]; - }) - .catch(error => { - throw new Error(error); - }); - } - nowPlaying() { const options = { uri: `http://${this.plexIP}:32400/status/sessions`, diff --git a/src/plex/requestRepository.js b/src/plex/requestRepository.js index 8a01d5f..85caaf1 100644 --- a/src/plex/requestRepository.js +++ b/src/plex/requestRepository.js @@ -28,15 +28,15 @@ class RequestRepository { }; } - search(query, type, page) { - return Promise.resolve() - .then(() => tmdb.search(query, type, page)) + static search(query, type, page) { + return tmdb + .search(query, type, page) .catch(error => Error(`error in the house${error}`)); } lookup(identifier, type = "movie") { - return Promise.resolve() - .then(() => tmdb.lookup(identifier, type)) + return tmdb + .lookup(identifier, type) .then(tmdbMovie => this.checkID(tmdbMovie)) .then(tmdbMovie => plexRepository.inPlex(tmdbMovie)) .catch(error => { @@ -44,18 +44,16 @@ class RequestRepository { }); } - checkID(tmdbMovie) { - return Promise.resolve() - .then(() => - this.database.get(this.queries.checkIfIdRequested, [ - tmdbMovie.id, - tmdbMovie.type - ]) - ) + checkID(_tmdbMovie) { + const tmdbMovie = _tmdbMovie; + + return this.database + .get(this.queries.checkIfIdRequested, [tmdbMovie.id, tmdbMovie.type]) .then((result, error) => { if (error) { throw new Error(error); } + tmdbMovie.requested = !!result; return tmdbMovie; }); @@ -67,44 +65,41 @@ class RequestRepository { * @returns {Promise} If nothing has gone wrong. */ sendRequest(identifier, type, ip, userAgent, user) { - return Promise.resolve() - .then(() => tmdb.lookup(identifier, type)) - .then(movie => { - const username = user === undefined ? undefined : user.username; - // Add request to database - return this.database.run(this.queries.insertRequest, [ - movie.id, - movie.title, - movie.year, - movie.poster_path, - movie.background_path, - username, - ip, - userAgent, - movie.type - ]); - }); - } - - fetchRequested(status, page = "1", type = "%") { - return Promise.resolve().then(() => { - if ( - status === "requested" || - status === "downloading" || - status === "downloaded" - ) - return this.database.all(this.queries.fetchRequestedItemsByStatus, [ - status, - type, - page - ]); - return this.database.all(this.queries.fetchRequestedItems, page); + return tmdb.lookup(identifier, type).then(movie => { + const username = user === undefined ? undefined : user.username; + // Add request to database + return this.database.run(this.queries.insertRequest, [ + movie.id, + movie.title, + movie.year, + movie.poster_path, + movie.background_path, + username, + ip, + userAgent, + movie.type + ]); }); } + fetchRequested(status, page = "1", type = "%") { + if ( + status === "requested" || + status === "downloading" || + status === "downloaded" + ) + return this.database.all(this.queries.fetchRequestedItemsByStatus, [ + status, + type, + page + ]); + + return this.database.all(this.queries.fetchRequestedItems, page); + } + userRequests(username) { - return Promise.resolve() - .then(() => this.database.all(this.queries.userRequests, username)) + return this.database + .all(this.queries.userRequests, username) .catch(error => { if (String(error).includes("no such column")) { throw new Error("Username not found"); @@ -113,8 +108,11 @@ class RequestRepository { }) .then(result => { // TODO do a correct mapping before sending, not just a dump of the database - result.map(item => (item.poster = item.poster_path)); - return result; + return result.map(_item => { + const item = { ..._item }; + item.poster = item.poster_path; + return item; + }); }); } diff --git a/src/request/request.js b/src/request/request.js index 922e933..3f3d91b 100644 --- a/src/request/request.js +++ b/src/request/request.js @@ -1,10 +1,18 @@ const assert = require("assert"); -const configuration = require("../config/configuration").getInstance(); -const TMDB = require("../tmdb/tmdb"); - -const tmdb = new TMDB(configuration.get("tmdb", "apiKey")); +// const configuration = require("../config/configuration").getInstance(); +// const TMDB = require("../tmdb/tmdb"); const establishedDatabase = require("../database/database"); +// const tmdb = new TMDB(configuration.get("tmdb", "apiKey")); + +// function mapToTmdbByType(rows) { +// return rows.map(row => { +// if (row.type === "movie") return tmdb.movieInfo(row.id); +// if (row.type === "show") return tmdb.showInfo(row.id); +// return null; +// }); +// } + class RequestRepository { constructor(database) { this.database = database || establishedDatabase; @@ -30,14 +38,6 @@ class RequestRepository { }; } - mapToTmdbByType(rows) { - return rows.map(row => { - if (row.type === "movie") return tmdb.movieInfo(row.id); - if (row.type === "show") return tmdb.showInfo(row.id); - return null; - }); - } - /** * Add tmdb movie|show to requests * @param {tmdb} tmdb class of movie|show to add @@ -69,7 +69,6 @@ class RequestRepository { ) { throw new Error("This id is already requested", error.message); } - console.log("Error @ request.addTmdb:", error); throw new Error("Could not add request"); }); } @@ -104,7 +103,7 @@ class RequestRepository { */ fetchAll(_page = 1, filter = null) { // TODO implemented sort and filter - const page = parseInt(_page); + const page = parseInt(_page, 10); let fetchQuery = this.queries.fetchAll; let fetchTotalResults = this.queries.totalRequests; let fetchParams = [page]; @@ -143,7 +142,7 @@ class RequestRepository { totalRequests ]; - return this.mapToTmdbByType(rows); + // return mapToTmdbByType(rows); }) .then(([result, totalPages, totalRequests]) => Promise.resolve({ @@ -154,7 +153,6 @@ class RequestRepository { }) ) .catch(error => { - console.log(error); throw error; }); } diff --git a/src/tautulli/tautulli.js b/src/tautulli/tautulli.js index cccd8a7..f5fdf3d 100644 --- a/src/tautulli/tautulli.js +++ b/src/tautulli/tautulli.js @@ -10,6 +10,10 @@ class TautulliUnexpectedError extends Error { } } +function logTautulliError(error) { + throw new TautulliUnexpectedError(error); +} + class Tautulli { constructor(apiKey, ip, port) { this.apiKey = apiKey; @@ -26,11 +30,6 @@ class Tautulli { return url; } - /* eslint-disable-next-line class-methods-use-this */ - logTautulliError(error) { - throw new TautulliUnexpectedError(error); - } - getPlaysByDayOfWeek(plexUserId, days, yAxis) { const url = this.buildUrlWithCmdAndUserid( "get_plays_by_dayofweek", @@ -41,7 +40,7 @@ class Tautulli { return fetch(url.href) .then(resp => resp.json()) - .catch(error => this.logTautulliError(error)); + .catch(error => logTautulliError(error)); } getPlaysByDays(plexUserId, days, yAxis) { @@ -51,7 +50,7 @@ class Tautulli { return fetch(url.href) .then(resp => resp.json()) - .catch(error => this.logTautulliError(error)); + .catch(error => logTautulliError(error)); } watchTimeStats(plexUserId) { @@ -63,7 +62,7 @@ class Tautulli { return fetch(url.href) .then(resp => resp.json()) - .catch(error => this.logTautulliError(error)); + .catch(error => logTautulliError(error)); } viewHistory(plexUserId) { @@ -74,7 +73,7 @@ class Tautulli { return fetch(url.href) .then(resp => resp.json()) - .catch(error => this.logTautulliError(error)); + .catch(error => logTautulliError(error)); } } diff --git a/src/tmdb/tmdb.js b/src/tmdb/tmdb.js index bddb86f..9adb346 100644 --- a/src/tmdb/tmdb.js +++ b/src/tmdb/tmdb.js @@ -20,14 +20,23 @@ class TMDBUnauthorizedError extends Error { } class TMDBUnexpectedError extends Error { - constructor(type) { + constructor(type, errorMessage) { const message = `An unexpected error occured while fetching ${type} from tmdb`; super(message); + this.errorMessage = errorMessage; this.statusCode = 500; } } +class TMDBNotReachableError extends Error { + constructor( + message = "TMDB api not reachable, check your internet connection" + ) { + super(message); + } +} + const tmdbErrorResponse = (error, type = null) => { if (error.status === 404) { const message = error.response.body.status_message; @@ -35,11 +44,45 @@ const tmdbErrorResponse = (error, type = null) => { throw new TMDBNotFoundError(`${message.slice(0, -1)} in tmdb.`); } else if (error.status === 401) { throw new TMDBUnauthorizedError(error?.response?.body?.status_message); + } else if (error?.code === "ENOTFOUND") { + throw new TMDBNotReachableError(); } - throw new TMDBUnexpectedError(type); + throw new TMDBUnexpectedError(type, error); }; +/** + * Maps our response from tmdb api to a movie/show object. + * @param {String} response from tmdb. + * @param {String} The type declared in listSearch. + * @returns {Promise} dict with tmdb results, mapped as movie/show objects. + */ +function mapResults(response, type = null) { + const results = response?.results?.map(result => { + if (type === "movie" || result.media_type === "movie") { + const movie = Movie.convertFromTmdbResponse(result); + return movie.createJsonResponse(); + } + if (type === "show" || result.media_type === "tv") { + const show = Show.convertFromTmdbResponse(result); + return show.createJsonResponse(); + } + if (type === "person" || result.media_type === "person") { + const person = Person.convertFromTmdbResponse(result); + return person.createJsonResponse(); + } + + return {}; + }); + + return { + results, + page: response?.page, + total_results: response?.total_results, + total_pages: response?.total_pages + }; +} + class TMDB { constructor(apiKey, cache, tmdbLibrary) { this.tmdbLibrary = tmdbLibrary || moviedb(apiKey); @@ -69,15 +112,17 @@ class TMDB { this.defaultTTL = 86400; } - getFromCacheOrFetchFromTmdb(cacheKey, tmdbMethod, query) { - return new Promise((resolve, reject) => - this.cache - .get(cacheKey) - .then(resolve) - .catch(() => this.tmdb(tmdbMethod, query)) - .then(resolve) - .catch(error => reject(tmdbErrorResponse(error, tmdbMethod))) - ); + async getFromCacheOrFetchFromTmdb(cacheKey, tmdbMethod, query) { + try { + const result = await this.cache.get(cacheKey); + if (!result) throw new Error(); + + return result; + } catch { + return this.tmdb(tmdbMethod, query) + .then(result => this.cache.set(cacheKey, result, this.defaultTTL)) + .catch(error => tmdbErrorResponse(error, tmdbMethod)); + } } /** @@ -91,9 +136,9 @@ class TMDB { const query = { id: identifier }; const cacheKey = `tmdb/${this.cacheTags.movieInfo}:${identifier}`; - return this.getFromCacheOrFetchFromTmdb(cacheKey, "movieInfo", query) - .then(movie => this.cache.set(cacheKey, movie, this.defaultTTL)) - .then(movie => Movie.convertFromTmdbResponse(movie)); + return this.getFromCacheOrFetchFromTmdb(cacheKey, "movieInfo", query).then( + movie => Movie.convertFromTmdbResponse(movie) + ); } /** @@ -105,9 +150,11 @@ class TMDB { const query = { id: identifier }; const cacheKey = `tmdb/${this.cacheTags.movieCredits}:${identifier}`; - return this.getFromCacheOrFetchFromTmdb(cacheKey, "movieCredits", query) - .then(credits => this.cache.set(cacheKey, credits, this.defaultTTL)) - .then(credits => Credits.convertFromTmdbResponse(credits)); + return this.getFromCacheOrFetchFromTmdb( + cacheKey, + "movieCredits", + query + ).then(credits => Credits.convertFromTmdbResponse(credits)); } /** @@ -123,11 +170,7 @@ class TMDB { cacheKey, "movieReleaseDates", query - ) - .then(releaseDates => - this.cache.set(cacheKey, releaseDates, this.defaultTTL) - ) - .then(releaseDates => ReleaseDates.convertFromTmdbResponse(releaseDates)); + ).then(releaseDates => ReleaseDates.convertFromTmdbResponse(releaseDates)); } /** @@ -140,18 +183,18 @@ class TMDB { const query = { id: identifier }; const cacheKey = `tmdb/${this.cacheTags.showInfo}:${identifier}`; - return this.getFromCacheOrFetchFromTmdb(cacheKey, "tvInfo", query) - .then(show => this.cache.set(cacheKey, show, this.defaultTTL)) - .then(show => Show.convertFromTmdbResponse(show)); + return this.getFromCacheOrFetchFromTmdb(cacheKey, "tvInfo", query).then( + show => Show.convertFromTmdbResponse(show) + ); } showCredits(identifier) { const query = { id: identifier }; const cacheKey = `tmdb/${this.cacheTags.showCredits}:${identifier}`; - return this.getFromCacheOrFetchFromTmdb(cacheKey, "tvCredits", query) - .then(credits => this.cache.set(cacheKey, credits, this.defaultTTL)) - .then(credits => Credits.convertFromTmdbResponse(credits)); + return this.getFromCacheOrFetchFromTmdb(cacheKey, "tvCredits", query).then( + credits => Credits.convertFromTmdbResponse(credits) + ); } /** @@ -164,9 +207,9 @@ class TMDB { const query = { id: identifier }; const cacheKey = `tmdb/${this.cacheTags.personInfo}:${identifier}`; - return this.getFromCacheOrFetchFromTmdb(cacheKey, "personInfo", query) - .then(person => this.cache.set(cacheKey, person, this.defaultTTL)) - .then(person => Person.convertFromTmdbResponse(person)); + return this.getFromCacheOrFetchFromTmdb(cacheKey, "personInfo", query).then( + person => Person.convertFromTmdbResponse(person) + ); } personCredits(identifier) { @@ -177,18 +220,18 @@ class TMDB { cacheKey, "personCombinedCredits", query - ) - .then(credits => this.cache.set(cacheKey, credits, this.defaultTTL)) - .then(credits => Credits.convertFromTmdbResponse(credits)); + ).then(credits => Credits.convertFromTmdbResponse(credits)); } multiSearch(searchQuery, page = 1, includeAdult = true) { const query = { query: searchQuery, page, include_adult: includeAdult }; const cacheKey = `tmdb/${this.cacheTags.multiSearch}:${page}:${searchQuery}:${includeAdult}`; - return this.getFromCacheOrFetchFromTmdb(cacheKey, "searchMulti", query) - .then(response => this.cache.set(cacheKey, response, this.defaultTTL)) - .then(response => this.mapResults(response)); + return this.getFromCacheOrFetchFromTmdb( + cacheKey, + "searchMulti", + query + ).then(response => mapResults(response)); } /** @@ -205,9 +248,11 @@ class TMDB { }; const cacheKey = `tmdb/${this.cacheTags.movieSearch}:${page}:${searchQuery}:${includeAdult}`; - return this.getFromCacheOrFetchFromTmdb(cacheKey, "searchMovie", tmdbquery) - .then(response => this.cache.set(cacheKey, response, this.defaultTTL)) - .then(response => this.mapResults(response, "movie")); + return this.getFromCacheOrFetchFromTmdb( + cacheKey, + "searchMovie", + tmdbquery + ).then(response => mapResults(response, "movie")); } /** @@ -224,9 +269,11 @@ class TMDB { }; const cacheKey = `tmdb/${this.cacheTags.showSearch}:${page}:${searchQuery}:${includeAdult}`; - return this.getFromCacheOrFetchFromTmdb(cacheKey, "searchTv", tmdbquery) - .then(response => this.cache.set(cacheKey, response, this.defaultTTL)) - .then(response => this.mapResults(response, "show")); + return this.getFromCacheOrFetchFromTmdb( + cacheKey, + "searchTv", + tmdbquery + ).then(response => mapResults(response, "show")); } /** @@ -243,60 +290,29 @@ class TMDB { }; const cacheKey = `tmdb/${this.cacheTags.personSearch}:${page}:${searchQuery}:${includeAdult}`; - return this.getFromCacheOrFetchFromTmdb(cacheKey, "searchPerson", tmdbquery) - .then(response => this.cache.set(cacheKey, response, this.defaultTTL)) - .then(response => this.mapResults(response, "person")); + return this.getFromCacheOrFetchFromTmdb( + cacheKey, + "searchPerson", + tmdbquery + ).then(response => mapResults(response, "person")); } movieList(listName, page = 1) { const query = { page }; const cacheKey = `tmdb/${this.cacheTags[listName]}:${page}`; - return this.getFromCacheOrFetchFromTmdb(cacheKey, listName, query) - .then(response => this.cache.set(cacheKey, response, this.defaultTTL)) - .then(response => this.mapResults(response, "movie")); + return this.getFromCacheOrFetchFromTmdb(cacheKey, listName, query).then( + response => mapResults(response, "movie") + ); } showList(listName, page = 1) { const query = { page }; const cacheKey = `tmdb/${this.cacheTags[listName]}:${page}`; - return this.getFromCacheOrFetchFromTmdb(cacheKey, listName, query) - .then(response => this.cache.set(cacheKey, response, this.defaultTTL)) - .then(response => this.mapResults(response, "show")); - } - - /** - * Maps our response from tmdb api to a movie/show object. - * @param {String} response from tmdb. - * @param {String} The type declared in listSearch. - * @returns {Promise} dict with tmdb results, mapped as movie/show objects. - */ - // eslint-disable-next-line class-methods-use-this - mapResults(response, type = undefined) { - const results = response?.results?.map(result => { - if (type === "movie" || result.media_type === "movie") { - const movie = Movie.convertFromTmdbResponse(result); - return movie.createJsonResponse(); - } - if (type === "show" || result.media_type === "tv") { - const show = Show.convertFromTmdbResponse(result); - return show.createJsonResponse(); - } - if (type === "person" || result.media_type === "person") { - const person = Person.convertFromTmdbResponse(result); - return person.createJsonResponse(); - } - - return {}; - }); - - return { - results, - page: response.page, - total_results: response.total_results, - total_pages: response.total_pages - }; + return this.getFromCacheOrFetchFromTmdb(cacheKey, listName, query).then( + response => mapResults(response, "show") + ); } /** diff --git a/src/user/userRepository.js b/src/user/userRepository.js index 6022f48..d23f0d4 100644 --- a/src/user/userRepository.js +++ b/src/user/userRepository.js @@ -1,6 +1,69 @@ const assert = require("assert"); const establishedDatabase = require("../database/database"); +class LinkPlexUserError extends Error { + constructor(errorMessage = null) { + const message = + "An unexpected error occured while linking plex and seasoned accounts"; + super(message); + + this.statusCode = 500; + this.errorMessage = errorMessage; + this.source = "database"; + } +} + +class UnlinkPlexUserError extends Error { + constructor(errorMessage = null) { + const message = + "An unexpected error occured while unlinking plex and seasoned accounts"; + super(message); + + this.statusCode = 500; + this.errorMessage = errorMessage; + this.source = "database"; + } +} + +class UnexpectedUserSettingsError extends Error { + constructor(errorMessage = null) { + const message = + "An unexpected error occured while fetching settings for your account"; + super(message); + + this.statusCode = 500; + this.errorMessage = errorMessage; + this.source = "database"; + } +} + +class NoSettingsUserNotFoundError extends Error { + constructor() { + const message = "User not found, no settings to get"; + super(message); + + this.statusCode = 404; + } +} + +const rejectUnexpectedDatabaseError = ( + message, + status, + error, + reject = null +) => { + const body = { + status, + message, + source: "seasoned database" + }; + + if (reject == null) { + return new Promise((_, reject) => reject(body)); + } + return reject(body); +}; + class UserRepository { constructor(database) { this.database = database || establishedDatabase; @@ -77,14 +140,7 @@ class UserRepository { this.database .run(this.queries.link, [plexUserID, username]) .then(row => resolve(row)) - .catch(error => - reject({ - status: 500, - message: - "An unexpected error occured while linking plex and seasoned accounts", - source: "seasoned database" - }) - ); + .catch(error => reject(new LinkPlexUserError(error))); }); } @@ -98,14 +154,7 @@ class UserRepository { this.database .run(this.queries.unlink, username) .then(row => resolve(row)) - .catch(error => - reject({ - status: 500, - message: - "An unexpected error occured while unlinking plex and seasoned accounts", - source: "seasoned database" - }) - ); + .catch(error => reject(new UnlinkPlexUserError(error))); }); } @@ -131,6 +180,7 @@ class UserRepository { .get(this.queries.getSettings, username) .then(async row => { if (row == null) { + // eslint-disable-next-line no-console console.debug( `settings do not exist for user: ${username}. Creating settings entry.` ); @@ -146,23 +196,13 @@ class UserRepository { reject(error); } } else { - reject({ - status: 404, - message: "User not found, no settings to get" - }); + reject(new NoSettingsUserNotFoundError()); } } resolve(row); }) - .catch(() => - reject({ - status: 500, - message: - "An unexpected error occured while fetching settings for your account", - source: "seasoned database" - }) - ); + .catch(error => reject(new UnexpectedUserSettingsError(error))); }); } @@ -173,10 +213,10 @@ class UserRepository { * @param {String} emoji * @returns {Promsie} */ - updateSettings(username, darkMode = null, emoji = null) { + updateSettings(username, _darkMode = null, _emoji = null) { const settings = this.getSettings(username); - darkMode = darkMode ? darkMode : settings.darkMode; - emoji = emoji ? emoji : settings.emoji; + const darkMode = _darkMode || settings.darkMode; + const emoji = _emoji || settings.emoji; return this.dbUpdateSettings(username, darkMode, emoji).catch(error => { if (error.status && error.message) { @@ -223,22 +263,4 @@ class UserRepository { } } -const rejectUnexpectedDatabaseError = ( - message, - status, - error, - reject = null -) => { - const body = { - status, - message, - source: "seasoned database" - }; - - if (reject == null) { - return new Promise((_, reject) => reject(body)); - } - reject(body); -}; - module.exports = UserRepository; diff --git a/src/webserver/app.js b/src/webserver/app.js index 859c4a4..dcbe080 100644 --- a/src/webserver/app.js +++ b/src/webserver/app.js @@ -56,11 +56,11 @@ router.get("/", (req, res) => { res.send("welcome to seasoned api"); }); -app.use(Raven.errorHandler()); -app.use((err, req, res) => { - res.statusCode = 500; - res.end(`${res.sentry}\n`); -}); +// app.use(Raven.errorHandler()); +// app.use((err, req, res) => { +// res.statusCode = 500; +// res.end(`${res.sentry}\n`); +// }); /** * User diff --git a/src/webserver/controllers/list/listController.js b/src/webserver/controllers/list/listController.js index 2b55c59..298dccf 100644 --- a/src/webserver/controllers/list/listController.js +++ b/src/webserver/controllers/list/listController.js @@ -15,16 +15,13 @@ const tmdb = new TMDB(configuration.get("tmdb", "apiKey")); // + newly created (tv/latest). // + movie/latest // -function handleError(error, res) { - const { status, message } = error; - - if (status && message) { - res.status(status).send({ success: false, message }); - } else { - res - .status(500) - .send({ message: "An unexpected error occured while requesting list" }); - } +function handleError(listname, error, res) { + return res.status(error?.statusCode || 500).send({ + success: false, + message: + error?.message || + `An unexpected error occured while requesting list with id: ${listname}` + }); } function fetchTmdbList(req, res, listname, type) { @@ -34,16 +31,17 @@ function fetchTmdbList(req, res, listname, type) { return tmdb .movieList(listname, page) .then(listResponse => res.send(listResponse)) - .catch(error => handleError(error, res)); + .catch(error => handleError(listname, error, res)); } if (type === "show") { return tmdb .showList(listname, page) .then(listResponse => res.send(listResponse)) - .catch(error => handleError(error, res)); + .catch(error => handleError(listname, error, res)); } return handleError( + listname, { status: 400, message: `'${type}' is not a valid list type.` diff --git a/src/webserver/controllers/movie/credits.js b/src/webserver/controllers/movie/credits.js index ce33c5f..ad467dd 100644 --- a/src/webserver/controllers/movie/credits.js +++ b/src/webserver/controllers/movie/credits.js @@ -10,15 +10,12 @@ const movieCreditsController = (req, res) => { .movieCredits(movieId) .then(credits => res.send(credits.createJsonResponse())) .catch(error => { - const { status, message } = error; - - if (status && message) { - res.status(status).send({ success: false, message }); - } else { - res.status(500).send({ - message: "An unexpected error occured while requesting movie credits" - }); - } + return res.status(error?.statusCode || 500).send({ + success: false, + message: + error?.message || + `An unexpected error occured while requesting credits for movie with id: ${movieId}` + }); }); }; diff --git a/src/webserver/controllers/movie/info.js b/src/webserver/controllers/movie/info.js index f22889f..6311b88 100644 --- a/src/webserver/controllers/movie/info.js +++ b/src/webserver/controllers/movie/info.js @@ -5,20 +5,6 @@ const Plex = require("../../../plex/plex"); const tmdb = new TMDB(configuration.get("tmdb", "apiKey")); const plex = new Plex(configuration.get("plex", "ip")); -function handleError(error, res) { - const { status, message } = error; - - if (status && message) { - res.status(status).send({ success: false, message }); - } else { - res.status(500).send({ - success: false, - message: "An unexpected error occured while requesting movie info", - errorResponse: error?.message - }); - } -} - /** * Controller: Retrieve information for a movie * @param {Request} req http request variable @@ -32,9 +18,9 @@ async function movieInfoController(req, res) { let releaseDates = req.query?.release_dates; let checkExistance = req.query?.check_existance; - credits = credits.toLowerCase() === "true"; - releaseDates = releaseDates.toLowerCase() === "true"; - checkExistance = checkExistance.toLowerCase() === "true"; + credits = credits?.toLowerCase() === "true"; + releaseDates = releaseDates?.toLowerCase() === "true"; + checkExistance = checkExistance?.toLowerCase() === "true"; const tmdbQueue = [tmdb.movieInfo(movieId)]; if (credits) tmdbQueue.push(tmdb.movieCredits(movieId)); @@ -54,9 +40,14 @@ async function movieInfoController(req, res) { } catch {} } - res.send(movie); + return res.send(movie); } catch (error) { - handleError(error, res); + return res.status(error?.statusCode || 500).send({ + success: false, + message: + error?.message || + `An unexpected error occured while requesting info for with id: ${movieId}` + }); } } diff --git a/src/webserver/controllers/movie/releaseDates.js b/src/webserver/controllers/movie/releaseDates.js index 8097002..74ad3d8 100644 --- a/src/webserver/controllers/movie/releaseDates.js +++ b/src/webserver/controllers/movie/releaseDates.js @@ -10,15 +10,12 @@ const movieReleaseDatesController = (req, res) => { .movieReleaseDates(movieId) .then(releaseDates => res.send(releaseDates.createJsonResponse())) .catch(error => { - const { status, message } = error; - - if (status && message) { - res.status(status).send({ success: false, message }); - } else { - res.status(500).send({ - message: "An unexpected error occured while requesting movie credits" - }); - } + return res.status(error?.statusCode || 500).send({ + success: false, + message: + error?.message || + `An unexpected error occured while requesting release dates for movie with id: ${movieId}` + }); }); }; diff --git a/src/webserver/controllers/person/credits.js b/src/webserver/controllers/person/credits.js index 414936a..72d9c5a 100644 --- a/src/webserver/controllers/person/credits.js +++ b/src/webserver/controllers/person/credits.js @@ -10,15 +10,12 @@ const personCreditsController = (req, res) => { .personCredits(personId) .then(credits => res.send(credits)) .catch(error => { - const { status, message } = error; - - if (status && message) { - res.status(status).send({ success: false, message }); - } else { - res.status(500).send({ - message: "An unexpected error occured while requesting person credits" - }); - } + return res.status(error?.statusCode || 500).send({ + success: false, + message: + error?.message || + `An unexpected error occured while requesting info for person with id ${personId}.` + }); }); }; diff --git a/src/webserver/controllers/person/info.js b/src/webserver/controllers/person/info.js index c57436f..5a00102 100644 --- a/src/webserver/controllers/person/info.js +++ b/src/webserver/controllers/person/info.js @@ -3,18 +3,6 @@ const TMDB = require("../../../tmdb/tmdb"); const tmdb = new TMDB(configuration.get("tmdb", "apiKey")); -function handleError(error, res) { - const { status, message } = error; - - if (status && message) { - res.status(status).send({ success: false, message }); - } else { - res.status(500).send({ - message: "An unexpected error occured while requesting person info." - }); - } -} - /** * Controller: Retrieve information for a person * @param {Request} req http request variable @@ -26,7 +14,7 @@ async function personInfoController(req, res) { const personId = req.params.id; let { credits } = req.query; - credits = credits.toLowerCase() === "true"; + credits = credits?.toLowerCase() === "true"; const tmdbQueue = [tmdb.personInfo(personId)]; if (credits) tmdbQueue.push(tmdb.personCredits(personId)); @@ -39,7 +27,12 @@ async function personInfoController(req, res) { return res.send(person); } catch (error) { - return handleError(error, res); + return res.status(error?.statusCode || 500).send({ + success: false, + message: + error?.message || + `An unexpected error occured while requesting info for person with id: ${personId}` + }); } } diff --git a/src/webserver/controllers/request/fetchAllRequests.js b/src/webserver/controllers/request/fetchAllRequests.js index c20ea23..252d76f 100644 --- a/src/webserver/controllers/request/fetchAllRequests.js +++ b/src/webserver/controllers/request/fetchAllRequests.js @@ -15,7 +15,12 @@ function fetchAllRequests(req, res) { .fetchAll(page, filter) .then(result => res.send(result)) .catch(error => { - res.status(404).send({ success: false, message: error.message }); + return res.status(error?.statusCode || 500).send({ + success: false, + message: + error?.message || + `An unexpected error occured while requesting all requests` + }); }); } diff --git a/src/webserver/controllers/request/getRequest.js b/src/webserver/controllers/request/getRequest.js index ec79520..b4a491f 100644 --- a/src/webserver/controllers/request/getRequest.js +++ b/src/webserver/controllers/request/getRequest.js @@ -16,7 +16,12 @@ function fetchAllRequests(req, res) { .getRequestByIdAndType(id, type) .then(result => res.send(result)) .catch(error => { - res.status(404).send({ success: false, message: error.message }); + return res.status(error?.statusCode || 500).send({ + success: false, + message: + error?.message || + `An unexpected error occured while requesting request with id: ${id}` + }); }); } diff --git a/src/webserver/controllers/search/movieSearch.js b/src/webserver/controllers/search/movieSearch.js index 8d216c4..d863cc4 100644 --- a/src/webserver/controllers/search/movieSearch.js +++ b/src/webserver/controllers/search/movieSearch.js @@ -24,15 +24,12 @@ function movieSearchController(req, res) { .movieSearch(query, page, includeAdult) .then(movieSearchResults => res.send(movieSearchResults)) .catch(error => { - const { status, message } = error; - - if (status && message) { - res.status(status).send({ success: false, message }); - } else { - res.status(500).send({ - message: `An unexpected error occured while searching movies with query: ${query}` - }); - } + return res.status(error?.statusCode || 500).send({ + success: false, + message: + error?.message || + `An unexpected error occured while searching movies with query: ${query}` + }); }); } diff --git a/src/webserver/controllers/search/multiSearch.js b/src/webserver/controllers/search/multiSearch.js index b96a4f1..f16ce30 100644 --- a/src/webserver/controllers/search/multiSearch.js +++ b/src/webserver/controllers/search/multiSearch.js @@ -24,15 +24,12 @@ function multiSearchController(req, res) { .multiSearch(query, page, includeAdult) .then(multiSearchResults => res.send(multiSearchResults)) .catch(error => { - const { status, message } = error; - - if (status && message) { - res.status(status).send({ success: false, message }); - } else { - res.status(500).send({ - message: `An unexpected error occured while searching with query: ${query}` - }); - } + return res.status(error?.statusCode || 500).send({ + success: false, + message: + error?.message || + `An unexpected error occured while searching with query: ${query}` + }); }); } diff --git a/src/webserver/controllers/search/personSearch.js b/src/webserver/controllers/search/personSearch.js index c908dea..838bed0 100644 --- a/src/webserver/controllers/search/personSearch.js +++ b/src/webserver/controllers/search/personSearch.js @@ -24,15 +24,12 @@ function personSearchController(req, res) { .personSearch(query, page, includeAdult) .then(persons => res.send(persons)) .catch(error => { - const { status, message } = error; - - if (status && message) { - res.status(status).send({ success: false, message }); - } else { - res.status(500).send({ - message: `An unexpected error occured while searching people with query: ${query}` - }); - } + return res.status(error?.statusCode || 500).send({ + success: false, + message: + error?.message || + `An unexpected error occured while searching person with query: ${query}` + }); }); } diff --git a/src/webserver/controllers/search/showSearch.js b/src/webserver/controllers/search/showSearch.js index 740f763..534d1d0 100644 --- a/src/webserver/controllers/search/showSearch.js +++ b/src/webserver/controllers/search/showSearch.js @@ -26,7 +26,12 @@ function showSearchController(req, res) { res.send(shows); }) .catch(error => { - res.status(500).send({ success: false, message: error.message }); + res.status(error?.statusCode || 500).send({ + success: false, + message: + error?.message || + `An unexpected error occured while searching person with query: ${query}` + }); }); } diff --git a/src/webserver/controllers/show/credits.js b/src/webserver/controllers/show/credits.js index bed66a9..672f3e8 100644 --- a/src/webserver/controllers/show/credits.js +++ b/src/webserver/controllers/show/credits.js @@ -10,15 +10,12 @@ const showCreditsController = (req, res) => { .showCredits(showId) .then(credits => res.send(credits.createJsonResponse())) .catch(error => { - const { status, message } = error; - - if (status && message) { - res.status(status).send({ success: false, message }); - } else { - res.status(500).send({ - message: "An unexpected error occured while requesting show credits" - }); - } + return res.status(error?.statusCode || 500).send({ + success: false, + message: + error?.message || + `An unexpected error occured while requesting credits for show with id: ${showId}.` + }); }); }; diff --git a/src/webserver/controllers/show/info.js b/src/webserver/controllers/show/info.js index 4eeba4a..68f7301 100644 --- a/src/webserver/controllers/show/info.js +++ b/src/webserver/controllers/show/info.js @@ -5,18 +5,6 @@ const Plex = require("../../../plex/plex"); const tmdb = new TMDB(configuration.get("tmdb", "apiKey")); const plex = new Plex(configuration.get("plex", "ip")); -function handleError(error, res) { - const { status, message } = error; - - if (status && message) { - res.status(status).send({ success: false, message }); - } else { - res.status(500).send({ - message: "An unexpected error occured while requesting show info." - }); - } -} - /** * Controller: Retrieve information for a show * @param {Request} req http request variable @@ -48,9 +36,14 @@ async function showInfoController(req, res) { } catch {} } - res.send(show); + return res.send(show); } catch (error) { - handleError(error, res); + return res.status(error?.statusCode || 500).send({ + success: false, + message: + error?.message || + `An unexpected error occured while requesting info for show with id: ${showId}` + }); } }