diff --git a/src/cache/redis.js b/src/cache/redis.js index 340d884..9318a31 100644 --- a/src/cache/redis.js +++ b/src/cache/redis.js @@ -1,4 +1,3 @@ -const { promisify } = require("util"); const configuration = require("../config/configuration").getInstance(); let client; @@ -16,7 +15,7 @@ try { client.on("connect", () => console.log("Redis connection established!")); - client.on("error", function (err) { + client.on("error", () => { client.quit(); console.error("Unable to connect to redis, setting up redis-mock."); @@ -38,7 +37,7 @@ function set(key, value, TTL = 10800) { const json = JSON.stringify(value); client.set(key, json, (error, reply) => { - if (reply == "OK") { + if (reply === "OK") { // successfully set value with key, now set TTL for key client.expire(key, TTL, e => { if (e) @@ -55,14 +54,14 @@ function set(key, value, TTL = 10800) { return value; } -function get() { +function get(key) { return new Promise((resolve, reject) => { client.get(key, (error, reply) => { - if (reply == null) { + if (reply === null) { return reject(); } - resolve(JSON.parse(reply)); + return resolve(JSON.parse(reply)); }); }); } diff --git a/src/config/configuration.js b/src/config/configuration.js index 0414dc4..9b9a192 100644 --- a/src/config/configuration.js +++ b/src/config/configuration.js @@ -1,5 +1,5 @@ const path = require("path"); -const Field = require("./field.js"); +const Field = require("./field"); let instance = null; diff --git a/src/config/field.js b/src/config/field.js index 546513b..4b86c80 100644 --- a/src/config/field.js +++ b/src/config/field.js @@ -1,5 +1,5 @@ -const Filters = require("./filters.js"); -const EnvironmentVariables = require("./environmentVariables.js"); +const Filters = require("./filters"); +const EnvironmentVariables = require("./environmentVariables"); class Field { constructor(rawValue, environmentVariables) { diff --git a/src/database/sqliteDatabase.js b/src/database/sqliteDatabase.js index a330487..a6f8e53 100644 --- a/src/database/sqliteDatabase.js +++ b/src/database/sqliteDatabase.js @@ -72,12 +72,11 @@ class SqliteDatabase { /** * Run a SQL query against the database and retrieve the status. * @param {String} sql SQL query - * @param {Array} parameters in the SQL query * @returns {Promise} */ execute(sql) { - return new Promise(resolve => { - this.connection.exec(sql, (err, database) => { + return new Promise((resolve, reject) => { + this.connection.exec(sql, err => { if (err) { console.log("ERROR: ", err); reject(err); diff --git a/src/media_classes/plex.js b/src/media_classes/plex.js index 2df0f77..2626e39 100644 --- a/src/media_classes/plex.js +++ b/src/media_classes/plex.js @@ -1,3 +1,5 @@ +/* eslint-disable camelcase */ + const Media = require("./media"); class Plex extends Media { diff --git a/src/media_classes/tmdb.js b/src/media_classes/tmdb.js index 724ac86..63a0c40 100644 --- a/src/media_classes/tmdb.js +++ b/src/media_classes/tmdb.js @@ -1,3 +1,5 @@ +/* eslint-disable camelcase */ + const Media = require("./media"); class TMDB extends Media { diff --git a/src/notifications/sms.js b/src/notifications/sms.js index a56587f..a8c80d7 100644 --- a/src/notifications/sms.js +++ b/src/notifications/sms.js @@ -1,6 +1,15 @@ const request = require("request"); const configuration = require("../config/configuration").getInstance(); +class SMSUnexpectedError extends Error { + constructor(errorMessage) { + const message = "Unexpected error from sms provider."; + super(message); + + this.errorMessage = errorMessage; + } +} + const sendSMS = message => { const apiKey = configuration.get("sms", "apikey"); @@ -24,8 +33,8 @@ const sendSMS = message => { } }, function (err, r, body) { - console.log(err || body); - console.log("sms provider response:", body); + const smsError = new SMSUnexpectedError(err || body); + console.error(smsError.message); resolve(); } ); diff --git a/src/pirate/pirateRepository.js b/src/pirate/pirateRepository.js index 70a030c..455ad0a 100644 --- a/src/pirate/pirateRepository.js +++ b/src/pirate/pirateRepository.js @@ -1,4 +1,3 @@ -const assert = require("assert"); const http = require("http"); const { URL } = require("url"); const PythonShell = require("python-shell"); @@ -8,12 +7,12 @@ const establishedDatabase = require("../database/database"); const cache = require("../cache/redis"); function getMagnetFromURL(url) { - return new Promise((resolve, reject) => { + return new Promise(resolve => { const options = new URL(url); if (options.protocol.includes("magnet")) resolve(url); http.get(options, res => { - if (res.statusCode == 301 || res.statusCode == 302) { + if (res.statusCode === 301 || res.statusCode === 302) { resolve(res.headers.location); } }); @@ -43,7 +42,6 @@ async function callPythonAddMagnet(url, callback) { PythonShell.run("deluge_cli.py", options, callback); }) .catch(err => { - console.log(err); throw new Error(err); }); } @@ -76,7 +74,7 @@ async function SearchPiratebay(query) { ); } -async function AddMagnet(magnet, name, tmdb_id) { +async function AddMagnet(magnet, name, tmdbId) { return await new Promise((resolve, reject) => callPythonAddMagnet(magnet, (err, results) => { if (err) { @@ -87,12 +85,12 @@ async function AddMagnet(magnet, name, tmdb_id) { /* eslint-disable no-console */ console.log("result/error:", err, results); - database = establishedDatabase; - insert_query = + const database = establishedDatabase; + const insert_query = "INSERT INTO requested_torrent(magnet,torrent_name,tmdb_id) \ VALUES (?,?,?)"; - const response = database.run(insert_query, [magnet, name, tmdb_id]); + const response = database.run(insert_query, [magnet, name, tmdbId]); console.log(`Response from requsted_torrent insert: ${response}`); resolve({ success: true }); diff --git a/src/plex/convertPlexToSeasoned.js b/src/plex/convertPlexToSeasoned.js index 40f1dcc..d216c23 100644 --- a/src/plex/convertPlexToSeasoned.js +++ b/src/plex/convertPlexToSeasoned.js @@ -1,3 +1,5 @@ +/* eslint-disable camelcase */ + const Plex = require("../media_classes/plex"); function translateAdded(date_string) { @@ -27,7 +29,6 @@ function convertPlexToSeasoned(plex) { seasons, episodes ); - // seasoned.print(); return seasoned; } diff --git a/src/plex/hookDump.js b/src/plex/hookDump.js deleted file mode 100644 index 5a34b2e..0000000 --- a/src/plex/hookDump.js +++ /dev/null @@ -1,7 +0,0 @@ -const configuration = require("../config/configuration").getInstance(); - -function hookDumpController(req, res) { - console.log(req); -} - -module.exports = hookDumpController; diff --git a/src/plex/plex.js b/src/plex/plex.js index 9108dcb..628c862 100644 --- a/src/plex/plex.js +++ b/src/plex/plex.js @@ -3,8 +3,6 @@ const convertPlexToMovie = require("./convertPlexToMovie"); const convertPlexToShow = require("./convertPlexToShow"); const convertPlexToEpisode = require("./convertPlexToEpisode"); -const { Movie, Show, Person } = require("../tmdb/types"); - const redisCache = require("../cache/redis"); const sanitize = string => string.toLowerCase().replace(/[^\w]/gi, ""); @@ -19,15 +17,14 @@ const matchingTitleAndYear = (plex, tmdb) => { let matchingTitle; let matchingYear; - if (plex.title != null && tmdb.title != null) { + if (plex?.title && tmdb?.title) { const plexTitle = sanitize(plex.title); const tmdbTitle = sanitize(tmdb.title); - matchingTitle = plexTitle == tmdbTitle; + matchingTitle = plexTitle === tmdbTitle; matchingTitle = matchingTitle || plexTitle.startsWith(tmdbTitle); } else matchingTitle = false; - if (plex.year != null && tmdb.year != null) - matchingYear = plex.year == tmdb.year; + if (plex?.year && tmdb?.year) matchingYear = plex.year === tmdb.year; else matchingYear = false; return matchingTitle && matchingYear; @@ -37,9 +34,9 @@ const successfullResponse = response => { if (response && response.MediaContainer) return response; if ( - response == null || - response.status == null || - response.statusText == null + response === null || + response.status === null || + response.statusText === null ) { throw Error("Unable to decode response"); } @@ -83,7 +80,7 @@ class Plex { ) .then(machineInfo => resolve(machineInfo.machineIdentifier)) .catch(error => { - if (error != undefined && error.type === "request-timeout") { + if (error !== undefined && error.type === "request-timeout") { reject({ message: "Plex did not respond", status: 408, @@ -99,7 +96,7 @@ class Plex { matchTmdbAndPlexMedia(plex, tmdb) { let match; - if (plex == null || tmdb == null) return false; + if (plex === null || tmdb === null) return false; if (plex instanceof Array) { const possibleMatches = plex.map(plexItem => @@ -129,7 +126,7 @@ class Plex { this.matchTmdbAndPlexMedia(plex, query) ); const matchesIndex = matchesInPlex.findIndex(el => el === true); - return matchesInPlex != -1 ? plexResults[matchesIndex] : null; + return matchesInPlex !== -1 ? plexResults[matchesIndex] : null; }); } @@ -145,10 +142,10 @@ class Plex { matchingObjectInPlexPromise ]).then(([machineIdentifier, matchingObjectInPlex]) => { if ( - matchingObjectInPlex == false || - matchingObjectInPlex == null || - matchingObjectInPlex.key == null || - machineIdentifier == null + matchingObjectInPlex === false || + matchingObjectInPlex === null || + matchingObjectInPlex.key === null || + machineIdentifier === null ) return false; @@ -177,7 +174,7 @@ class Plex { .then(this.mapResults) .then(resolve) .catch(error => { - if (error != undefined && error.type === "request-timeout") { + if (error !== undefined && error.type === "request-timeout") { reject({ message: "Plex did not respond", status: 408, @@ -202,7 +199,7 @@ class Plex { cacheKey, (error, response => { - if (response == 1) return true; + 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 @@ -213,11 +210,7 @@ class Plex { } mapResults(response) { - if ( - response == null || - response.MediaContainer == null || - response.MediaContainer.Hub == null - ) { + if (response?.MediaContainer?.Hub === null) { return []; } @@ -232,8 +225,10 @@ class Plex { if (category.type === "episode") { return category.Metadata.map(convertPlexToEpisode); } + + return null; }) - .filter(result => result !== undefined); + .filter(result => result !== null); } } diff --git a/src/plex/requestRepository.js b/src/plex/requestRepository.js index fcf480e..8a01d5f 100644 --- a/src/plex/requestRepository.js +++ b/src/plex/requestRepository.js @@ -66,7 +66,7 @@ class RequestRepository { * @param {identifier, type} the id of the media object and type of media must be defined * @returns {Promise} If nothing has gone wrong. */ - sendRequest(identifier, type, ip, user_agent, user) { + sendRequest(identifier, type, ip, userAgent, user) { return Promise.resolve() .then(() => tmdb.lookup(identifier, type)) .then(movie => { @@ -80,7 +80,7 @@ class RequestRepository { movie.background_path, username, ip, - user_agent, + userAgent, movie.type ]); }); diff --git a/src/request/request.js b/src/request/request.js index 14ca0dc..922e933 100644 --- a/src/request/request.js +++ b/src/request/request.js @@ -4,7 +4,6 @@ const TMDB = require("../tmdb/tmdb"); const tmdb = new TMDB(configuration.get("tmdb", "apiKey")); const establishedDatabase = require("../database/database"); -const utils = require("./utils"); class RequestRepository { constructor(database) { @@ -19,76 +18,23 @@ class RequestRepository { 'select count(*) as totalRequests from requests where status != "downloaded"', totalRequestsFilteredStatus: "select count(*) as totalRequests from requests where status = ?", - fetchAllSort: `select id, type from request order by ? ?`, - fetchAllFilter: `select id, type from request where ? is "?"`, - fetchAllQuery: `select id, type from request where title like "%?%" or year like "%?%"`, - fetchAllFilterAndSort: `select id, type from request where ? is "?" order by ? ?`, - downloaded: - "(select status from requests where id is request.id and type is request.type limit 1)", + // fetchAllSort: `select id, type from request order by ? ?`, + // fetchAllFilter: `select id, type from request where ? is "?"`, + // fetchAllQuery: `select id, type from request where title like "%?%" or year like "%?%"`, + // fetchAllFilterAndSort: `select id, type from request where ? is "?" order by ? ?`, + // downloaded: "(select status from requests where id is request.id and type is request.type limit 1)", // deluge: '(select status from deluge_torrent where id is request.id and type is request.type limit 1)', // fetchAllFilterStatus: 'select * from request where ' - readWithoutUserData: - "select id, title, year, type, status, date from requests where id is ? and type is ?", + // readWithoutUserData: "select id, title, year, type, status, date from requests where id is ? and type is ?", read: "select id, title, year, type, status, requested_by, ip, date, user_agent from requests where id is ? and type is ?" }; } - sortAndFilterToDbQuery(by, direction, filter, query) { - let dbQuery; - - if (query !== undefined) { - const dbParams = [query, query]; - const dbquery = this.queries.fetchAllQuery; - - dbQuery = dbquery - .split("") - .map(char => (char === "?" ? dbParams.shift() : char)) - .join(""); - } else if (by !== undefined && filter !== undefined) { - const paramToColumnAndValue = { - movie: ["type", "movie"], - show: ["type", "show"] - }; - const dbParams = paramToColumnAndValue[filter].concat([by, direction]); - const query = this.queries.fetchAllFilterAndSort; - - dbQuery = query - .split("") - .map(char => (char === "?" ? dbParams.shift() : char)) - .join(""); - } else if (by !== undefined) { - const dbParams = [by, direction]; - const query = this.queries.fetchAllSort; - - dbQuery = query - .split("") - .map(char => (char === "?" ? dbParams.shift() : char)) - .join(""); - } else if (filter !== undefined) { - const paramToColumnAndValue = { - movie: ["type", "movie"], - show: ["type", "show"], - downloaded: [this.queries.downloaded, "downloaded"] - // downloading: [this.database.delugeStatus, 'downloading'] - }; - const dbParams = paramToColumnAndValue[filter]; - const query = this.queries.fetchAllFilter; - - dbQuery = query - .split("") - .map(char => (char === "?" ? dbParams.shift() : char)) - .join(""); - } else { - dbQuery = this.queries.fetchAll; - } - - return dbQuery; - } - 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; }); } @@ -97,7 +43,7 @@ class RequestRepository { * @param {tmdb} tmdb class of movie|show to add * @returns {Promise} */ - requestFromTmdb(tmdb, ip, user_agent, username) { + requestFromTmdb(tmdb, ip, userAgent, username) { return Promise.resolve() .then(() => this.database.get(this.queries.read, [tmdb.id, tmdb.type])) .then(row => @@ -112,7 +58,7 @@ class RequestRepository { tmdb.backdrop, username, ip, - user_agent, + userAgent, tmdb.type ]) ) @@ -153,20 +99,12 @@ class RequestRepository { /** * Fetch all requests with optional sort and filter params * @param {String} what we are sorting by - * @param {String} direction that can be either 'asc' or 'desc', default 'asc'. * @param {String} params to filter by - * @param {String} query param to filter result on. Filters on title and year * @returns {Promise} */ - fetchAll( - page = 1, - sort_by = undefined, - sort_direction = "asc", - filter = undefined, - query = undefined - ) { + fetchAll(_page = 1, filter = null) { // TODO implemented sort and filter - page = parseInt(page); + const page = parseInt(_page); let fetchQuery = this.queries.fetchAll; let fetchTotalResults = this.queries.totalRequests; let fetchParams = [page]; @@ -177,26 +115,24 @@ class RequestRepository { filter === "downloaded" || filter === "requested") ) { - console.log("tes"); fetchQuery = this.queries.fetchAllFilteredStatus; fetchTotalResults = this.queries.totalRequestsFilteredStatus; fetchParams = [filter, page]; - } else { - filter = undefined; } - return Promise.resolve() - .then(dbQuery => this.database.all(fetchQuery, fetchParams)) + return this.database + .all(fetchQuery, fetchParams) .then(async rows => { const sqliteResponse = await this.database.get( fetchTotalResults, - filter || undefined + filter || null ); const { totalRequests } = sqliteResponse; const totalPages = Math.ceil(totalRequests / 26); return [ - rows.map(item => { + rows.map(_item => { + const item = _item; item.poster = item.poster_path; delete item.poster_path; item.backdrop = item.background_path; @@ -206,7 +142,8 @@ class RequestRepository { totalPages, totalRequests ]; - return Promise.all(this.mapToTmdbByType(rows)); + + return this.mapToTmdbByType(rows); }) .then(([result, totalPages, totalRequests]) => Promise.resolve({ diff --git a/src/request/utils.js b/src/request/utils.js index 6cbe37a..67756c0 100644 --- a/src/request/utils.js +++ b/src/request/utils.js @@ -29,13 +29,13 @@ function validSort(by, direction) { }); } -function validFilter(filter_param) { +function validFilter(filterParam) { return new Promise((resolve, reject) => { - if (filter_param === undefined) { + if (filterParam === undefined) { resolve(); } - if (filter_param && validFilterParams.includes(filter_param)) { + if (filterParam && validFilterParams.includes(filterParam)) { resolve(); } else { reject( diff --git a/src/searchHistory/searchHistory.js b/src/searchHistory/searchHistory.js index 15fdcb1..9c25380 100644 --- a/src/searchHistory/searchHistory.js +++ b/src/searchHistory/searchHistory.js @@ -1,5 +1,15 @@ const establishedDatabase = require("../database/database"); +class SearchHistoryCreateDatabaseError extends Error { + constructor(message = "an unexpected error occured", errorResponse = null) { + super(message); + + this.source = "database"; + this.statusCode = 500; + this.errorResponse = errorResponse; + } +} + class SearchHistory { constructor(database) { this.database = database || establishedDatabase; @@ -16,18 +26,12 @@ class SearchHistory { * @returns {Promise} */ read(user) { - return new Promise((resolve, reject) => - this.database - .all(this.queries.read, user) - .then((result, error) => { - if (error) throw new Error(error); - resolve(result.map(row => row.search_query)); - }) - .catch(error => { - console.log("Error when fetching history from database:", error); - reject("Unable to get history."); - }) - ); + return this.database + .all(this.queries.read, user) + .then(result => result.map(row => row.search_query)) + .catch(error => { + throw new Error("Unable to get history.", error); + }); } /** @@ -41,15 +45,12 @@ class SearchHistory { .run(this.queries.create, [searchQuery, username]) .catch(error => { if (error.message.includes("FOREIGN")) { - throw new Error("Could not create search history."); + throw new SearchHistoryCreateDatabaseError( + "Could not create search history." + ); } - throw { - success: false, - status: 500, - message: "An unexpected error occured", - source: "database" - }; + throw new SearchHistoryCreateDatabaseError(); }); } } diff --git a/src/tautulli/tautulli.js b/src/tautulli/tautulli.js index 85adc6d..cccd8a7 100644 --- a/src/tautulli/tautulli.js +++ b/src/tautulli/tautulli.js @@ -1,5 +1,15 @@ const fetch = require("node-fetch"); +class TautulliUnexpectedError extends Error { + constructor(errorMessage) { + const message = "Unexpected error fetching from tautulli."; + super(message); + + this.statusCode = 500; + this.errorMessage = errorMessage; + } +} + class Tautulli { constructor(apiKey, ip, port) { this.apiKey = apiKey; @@ -7,38 +17,37 @@ class Tautulli { this.port = port; } - buildUrlWithCmdAndUserid(cmd, user_id) { + buildUrlWithCmdAndUserid(cmd, userId) { const url = new URL("api/v2", `http://${this.ip}:${this.port}`); url.searchParams.append("apikey", this.apiKey); url.searchParams.append("cmd", cmd); - url.searchParams.append("user_id", user_id); + url.searchParams.append("user_id", userId); return url; } + /* eslint-disable-next-line class-methods-use-this */ logTautulliError(error) { - console.error("error fetching from tautulli"); - - throw error; + throw new TautulliUnexpectedError(error); } - getPlaysByDayOfWeek(plexUserId, days, y_axis) { + getPlaysByDayOfWeek(plexUserId, days, yAxis) { const url = this.buildUrlWithCmdAndUserid( "get_plays_by_dayofweek", plexUserId ); url.searchParams.append("time_range", days); - url.searchParams.append("y_axis", y_axis); + url.searchParams.append("y_axis", yAxis); return fetch(url.href) .then(resp => resp.json()) .catch(error => this.logTautulliError(error)); } - getPlaysByDays(plexUserId, days, y_axis) { + getPlaysByDays(plexUserId, days, yAxis) { const url = this.buildUrlWithCmdAndUserid("get_plays_by_date", plexUserId); url.searchParams.append("time_range", days); - url.searchParams.append("y_axis", y_axis); + url.searchParams.append("y_axis", yAxis); return fetch(url.href) .then(resp => resp.json()) @@ -63,8 +72,6 @@ class Tautulli { url.searchParams.append("start", 0); url.searchParams.append("length", 50); - console.log("fetching url", url.href); - return fetch(url.href) .then(resp => resp.json()) .catch(error => this.logTautulliError(error)); diff --git a/src/tmdb/tmdb.js b/src/tmdb/tmdb.js index 9110167..bddb86f 100644 --- a/src/tmdb/tmdb.js +++ b/src/tmdb/tmdb.js @@ -3,25 +3,41 @@ const redisCache = require("../cache/redis"); const { Movie, Show, Person, Credits, ReleaseDates } = require("./types"); -const tmdbErrorResponse = (error, typeString = undefined) => { +class TMDBNotFoundError extends Error { + constructor(message) { + super(message); + + this.statusCode = 404; + } +} + +class TMDBUnauthorizedError extends Error { + constructor(message = "TMDB returned access denied, requires api token.") { + super(message); + + this.statusCode = 401; + } +} + +class TMDBUnexpectedError extends Error { + constructor(type) { + const message = `An unexpected error occured while fetching ${type} from tmdb`; + super(message); + + this.statusCode = 500; + } +} + +const tmdbErrorResponse = (error, type = null) => { if (error.status === 404) { const message = error.response.body.status_message; - throw { - status: 404, - message: `${message.slice(0, -1)} in tmdb.` - }; + throw new TMDBNotFoundError(`${message.slice(0, -1)} in tmdb.`); } else if (error.status === 401) { - throw { - status: 401, - message: error.response.body.status_message - }; + throw new TMDBUnauthorizedError(error?.response?.body?.status_message); } - throw { - status: 500, - message: `An unexpected error occured while fetching ${typeString} from tmdb` - }; + throw new TMDBUnexpectedError(type); }; class TMDB { @@ -166,9 +182,9 @@ class TMDB { .then(credits => Credits.convertFromTmdbResponse(credits)); } - multiSearch(search_query, page = 1, include_adult = true) { - const query = { query: search_query, page, include_adult }; - const cacheKey = `tmdb/${this.cacheTags.multiSearch}:${page}:${search_query}:${include_adult}`; + 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)) @@ -181,9 +197,13 @@ class TMDB { * @param {Number} page representing pagination of results * @returns {Promise} dict with query results, current page and total_pages */ - movieSearch(search_query, page = 1, include_adult = true) { - const tmdbquery = { query: search_query, page, include_adult }; - const cacheKey = `tmdb/${this.cacheTags.movieSearch}:${page}:${search_query}:${include_adult}`; + movieSearch(searchQuery, page = 1, includeAdult = true) { + const tmdbquery = { + query: searchQuery, + page, + include_adult: includeAdult + }; + const cacheKey = `tmdb/${this.cacheTags.movieSearch}:${page}:${searchQuery}:${includeAdult}`; return this.getFromCacheOrFetchFromTmdb(cacheKey, "searchMovie", tmdbquery) .then(response => this.cache.set(cacheKey, response, this.defaultTTL)) @@ -196,9 +216,13 @@ class TMDB { * @param {Number} page representing pagination of results * @returns {Promise} dict with query results, current page and total_pages */ - showSearch(search_query, page = 1, include_adult = true) { - const tmdbquery = { query: search_query, page, include_adult }; - const cacheKey = `tmdb/${this.cacheTags.showSearch}:${page}:${search_query}:${include_adult}`; + showSearch(searchQuery, page = 1, includeAdult = true) { + const tmdbquery = { + query: searchQuery, + page, + include_adult: includeAdult + }; + const cacheKey = `tmdb/${this.cacheTags.showSearch}:${page}:${searchQuery}:${includeAdult}`; return this.getFromCacheOrFetchFromTmdb(cacheKey, "searchTv", tmdbquery) .then(response => this.cache.set(cacheKey, response, this.defaultTTL)) @@ -211,27 +235,31 @@ class TMDB { * @param {Number} page representing pagination of results * @returns {Promise} dict with query results, current page and total_pages */ - personSearch(search_query, page = 1, include_adult = true) { - const tmdbquery = { query: search_query, page, include_adult }; - const cacheKey = `tmdb/${this.cacheTags.personSearch}:${page}:${search_query}:${include_adult}`; + personSearch(searchQuery, page = 1, includeAdult = true) { + const tmdbquery = { + query: searchQuery, + page, + include_adult: includeAdult + }; + 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")); } - movieList(listname, page = 1) { + movieList(listName, page = 1) { const query = { page }; - const cacheKey = `tmdb/${this.cacheTags[listname]}:${page}`; + const cacheKey = `tmdb/${this.cacheTags[listName]}:${page}`; - return this.getFromCacheOrFetchFromTmdb(cacheKey, listname, query) + return this.getFromCacheOrFetchFromTmdb(cacheKey, listName, query) .then(response => this.cache.set(cacheKey, response, this.defaultTTL)) .then(response => this.mapResults(response, "movie")); } - showList(listname, page = 1) { + showList(listName, page = 1) { const query = { page }; - const cacheKey = `tmdb/${this.cacheTags[listname]}:${page}`; + const cacheKey = `tmdb/${this.cacheTags[listName]}:${page}`; return this.getFromCacheOrFetchFromTmdb(cacheKey, listName, query) .then(response => this.cache.set(cacheKey, response, this.defaultTTL)) @@ -244,8 +272,9 @@ class 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 => { + const results = response?.results?.map(result => { if (type === "movie" || result.media_type === "movie") { const movie = Movie.convertFromTmdbResponse(result); return movie.createJsonResponse(); @@ -258,6 +287,8 @@ class TMDB { const person = Person.convertFromTmdbResponse(result); return person.createJsonResponse(); } + + return {}; }); return { @@ -280,7 +311,7 @@ class TMDB { if (error) { return reject(error); } - resolve(reponse); + return resolve(reponse); }; if (!argument) { diff --git a/src/tmdb/types.js b/src/tmdb/types.js index f3a831a..cbf5cbc 100644 --- a/src/tmdb/types.js +++ b/src/tmdb/types.js @@ -1,7 +1,7 @@ -const Movie = require("./types/movie.js"); -const Show = require("./types/show.js"); -const Person = require("./types/person.js"); -const Credits = require("./types/credits.js"); -const ReleaseDates = require("./types/releaseDates.js"); +const Movie = require("./types/movie"); +const Show = require("./types/show"); +const Person = require("./types/person"); +const Credits = require("./types/credits"); +const ReleaseDates = require("./types/releaseDates"); module.exports = { Movie, Show, Person, Credits, ReleaseDates }; diff --git a/src/tmdb/types.ts b/src/tmdb/types.ts index 89e2f8a..e079157 100644 --- a/src/tmdb/types.ts +++ b/src/tmdb/types.ts @@ -61,4 +61,4 @@ interface Genre { name: string; } -export { Movie, Show, Person, Genre } +export { Movie, Show, Person, Genre }; diff --git a/src/tmdb/types/credits.js b/src/tmdb/types/credits.js index b32a557..8740b6d 100644 --- a/src/tmdb/types/credits.js +++ b/src/tmdb/types/credits.js @@ -1,5 +1,55 @@ -import Movie from "./movie"; -import Show from "./show"; +/* eslint-disable camelcase */ +const Movie = require("./movie"); +const Show = require("./show"); + +class CreditedMovie extends Movie {} +class CreditedShow extends Show {} + +class CastMember { + constructor(character, gender, id, name, profile_path) { + this.character = character; + this.gender = gender; + this.id = id; + this.name = name; + this.profile_path = profile_path; + this.type = "person"; + } + + createJsonResponse() { + return { + character: this.character, + gender: this.gender, + id: this.id, + name: this.name, + profile_path: this.profile_path, + type: this.type + }; + } +} + +class CrewMember { + constructor(department, gender, id, job, name, profile_path) { + this.department = department; + this.gender = gender; + this.id = id; + this.job = job; + this.name = name; + this.profile_path = profile_path; + this.type = "person"; + } + + createJsonResponse() { + return { + department: this.department, + gender: this.gender, + id: this.id, + job: this.job, + name: this.name, + profile_path: this.profile_path, + type: this.type + }; + } +} class Credits { constructor(id, cast = [], crew = []) { @@ -63,53 +113,4 @@ class Credits { } } -class CastMember { - constructor(character, gender, id, name, profile_path) { - this.character = character; - this.gender = gender; - this.id = id; - this.name = name; - this.profile_path = profile_path; - this.type = "person"; - } - - createJsonResponse() { - return { - character: this.character, - gender: this.gender, - id: this.id, - name: this.name, - profile_path: this.profile_path, - type: this.type - }; - } -} - -class CrewMember { - constructor(department, gender, id, job, name, profile_path) { - this.department = department; - this.gender = gender; - this.id = id; - this.job = job; - this.name = name; - this.profile_path = profile_path; - this.type = "person"; - } - - createJsonResponse() { - return { - department: this.department, - gender: this.gender, - id: this.id, - job: this.job, - name: this.name, - profile_path: this.profile_path, - type: this.type - }; - } -} - -class CreditedMovie extends Movie {} -class CreditedShow extends Show {} - module.exports = Credits; diff --git a/src/tmdb/types/movie.js b/src/tmdb/types/movie.js index 09b5816..db78856 100644 --- a/src/tmdb/types/movie.js +++ b/src/tmdb/types/movie.js @@ -1,3 +1,5 @@ +/* eslint-disable camelcase */ + class Movie { constructor( id, diff --git a/src/tmdb/types/person.js b/src/tmdb/types/person.js index 67ca569..2cd24d9 100644 --- a/src/tmdb/types/person.js +++ b/src/tmdb/types/person.js @@ -1,3 +1,5 @@ +/* eslint-disable camelcase */ + class Person { constructor( id, diff --git a/src/tmdb/types/releaseDates.js b/src/tmdb/types/releaseDates.js index 02b6a4a..94ffa31 100644 --- a/src/tmdb/types/releaseDates.js +++ b/src/tmdb/types/releaseDates.js @@ -1,3 +1,57 @@ +const releaseTypeEnum = { + 1: "Premier", + 2: "Limited theatrical", + 3: "Theatrical", + 4: "Digital", + 5: "Physical", + 6: "TV" +}; + +class Release { + constructor(country, releaseDates) { + this.country = country; + this.releaseDates = releaseDates; + } + + createJsonResponse() { + return { + country: this.country, + release_dates: this.releaseDates.map(releaseDate => + releaseDate.createJsonResponse() + ) + }; + } +} + +class ReleaseDate { + constructor(certification, language, releaseDate, type, note) { + this.certification = certification; + this.language = language; + this.releaseDate = releaseDate; + this.type = this.releaseTypeLookup(type); + this.note = note; + } + + static releaseTypeLookup(releaseTypeKey) { + if (releaseTypeKey <= Object.keys(releaseTypeEnum).length) { + return releaseTypeEnum[releaseTypeKey]; + } + + // TODO log | Release type not defined, does this need updating? + return null; + } + + createJsonResponse() { + return { + certification: this.certification, + language: this.language, + release_date: this.releaseDate, + type: this.type, + note: this.note + }; + } +} + class ReleaseDates { constructor(id, releases) { this.id = id; @@ -35,56 +89,4 @@ class ReleaseDates { } } -class Release { - constructor(country, releaseDates) { - this.country = country; - this.releaseDates = releaseDates; - } - - createJsonResponse() { - return { - country: this.country, - release_dates: this.releaseDates.map(releaseDate => - releaseDate.createJsonResponse() - ) - }; - } -} - -class ReleaseDate { - constructor(certification, language, releaseDate, type, note) { - this.certification = certification; - this.language = language; - this.releaseDate = releaseDate; - this.type = this.releaseTypeLookup(type); - this.note = note; - } - - releaseTypeLookup(releaseTypeKey) { - const releaseTypeEnum = { - 1: "Premier", - 2: "Limited theatrical", - 3: "Theatrical", - 4: "Digital", - 5: "Physical", - 6: "TV" - }; - if (releaseTypeKey <= Object.keys(releaseTypeEnum).length) { - return releaseTypeEnum[releaseTypeKey]; - } - // TODO log | Release type not defined, does this need updating? - return null; - } - - createJsonResponse() { - return { - certification: this.certification, - language: this.language, - release_date: this.releaseDate, - type: this.type, - note: this.note - }; - } -} - module.exports = ReleaseDates; diff --git a/src/tmdb/types/show.js b/src/tmdb/types/show.js index 995a4de..cbbf434 100644 --- a/src/tmdb/types/show.js +++ b/src/tmdb/types/show.js @@ -1,3 +1,5 @@ +/* eslint-disable camelcase */ + class Show { constructor( id, diff --git a/src/user/userRepository.js b/src/user/userRepository.js index 4f9683c..6022f48 100644 --- a/src/user/userRepository.js +++ b/src/user/userRepository.js @@ -51,8 +51,7 @@ class UserRepository { assert(row, "The user does not exist."); return row.password; }) - .catch(err => { - console.log(error); + .catch(() => { throw new Error("Unable to find your user."); }); } @@ -78,17 +77,14 @@ class UserRepository { this.database .run(this.queries.link, [plexUserID, username]) .then(row => resolve(row)) - .catch(error => { - // TODO log this unknown db error - console.error("db error", error); - + .catch(error => reject({ status: 500, message: "An unexpected error occured while linking plex and seasoned accounts", source: "seasoned database" - }); - }); + }) + ); }); } @@ -102,17 +98,14 @@ class UserRepository { this.database .run(this.queries.unlink, username) .then(row => resolve(row)) - .catch(error => { - // TODO log this unknown db error - console.log("db error", error); - + .catch(error => reject({ status: 500, message: "An unexpected error occured while unlinking plex and seasoned accounts", source: "seasoned database" - }); - }); + }) + ); }); } @@ -162,18 +155,14 @@ class UserRepository { resolve(row); }) - .catch(error => { - console.error( - "Unexpected error occured while fetching settings for your account. Error:", - error - ); + .catch(() => reject({ status: 500, message: "An unexpected error occured while fetching settings for your account", source: "seasoned database" - }); - }); + }) + ); }); } @@ -184,12 +173,12 @@ class UserRepository { * @param {String} emoji * @returns {Promsie} */ - updateSettings(username, dark_mode = undefined, emoji = undefined) { + updateSettings(username, darkMode = null, emoji = null) { const settings = this.getSettings(username); - dark_mode = dark_mode !== undefined ? dark_mode : settings.dark_mode; - emoji = emoji !== undefined ? emoji : settings.emoji; + darkMode = darkMode ? darkMode : settings.darkMode; + emoji = emoji ? emoji : settings.emoji; - return this.dbUpdateSettings(username, dark_mode, emoji).catch(error => { + return this.dbUpdateSettings(username, darkMode, emoji).catch(error => { if (error.status && error.message) { return error; } @@ -225,10 +214,10 @@ class UserRepository { * @param {String} username * @returns {Promsie} */ - dbUpdateSettings(username, dark_mode, emoji) { - return new Promise((resolve, reject) => + dbUpdateSettings(username, darkMode, emoji) { + return new Promise(resolve => this.database - .run(this.queries.updateSettings, [username, dark_mode, emoji]) + .run(this.queries.updateSettings, [username, darkMode, emoji]) .then(row => resolve(row)) ); } @@ -240,7 +229,6 @@ const rejectUnexpectedDatabaseError = ( error, reject = null ) => { - console.error(error); const body = { status, message, @@ -248,7 +236,7 @@ const rejectUnexpectedDatabaseError = ( }; if (reject == null) { - return new Promise((resolve, reject) => reject(body)); + return new Promise((_, reject) => reject(body)); } reject(body); }; diff --git a/src/user/userSecurity.js b/src/user/userSecurity.js index ea07bb8..5caef59 100644 --- a/src/user/userSecurity.js +++ b/src/user/userSecurity.js @@ -50,7 +50,7 @@ class UserSecurity { return new Promise((resolve, reject) => { bcrypt.compare(clearPassword, hash, (error, match) => { if (match) resolve(true); - reject(false); + reject(error); }); }); } @@ -61,7 +61,7 @@ class UserSecurity { * @returns {Promise} */ static hashPassword(clearPassword) { - return new Promise(resolve => { + return new Promise((resolve, reject) => { const saltRounds = 10; bcrypt.hash(clearPassword, saltRounds, (error, hash) => { if (error) reject(error); diff --git a/src/webserver/app.js b/src/webserver/app.js index e184f5c..859c4a4 100644 --- a/src/webserver/app.js +++ b/src/webserver/app.js @@ -11,7 +11,7 @@ const mustBeAdmin = require("./middleware/mustBeAdmin"); const mustHaveAccountLinkedToPlex = require("./middleware/mustHaveAccountLinkedToPlex"); const listController = require("./controllers/list/listController"); -const tautulli = require("./controllers/user/viewHistory.js"); +const tautulli = require("./controllers/user/viewHistory"); const SettingsController = require("./controllers/user/settings"); const AuthenticatePlexAccountController = require("./controllers/user/authenticatePlexAccount"); @@ -24,7 +24,7 @@ app.use(bodyParser.json()); app.use(cookieParser()); const router = express.Router(); -const allowedOrigins = configuration.get("webserver", "origins"); +// const allowedOrigins = configuration.get("webserver", "origins"); // TODO: All JSON handling in a single router // router.use(bodyParser.json()); @@ -57,7 +57,7 @@ router.get("/", (req, res) => { }); app.use(Raven.errorHandler()); -app.use((err, req, res, next) => { +app.use((err, req, res) => { res.statusCode = 500; res.end(`${res.sentry}\n`); }); @@ -65,9 +65,9 @@ app.use((err, req, res, next) => { /** * User */ -router.post("/v1/user", require("./controllers/user/register.js")); -router.post("/v1/user/login", require("./controllers/user/login.js")); -router.post("/v1/user/logout", require("./controllers/user/logout.js")); +router.post("/v1/user", require("./controllers/user/register")); +router.post("/v1/user/login", require("./controllers/user/login")); +router.post("/v1/user/logout", require("./controllers/user/logout")); router.get( "/v1/user/settings", @@ -82,12 +82,12 @@ router.put( router.get( "/v1/user/search_history", mustBeAuthenticated, - require("./controllers/user/searchHistory.js") + require("./controllers/user/searchHistory") ); router.get( "/v1/user/requests", mustBeAuthenticated, - require("./controllers/user/requests.js") + require("./controllers/user/requests") ); router.post( @@ -125,46 +125,40 @@ router.get( /** * Seasoned */ -router.get("/v1/seasoned/all", require("./controllers/seasoned/readStrays.js")); +router.get("/v1/seasoned/all", require("./controllers/seasoned/readStrays")); router.get( "/v1/seasoned/:strayId", - require("./controllers/seasoned/strayById.js") + require("./controllers/seasoned/strayById") ); router.post( "/v1/seasoned/verify/:strayId", - require("./controllers/seasoned/verifyStray.js") + require("./controllers/seasoned/verifyStray") ); -router.get("/v2/search/", require("./controllers/search/multiSearch.js")); -router.get("/v2/search/movie", require("./controllers/search/movieSearch.js")); -router.get("/v2/search/show", require("./controllers/search/showSearch.js")); -router.get( - "/v2/search/person", - require("./controllers/search/personSearch.js") -); +router.get("/v2/search/", require("./controllers/search/multiSearch")); +router.get("/v2/search/movie", require("./controllers/search/movieSearch")); +router.get("/v2/search/show", require("./controllers/search/showSearch")); +router.get("/v2/search/person", require("./controllers/search/personSearch")); router.get("/v2/movie/now_playing", listController.nowPlayingMovies); router.get("/v2/movie/popular", listController.popularMovies); router.get("/v2/movie/top_rated", listController.topRatedMovies); router.get("/v2/movie/upcoming", listController.upcomingMovies); -router.get("/v2/movie/:id/credits", require("./controllers/movie/credits.js")); +router.get("/v2/movie/:id/credits", require("./controllers/movie/credits")); router.get( "/v2/movie/:id/release_dates", - require("./controllers/movie/releaseDates.js") + require("./controllers/movie/releaseDates") ); -router.get("/v2/movie/:id", require("./controllers/movie/info.js")); +router.get("/v2/movie/:id", require("./controllers/movie/info")); router.get("/v2/show/now_playing", listController.nowPlayingShows); router.get("/v2/show/popular", listController.popularShows); router.get("/v2/show/top_rated", listController.topRatedShows); -router.get("/v2/show/:id/credits", require("./controllers/show/credits.js")); -router.get("/v2/show/:id", require("./controllers/show/info.js")); +router.get("/v2/show/:id/credits", require("./controllers/show/credits")); +router.get("/v2/show/:id", require("./controllers/show/info")); -router.get( - "/v2/person/:id/credits", - require("./controllers/person/credits.js") -); -router.get("/v2/person/:id", require("./controllers/person/info.js")); +router.get("/v2/person/:id/credits", require("./controllers/person/credits")); +router.get("/v2/person/:id", require("./controllers/person/info")); /** * Plex @@ -174,40 +168,40 @@ router.get("/v2/plex/search", require("./controllers/plex/search")); /** * List */ -router.get("/v1/plex/search", require("./controllers/plex/searchMedia.js")); -router.get("/v1/plex/playing", require("./controllers/plex/plexPlaying.js")); -router.get("/v1/plex/request", require("./controllers/plex/searchRequest.js")); +router.get("/v1/plex/search", require("./controllers/plex/searchMedia")); +router.get("/v1/plex/playing", require("./controllers/plex/plexPlaying")); +router.get("/v1/plex/request", require("./controllers/plex/searchRequest")); router.get( "/v1/plex/request/:mediaId", - require("./controllers/plex/readRequest.js") + require("./controllers/plex/readRequest") ); router.post( "/v1/plex/request/:mediaId", - require("./controllers/plex/submitRequest.js") + require("./controllers/plex/submitRequest") ); -router.post("/v1/plex/hook", require("./controllers/plex/hookDump.js")); +router.post("/v1/plex/hook", require("./controllers/plex/hookDump")); router.get( "/v1/plex/watch-link", mustBeAuthenticated, - require("./controllers/plex/watchDirectLink.js") + require("./controllers/plex/watchDirectLink") ); /** * Requests */ -router.get("/v2/request", require("./controllers/request/fetchAllRequests.js")); -router.get("/v2/request/:id", require("./controllers/request/getRequest.js")); -router.post("/v2/request", require("./controllers/request/requestTmdbId.js")); +router.get("/v2/request", require("./controllers/request/fetchAllRequests")); +router.get("/v2/request/:id", require("./controllers/request/getRequest")); +router.post("/v2/request", require("./controllers/request/requestTmdbId")); router.get( "/v1/plex/requests/all", - require("./controllers/plex/fetchRequested.js") + require("./controllers/plex/fetchRequested") ); router.put( "/v1/plex/request/:requestId", mustBeAuthenticated, - require("./controllers/plex/updateRequested.js") + require("./controllers/plex/updateRequested") ); /** @@ -215,24 +209,24 @@ router.put( */ router.get( "/v1/pirate/search", - mustBeAuthenticated, - require("./controllers/pirate/searchTheBay.js") + mustBeAdmin, + require("./controllers/pirate/searchTheBay") ); router.post( "/v1/pirate/add", - mustBeAuthenticated, - require("./controllers/pirate/addMagnet.js") + mustBeAdmin, + require("./controllers/pirate/addMagnet") ); /** * git */ -router.post("/v1/git/dump", require("./controllers/git/dumpHook.js")); +router.post("/v1/git/dump", require("./controllers/git/dumpHook")); /** * misc */ -router.get("/v1/emoji", require("./controllers/misc/emoji.js")); +router.get("/v1/emoji", require("./controllers/misc/emoji")); // REGISTER OUR ROUTES ------------------------------- // all of our routes will be prefixed with /api diff --git a/src/webserver/controllers/git/dumpHook.js b/src/webserver/controllers/git/dumpHook.js index bed04f5..5016893 100644 --- a/src/webserver/controllers/git/dumpHook.js +++ b/src/webserver/controllers/git/dumpHook.js @@ -5,12 +5,8 @@ const gitRepository = new GitRepository(); function dumpHookController(req, res) { gitRepository .dumpHook(req.body) - .then(() => { - res.status(200); - }) - .catch(error => { - res.status(500); - }); + .then(() => res.status(200)) + .catch(() => res.status(500)); } module.exports = dumpHookController; diff --git a/src/webserver/controllers/list/listController.js b/src/webserver/controllers/list/listController.js index 9557099..2b55c59 100644 --- a/src/webserver/controllers/list/listController.js +++ b/src/webserver/controllers/list/listController.js @@ -21,17 +21,12 @@ function handleError(error, res) { if (status && message) { res.status(status).send({ success: false, message }); } else { - console.log("caught list controller error", error); res .status(500) .send({ message: "An unexpected error occured while requesting list" }); } } -function handleListResponse(response, res) { - return res.send(response).catch(error => handleError(error, res)); -} - function fetchTmdbList(req, res, listname, type) { const { page } = req.query; @@ -48,7 +43,7 @@ function fetchTmdbList(req, res, listname, type) { .catch(error => handleError(error, res)); } - handleError( + return handleError( { 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 f8ec6c1..ce33c5f 100644 --- a/src/webserver/controllers/movie/credits.js +++ b/src/webserver/controllers/movie/credits.js @@ -15,8 +15,6 @@ const movieCreditsController = (req, res) => { if (status && message) { res.status(status).send({ success: false, message }); } else { - // TODO log unhandled errors - console.log("caugth movie credits controller error", error); res.status(500).send({ message: "An unexpected error occured while requesting movie credits" }); diff --git a/src/webserver/controllers/movie/info.js b/src/webserver/controllers/movie/info.js index 5d31b7e..f22889f 100644 --- a/src/webserver/controllers/movie/info.js +++ b/src/webserver/controllers/movie/info.js @@ -1,6 +1,7 @@ const configuration = require("../../../config/configuration").getInstance(); const TMDB = require("../../../tmdb/tmdb"); const Plex = require("../../../plex/plex"); + const tmdb = new TMDB(configuration.get("tmdb", "apiKey")); const plex = new Plex(configuration.get("plex", "ip")); @@ -10,9 +11,10 @@ function handleError(error, res) { if (status && message) { res.status(status).send({ success: false, message }); } else { - console.log("caught movieinfo controller error", error); res.status(500).send({ - message: "An unexpected error occured while requesting movie info" + success: false, + message: "An unexpected error occured while requesting movie info", + errorResponse: error?.message }); } } @@ -25,21 +27,18 @@ function handleError(error, res) { */ async function movieInfoController(req, res) { const movieId = req.params.id; - let { credits, release_dates, check_existance } = req.query; - credits && credits.toLowerCase() === "true" - ? (credits = true) - : (credits = false); - release_dates && release_dates.toLowerCase() === "true" - ? (release_dates = true) - : (release_dates = false); - check_existance && check_existance.toLowerCase() === "true" - ? (check_existance = true) - : (check_existance = false); + let credits = req.query?.credits; + let releaseDates = req.query?.release_dates; + let checkExistance = req.query?.check_existance; - let tmdbQueue = [tmdb.movieInfo(movieId)]; + credits = credits.toLowerCase() === "true"; + releaseDates = releaseDates.toLowerCase() === "true"; + checkExistance = checkExistance.toLowerCase() === "true"; + + const tmdbQueue = [tmdb.movieInfo(movieId)]; if (credits) tmdbQueue.push(tmdb.movieCredits(movieId)); - if (release_dates) tmdbQueue.push(tmdb.movieReleaseDates(movieId)); + if (releaseDates) tmdbQueue.push(tmdb.movieReleaseDates(movieId)); try { const [Movie, Credits, ReleaseDates] = await Promise.all(tmdbQueue); @@ -47,19 +46,12 @@ async function movieInfoController(req, res) { const movie = Movie.createJsonResponse(); if (Credits) movie.credits = Credits.createJsonResponse(); if (ReleaseDates) - movie.release_dates = ReleaseDates.createJsonResponse().results; + movie.releaseDates = ReleaseDates.createJsonResponse().results; - if (check_existance) { + if (checkExistance) { try { movie.exists_in_plex = await plex.existsInPlex(movie); - } catch (error) { - if (error.status === 401) { - console.log("Unathorized request, check plex server LAN settings"); - } else { - console.log("Unkown error from plex!"); - } - console.log(error?.message); - } + } catch {} } res.send(movie); diff --git a/src/webserver/controllers/movie/releaseDates.js b/src/webserver/controllers/movie/releaseDates.js index 84bd43b..8097002 100644 --- a/src/webserver/controllers/movie/releaseDates.js +++ b/src/webserver/controllers/movie/releaseDates.js @@ -15,8 +15,6 @@ const movieReleaseDatesController = (req, res) => { if (status && message) { res.status(status).send({ success: false, message }); } else { - // TODO log unhandled errors : here our at tmdbReleaseError ? - console.log("caugth release dates controller error", error); res.status(500).send({ message: "An unexpected error occured while requesting movie credits" }); diff --git a/src/webserver/controllers/person/credits.js b/src/webserver/controllers/person/credits.js index 19eb5c2..414936a 100644 --- a/src/webserver/controllers/person/credits.js +++ b/src/webserver/controllers/person/credits.js @@ -15,8 +15,6 @@ const personCreditsController = (req, res) => { if (status && message) { res.status(status).send({ success: false, message }); } else { - // TODO log unhandled errors - console.log("caugth show credits controller error", error); res.status(500).send({ message: "An unexpected error occured while requesting person credits" }); diff --git a/src/webserver/controllers/person/info.js b/src/webserver/controllers/person/info.js index 87c91ff..c57436f 100644 --- a/src/webserver/controllers/person/info.js +++ b/src/webserver/controllers/person/info.js @@ -9,7 +9,6 @@ function handleError(error, res) { if (status && message) { res.status(status).send({ success: false, message }); } else { - console.log("caught personinfo controller error", error); res.status(500).send({ message: "An unexpected error occured while requesting person info." }); @@ -26,11 +25,8 @@ function handleError(error, res) { async function personInfoController(req, res) { const personId = req.params.id; let { credits } = req.query; - arguments; - credits && credits.toLowerCase() === "true" - ? (credits = true) - : (credits = false); + credits = credits.toLowerCase() === "true"; const tmdbQueue = [tmdb.personInfo(personId)]; if (credits) tmdbQueue.push(tmdb.personCredits(personId)); @@ -43,7 +39,7 @@ async function personInfoController(req, res) { return res.send(person); } catch (error) { - handleError(error, res); + return handleError(error, res); } } diff --git a/src/webserver/controllers/pirate/addMagnet.js b/src/webserver/controllers/pirate/addMagnet.js index 7dfbbdd..2ff7599 100644 --- a/src/webserver/controllers/pirate/addMagnet.js +++ b/src/webserver/controllers/pirate/addMagnet.js @@ -8,14 +8,11 @@ const PirateRepository = require("../../../pirate/pirateRepository"); function addMagnet(req, res) { - const { magnet } = req.body; - const { name } = req.body; - const { tmdb_id } = req.body; + const { magnet, name } = req.body; + const tmdbId = req.body?.tmdb_id; - PirateRepository.AddMagnet(magnet, name, tmdb_id) - .then(result => { - res.send(result); - }) + PirateRepository.AddMagnet(magnet, name, tmdbId) + .then(result => res.send(result)) .catch(error => { res.status(500).send({ success: false, message: error.message }); }); diff --git a/src/webserver/controllers/plex/fetchRequested.js b/src/webserver/controllers/plex/fetchRequested.js index 56eeaaa..14ba536 100644 --- a/src/webserver/controllers/plex/fetchRequested.js +++ b/src/webserver/controllers/plex/fetchRequested.js @@ -1,4 +1,4 @@ -const RequestRepository = require("../../../plex/requestRepository.js"); +const RequestRepository = require("../../../plex/requestRepository"); const requestRepository = new RequestRepository(); diff --git a/src/webserver/controllers/plex/hookDump.js b/src/webserver/controllers/plex/hookDump.js index 15682a0..bd39fcb 100644 --- a/src/webserver/controllers/plex/hookDump.js +++ b/src/webserver/controllers/plex/hookDump.js @@ -1,12 +1,8 @@ -/* - * @Author: KevinMidboe - * @Date: 2017-05-03 23:26:46 - * @Last Modified by: KevinMidboe - * @Last Modified time: 2018-02-06 20:54:22 - */ - function hookDumpController(req, res) { - console.log(req); + // eslint-disable-next-line no-console + console.log("plex hook dump:", req); + + res.status(200); } module.exports = hookDumpController; diff --git a/src/webserver/controllers/plex/searchRequest.js b/src/webserver/controllers/plex/searchRequest.js index c040f48..b7ce101 100644 --- a/src/webserver/controllers/plex/searchRequest.js +++ b/src/webserver/controllers/plex/searchRequest.js @@ -1,6 +1,6 @@ const SearchHistory = require("../../../searchHistory/searchHistory"); const Cache = require("../../../tmdb/cache"); -const RequestRepository = require("../../../plex/requestRepository.js"); +const RequestRepository = require("../../../plex/requestRepository"); const cache = new Cache(); const requestRepository = new RequestRepository(cache); @@ -10,8 +10,8 @@ function searchRequestController(req, res) { const { query, page, type } = req.query; const username = req.loggedInUser ? req.loggedInUser.username : null; - Promise.resolve() - .then(() => searchHistory.create(username, query)) + searchHistory + .create(username, query) .then(() => requestRepository.search(query, page, type)) .then(searchResult => { res.send(searchResult); diff --git a/src/webserver/controllers/plex/submitRequest.js b/src/webserver/controllers/plex/submitRequest.js index c06083a..6039c47 100644 --- a/src/webserver/controllers/plex/submitRequest.js +++ b/src/webserver/controllers/plex/submitRequest.js @@ -24,16 +24,14 @@ function submitRequestController(req, res) { const id = req.params.mediaId; const type = req.query.type ? req.query.type.toLowerCase() : undefined; const ip = req.headers["x-forwarded-for"] || req.connection.remoteAddress; - const user_agent = req.headers["user-agent"]; + const userAgent = req.headers["user-agent"]; const username = req.loggedInUser ? req.loggedInUser.username : null; let mediaFunction; if (type === "movie") { - console.log("movie"); mediaFunction = tmdbMovieInfo; } else if (type === "show") { - console.log("show"); mediaFunction = tmdbShowInfo; } else { res.status(422).send({ @@ -49,7 +47,7 @@ function submitRequestController(req, res) { mediaFunction(id) .then(tmdbMedia => - request.requestFromTmdb(tmdbMedia, ip, user_agent, username) + request.requestFromTmdb(tmdbMedia, ip, userAgent, username) ) .then(() => res.send({ success: true, message: "Media item successfully requested" }) diff --git a/src/webserver/controllers/plex/watchDirectLink.js b/src/webserver/controllers/plex/watchDirectLink.js index 13c4eb1..dabee21 100644 --- a/src/webserver/controllers/plex/watchDirectLink.js +++ b/src/webserver/controllers/plex/watchDirectLink.js @@ -16,7 +16,7 @@ function watchDirectLink(req, res) { plex .getDirectLinkByTitleAndYear(title, year) .then(plexDirectLink => { - if (plexDirectLink == false) + if (plexDirectLink === false) res.status(404).send({ success: true, link: null }); else res.status(200).send({ success: true, link: plexDirectLink }); }) diff --git a/src/webserver/controllers/request/fetchAllRequests.js b/src/webserver/controllers/request/fetchAllRequests.js index c6f8ae2..c20ea23 100644 --- a/src/webserver/controllers/request/fetchAllRequests.js +++ b/src/webserver/controllers/request/fetchAllRequests.js @@ -9,16 +9,10 @@ const request = new RequestRepository(); * @returns {Callback} */ function fetchAllRequests(req, res) { - const { page, filter, sort, query } = req.query; - let sort_by = sort; - let sort_direction; + const { page, filter } = req.query; - if (sort !== undefined && sort.includes(":")) { - [sort_by, sort_direction] = sort.split(":"); - } - - Promise.resolve() - .then(() => request.fetchAll(page, sort_by, sort_direction, filter, query)) + request + .fetchAll(page, filter) .then(result => res.send(result)) .catch(error => { res.status(404).send({ success: false, message: error.message }); diff --git a/src/webserver/controllers/request/requestTmdbId.js b/src/webserver/controllers/request/requestTmdbId.js index 139e560..01d5c1a 100644 --- a/src/webserver/controllers/request/requestTmdbId.js +++ b/src/webserver/controllers/request/requestTmdbId.js @@ -24,7 +24,7 @@ function requestTmdbIdController(req, res) { const { id, type } = req.body; const ip = req.headers["x-forwarded-for"] || req.connection.remoteAddress; - const user_agent = req.headers["user-agent"]; + const userAgent = req.headers["user-agent"]; const username = req.loggedInUser ? req.loggedInUser.username : null; let mediaFunction; @@ -50,7 +50,7 @@ function requestTmdbIdController(req, res) { mediaFunction(id) // .catch((error) => { console.error(error); res.status(404).send({ success: false, error: 'Id not found' }) }) .then(tmdbMedia => { - request.requestFromTmdb(tmdbMedia, ip, user_agent, username); + request.requestFromTmdb(tmdbMedia, ip, userAgent, username); // TODO enable SMS // const url = `https://request.movie?${tmdbMedia.type}=${tmdbMedia.id}`; diff --git a/src/webserver/controllers/search/movieSearch.js b/src/webserver/controllers/search/movieSearch.js index 3fa329d..8d216c4 100644 --- a/src/webserver/controllers/search/movieSearch.js +++ b/src/webserver/controllers/search/movieSearch.js @@ -14,7 +14,7 @@ const searchHistory = new SearchHistory(); function movieSearchController(req, res) { const { query, page, adult } = req.query; const username = req.loggedInUser ? req.loggedInUser.username : null; - const includeAdult = adult == "true"; + const includeAdult = adult === "true"; if (username) { searchHistory.create(username, query); @@ -29,8 +29,6 @@ function movieSearchController(req, res) { if (status && message) { res.status(status).send({ success: false, message }); } else { - // TODO log unhandled errors - console.log("caugth movie search controller error", error); res.status(500).send({ 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 5172612..b96a4f1 100644 --- a/src/webserver/controllers/search/multiSearch.js +++ b/src/webserver/controllers/search/multiSearch.js @@ -5,13 +5,6 @@ const SearchHistory = require("../../../searchHistory/searchHistory"); const tmdb = new TMDB(configuration.get("tmdb", "apiKey")); const searchHistory = new SearchHistory(); -function checkAndCreateJsonResponse(result) { - if (typeof result.createJsonResponse === "function") { - return result.createJsonResponse(); - } - return result; -} - /** * Controller: Search for multi (movies, shows and people by query and pagey * @param {Request} req http request variable @@ -21,13 +14,14 @@ function checkAndCreateJsonResponse(result) { function multiSearchController(req, res) { const { query, page, adult } = req.query; const username = req.loggedInUser ? req.loggedInUser.username : null; + const includeAdult = adult === "true"; if (username) { searchHistory.create(username, query); } return tmdb - .multiSearch(query, page, adult) + .multiSearch(query, page, includeAdult) .then(multiSearchResults => res.send(multiSearchResults)) .catch(error => { const { status, message } = error; @@ -35,8 +29,6 @@ function multiSearchController(req, res) { if (status && message) { res.status(status).send({ success: false, message }); } else { - // TODO log unhandled errors - console.log("caugth multi search controller error", error); res.status(500).send({ 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 4a3699f..c908dea 100644 --- a/src/webserver/controllers/search/personSearch.js +++ b/src/webserver/controllers/search/personSearch.js @@ -14,7 +14,7 @@ const searchHistory = new SearchHistory(); function personSearchController(req, res) { const { query, page, adult } = req.query; const username = req.loggedInUser ? req.loggedInUser.username : null; - const includeAdult = adult == "true"; + const includeAdult = adult === "true"; if (username) { searchHistory.create(username, query); @@ -29,8 +29,6 @@ function personSearchController(req, res) { if (status && message) { res.status(status).send({ success: false, message }); } else { - // TODO log unhandled errors - console.log("caugth person search controller error", error); res.status(500).send({ message: `An unexpected error occured while searching people with query: ${query}` }); diff --git a/src/webserver/controllers/search/showSearch.js b/src/webserver/controllers/search/showSearch.js index 2566010..740f763 100644 --- a/src/webserver/controllers/search/showSearch.js +++ b/src/webserver/controllers/search/showSearch.js @@ -14,7 +14,7 @@ const searchHistory = new SearchHistory(); function showSearchController(req, res) { const { query, page, adult } = req.query; const username = req.loggedInUser ? req.loggedInUser.username : null; - const includeAdult = adult == "true"; + const includeAdult = adult === "true"; if (username) { searchHistory.create(username, query); diff --git a/src/webserver/controllers/show/credits.js b/src/webserver/controllers/show/credits.js index f381efd..bed66a9 100644 --- a/src/webserver/controllers/show/credits.js +++ b/src/webserver/controllers/show/credits.js @@ -15,8 +15,6 @@ const showCreditsController = (req, res) => { if (status && message) { res.status(status).send({ success: false, message }); } else { - // TODO log unhandled errors - console.log("caugth show credits controller error", error); res.status(500).send({ message: "An unexpected error occured while requesting show credits" }); diff --git a/src/webserver/controllers/show/info.js b/src/webserver/controllers/show/info.js index 9ae9e58..4eeba4a 100644 --- a/src/webserver/controllers/show/info.js +++ b/src/webserver/controllers/show/info.js @@ -11,7 +11,6 @@ function handleError(error, res) { if (status && message) { res.status(status).send({ success: false, message }); } else { - console.log("caught showinfo controller error", error); res.status(500).send({ message: "An unexpected error occured while requesting show info." }); @@ -27,14 +26,11 @@ function handleError(error, res) { async function showInfoController(req, res) { const showId = req.params.id; - let { credits, check_existance } = req.query; + let credits = req.query?.credits; + let checkExistance = req.query?.check_existance; - credits && credits.toLowerCase() === "true" - ? (credits = true) - : (credits = false); - check_existance && check_existance.toLowerCase() === "true" - ? (check_existance = true) - : (check_existance = false); + credits = credits?.toLowerCase() === "true"; + checkExistance = checkExistance?.toLowerCase() === "true"; const tmdbQueue = [tmdb.showInfo(showId)]; if (credits) tmdbQueue.push(tmdb.showCredits(showId)); @@ -45,7 +41,12 @@ async function showInfoController(req, res) { const show = Show.createJsonResponse(); if (credits) show.credits = Credits.createJsonResponse(); - if (check_existance) show.exists_in_plex = await plex.existsInPlex(show); + if (checkExistance) { + /* eslint no-empty: ["error", { "allowEmptyCatch": true }] */ + try { + show.exists_in_plex = await plex.existsInPlex(show); + } catch {} + } res.send(show); } catch (error) { diff --git a/src/webserver/controllers/user/authenticatePlexAccount.js b/src/webserver/controllers/user/authenticatePlexAccount.js index 4e5a09c..8ac5896 100644 --- a/src/webserver/controllers/user/authenticatePlexAccount.js +++ b/src/webserver/controllers/user/authenticatePlexAccount.js @@ -1,20 +1,33 @@ +const FormData = require("form-data"); const UserRepository = require("../../../user/userRepository"); const userRepository = new UserRepository(); -const fetch = require("node-fetch"); -const FormData = require("form-data"); + +class PlexAuthenticationError extends Error { + constructor(errorResponse, statusCode) { + const message = + "Unexptected error while authenticating to plex signin api. View error response."; + super(message); + + this.errorResponse = errorResponse; + this.statusCode = statusCode; + this.success = false; + } +} function handleError(error, res) { - let { status, message, source } = error; + const status = error?.status; + let { message, source } = error; if (status && message) { if (status === 401) { - (message = "Unauthorized. Please check plex credentials."), - (source = "plex"); + message = "Unauthorized. Please check plex credentials."; + source = "plex"; } res.status(status).send({ success: false, message, source }); } else { + // eslint-disable-next-line no-console console.log("caught authenticate plex account controller error", error); res.status(500).send({ message: @@ -26,11 +39,7 @@ function handleError(error, res) { function handleResponse(response) { if (!response.ok) { - throw { - success: false, - status: response.status, - message: response.statusText - }; + throw new PlexAuthenticationError(response.statusText, response.status); } return response.json(); @@ -64,7 +73,7 @@ function link(req, res) { return plexAuthenticate(username, password) .then(plexUser => userRepository.linkPlexUserId(user.username, plexUser.id)) - .then(response => + .then(() => res.send({ success: true, message: @@ -79,7 +88,7 @@ function unlink(req, res) { return userRepository .unlinkPlexUserId(username) - .then(response => + .then(() => res.send({ success: true, message: "Successfully unlinked plex account from seasoned request." diff --git a/src/webserver/controllers/user/register.js b/src/webserver/controllers/user/register.js index 2c36f4e..655f920 100644 --- a/src/webserver/controllers/user/register.js +++ b/src/webserver/controllers/user/register.js @@ -1,12 +1,10 @@ const User = require("../../../user/user"); const Token = require("../../../user/token"); const UserSecurity = require("../../../user/userSecurity"); -const UserRepository = require("../../../user/userRepository"); const configuration = require("../../../config/configuration").getInstance(); const secret = configuration.get("authentication", "secret"); const userSecurity = new UserSecurity(); -const userRepository = new UserRepository(); const isProduction = process.env.NODE_ENV === "production"; const cookieOptions = { diff --git a/src/webserver/controllers/user/requests.js b/src/webserver/controllers/user/requests.js index e412c64..5fe0f22 100644 --- a/src/webserver/controllers/user/requests.js +++ b/src/webserver/controllers/user/requests.js @@ -1,4 +1,4 @@ -const RequestRepository = require("../../../plex/requestRepository.js"); +const RequestRepository = require("../../../plex/requestRepository"); const requestRepository = new RequestRepository(); diff --git a/src/webserver/controllers/user/settings.js b/src/webserver/controllers/user/settings.js index caf55f8..adf0d8d 100644 --- a/src/webserver/controllers/user/settings.js +++ b/src/webserver/controllers/user/settings.js @@ -23,11 +23,12 @@ const getSettingsController = (req, res) => { const updateSettingsController = (req, res) => { const username = req.loggedInUser ? req.loggedInUser.username : null; - const idempotencyKey = req.headers("Idempotency-Key"); // TODO implement better transactions - const { dark_mode, emoji } = req.body; + // const idempotencyKey = req.headers("Idempotency-Key"); // TODO implement better transactions + const emoji = req.body?.emoji; + const darkMode = req.body?.dark_mode; userRepository - .updateSettings(username, dark_mode, emoji) + .updateSettings(username, darkMode, emoji) .then(settings => { res.send({ success: true, settings }); }) diff --git a/src/webserver/controllers/user/viewHistory.js b/src/webserver/controllers/user/viewHistory.js index 55b3077..865a3d7 100644 --- a/src/webserver/controllers/user/viewHistory.js +++ b/src/webserver/controllers/user/viewHistory.js @@ -12,7 +12,7 @@ function handleError(error, res) { if (status && message) { return res.status(status).send({ success: false, message }); } - console.log("caught view history controller error", error); + return res.status(500).send({ message: "An unexpected error occured while fetching view history" }); @@ -35,10 +35,11 @@ function watchTimeStatsController(req, res) { function getPlaysByDayOfWeekController(req, res) { const user = req.loggedInUser; - const { days, y_axis } = req.query; + const days = req.query?.days; + const yAxis = req.query?.y_axis; return tautulli - .getPlaysByDayOfWeek(user.plexUserId, days, y_axis) + .getPlaysByDayOfWeek(user.plexUserId, days, yAxis) .then(data => res.send({ success: true, @@ -51,7 +52,8 @@ function getPlaysByDayOfWeekController(req, res) { function getPlaysByDaysController(req, res) { const user = req.loggedInUser; - const { days, y_axis } = req.query; + const days = req.query?.days; + const yAxis = req.query?.y_axis; if (days === undefined) { return res.status(422).send({ @@ -61,7 +63,7 @@ function getPlaysByDaysController(req, res) { } const allowedYAxisDataType = ["plays", "duration"]; - if (!allowedYAxisDataType.includes(y_axis)) { + if (!allowedYAxisDataType.includes(yAxis)) { return res.status(422).send({ success: false, message: `Y axis parameter must be one of values: [${allowedYAxisDataType}]` @@ -69,7 +71,7 @@ function getPlaysByDaysController(req, res) { } return tautulli - .getPlaysByDays(user.plexUserId, days, y_axis) + .getPlaysByDays(user.plexUserId, days, yAxis) .then(data => res.send({ success: true, diff --git a/tests/fixtures/blade_runner_2049-info-success-response.json b/tests/fixtures/blade_runner_2049-info-success-response.json index 62eb96f..4c412e0 100644 --- a/tests/fixtures/blade_runner_2049-info-success-response.json +++ b/tests/fixtures/blade_runner_2049-info-success-response.json @@ -1 +1,89 @@ -[{"adult":false,"backdrop_path":"/mVr0UiqyltcfqxbAUcLl9zWL8ah.jpg","belongs_to_collection":{"id":422837,"name":"Blade Runner Collection","poster_path":"/cWESb1o9lW2i2Z3Xllv9u40aNIk.jpg","backdrop_path":"/bSHZIvLoPBWyGLeiAudN1mXdvQX.jpg"},"budget":150000000,"genres":[{"id":9648,"name":"Mystery"},{"id":878,"name":"Science Fiction"},{"id":53,"name":"Thriller"}],"homepage":"http://bladerunnermovie.com/","id":335984,"imdb_id":"tt1856101","original_language":"en","original_title":"Blade Runner 2049","overview":"Thirty years after the events of the first film, a new blade runner, LAPD Officer K, unearths a long-buried secret that has the potential to plunge what's left of society into chaos. K's discovery leads him on a quest to find Rick Deckard, a former LAPD blade runner who has been missing for 30 years.","popularity":30.03,"poster_path":"/gajva2L0rPYkEWjzgFlBXCAVBE5.jpg","production_companies":[{"id":79529,"logo_path":"/gVN3k8emmKy4iV4KREWcCtxusZK.png","name":"Torridon Films","origin_country":"US"},{"id":101829,"logo_path":"/8IOjCvgjq0zTrtP91cWD3kL2jMK.png","name":"16:14 Entertainment","origin_country":"US"},{"id":1645,"logo_path":"/6Ry6uNBaa0IbbSs1XYIgX5DkA9r.png","name":"Scott Free Productions","origin_country":""},{"id":5,"logo_path":"/71BqEFAF4V3qjjMPCpLuyJFB9A.png","name":"Columbia Pictures","origin_country":"US"},{"id":1088,"logo_path":"/9WOE5AQUXbOtLU6GTwfjS8OMF0v.png","name":"Alcon Entertainment","origin_country":"US"},{"id":78028,"logo_path":"/sTFcDFfJaSVT3sv3DoaZDE4SlGB.png","name":"Thunderbird Entertainment","origin_country":"CA"},{"id":174,"logo_path":"/ky0xOc5OrhzkZ1N6KyUxacfQsCk.png","name":"Warner Bros. Pictures","origin_country":"US"}],"production_countries":[{"iso_3166_1":"CA","name":"Canada"},{"iso_3166_1":"US","name":"United States of America"},{"iso_3166_1":"HU","name":"Hungary"},{"iso_3166_1":"GB","name":"United Kingdom"}],"release_date":"2017-10-04","revenue":259239658,"runtime":163,"spoken_languages":[{"iso_639_1":"en","name":"English"},{"iso_639_1":"fi","name":"suomi"}],"status":"Released","tagline":"There's still a page left.","title":"Blade Runner 2049","video":false,"vote_average":7.3,"vote_count":5478}] +[ + { + "adult": false, + "backdrop_path": "/mVr0UiqyltcfqxbAUcLl9zWL8ah.jpg", + "belongs_to_collection": { + "id": 422837, + "name": "Blade Runner Collection", + "poster_path": "/cWESb1o9lW2i2Z3Xllv9u40aNIk.jpg", + "backdrop_path": "/bSHZIvLoPBWyGLeiAudN1mXdvQX.jpg" + }, + "budget": 150000000, + "genres": [ + { "id": 9648, "name": "Mystery" }, + { "id": 878, "name": "Science Fiction" }, + { "id": 53, "name": "Thriller" } + ], + "homepage": "http://bladerunnermovie.com/", + "id": 335984, + "imdb_id": "tt1856101", + "original_language": "en", + "original_title": "Blade Runner 2049", + "overview": "Thirty years after the events of the first film, a new blade runner, LAPD Officer K, unearths a long-buried secret that has the potential to plunge what's left of society into chaos. K's discovery leads him on a quest to find Rick Deckard, a former LAPD blade runner who has been missing for 30 years.", + "popularity": 30.03, + "poster_path": "/gajva2L0rPYkEWjzgFlBXCAVBE5.jpg", + "production_companies": [ + { + "id": 79529, + "logo_path": "/gVN3k8emmKy4iV4KREWcCtxusZK.png", + "name": "Torridon Films", + "origin_country": "US" + }, + { + "id": 101829, + "logo_path": "/8IOjCvgjq0zTrtP91cWD3kL2jMK.png", + "name": "16:14 Entertainment", + "origin_country": "US" + }, + { + "id": 1645, + "logo_path": "/6Ry6uNBaa0IbbSs1XYIgX5DkA9r.png", + "name": "Scott Free Productions", + "origin_country": "" + }, + { + "id": 5, + "logo_path": "/71BqEFAF4V3qjjMPCpLuyJFB9A.png", + "name": "Columbia Pictures", + "origin_country": "US" + }, + { + "id": 1088, + "logo_path": "/9WOE5AQUXbOtLU6GTwfjS8OMF0v.png", + "name": "Alcon Entertainment", + "origin_country": "US" + }, + { + "id": 78028, + "logo_path": "/sTFcDFfJaSVT3sv3DoaZDE4SlGB.png", + "name": "Thunderbird Entertainment", + "origin_country": "CA" + }, + { + "id": 174, + "logo_path": "/ky0xOc5OrhzkZ1N6KyUxacfQsCk.png", + "name": "Warner Bros. Pictures", + "origin_country": "US" + } + ], + "production_countries": [ + { "iso_3166_1": "CA", "name": "Canada" }, + { "iso_3166_1": "US", "name": "United States of America" }, + { "iso_3166_1": "HU", "name": "Hungary" }, + { "iso_3166_1": "GB", "name": "United Kingdom" } + ], + "release_date": "2017-10-04", + "revenue": 259239658, + "runtime": 163, + "spoken_languages": [ + { "iso_639_1": "en", "name": "English" }, + { "iso_639_1": "fi", "name": "suomi" } + ], + "status": "Released", + "tagline": "There's still a page left.", + "title": "Blade Runner 2049", + "video": false, + "vote_average": 7.3, + "vote_count": 5478 + } +] diff --git a/tests/fixtures/interstellar-query-movie-success-response.json b/tests/fixtures/interstellar-query-movie-success-response.json index 19162ac..d511be2 100644 --- a/tests/fixtures/interstellar-query-movie-success-response.json +++ b/tests/fixtures/interstellar-query-movie-success-response.json @@ -1 +1,193 @@ -{"results":[{"id":157336,"title":"Interstellar","year":2014,"overview":"Interstellar chronicles the adventures of a group of explorers who make use of a newly discovered wormhole to surpass the limitations on human space travel and conquer the vast distances involved in an interstellar voyage.","poster":"/nBNZadXqJSdt05SHLqgT0HuC5Gm.jpg","backdrop":"/xu9zaAevzQ5nnrsXN6JcahLnG4i.jpg","rank":8.2,"genres":null,"status":null,"tagline":null,"runtime":null,"imdb_id":null,"type":"movie","release_date":"2014-11-05T00:00:00.000Z"},{"id":301959,"title":"Interstellar: Nolan's Odyssey","year":2014,"overview":"Behind the scenes of Christopher Nolan's sci-fi drama, which stars Matthew McConaughey and Anne Hathaway","poster":"/xZwUIPqBHyJ2QIfMPANOZ1mAld6.jpg","backdrop":"/bT5jpIZE50MI0COE8pOeq0kMpQo.jpg","rank":7.9,"genres":null,"status":null,"tagline":null,"runtime":null,"imdb_id":null,"type":"movie","credits":1,"release_date":"2014-11-05T00:00:00.000Z"},{"id":398188,"title":"Interstellar Wars","year":2016,"overview":"For Millennia the Aliien force has watched and waited, a brooding menace that has now at last decided to take over the Earth. Communications systems worldwide are sent into chaos by a strange atmospheric interference and this has turned into a global phenomenon. A massive spaceship headed towards Earth and smaller spaceships began to cover entire cities around the world. Suddenly, the wonder turns into horror as the spaceships destroy the cities with energy weapons. When the world counterattacks, the alien ships are invincible to military weapons. The survivors have to use their wits to kill the aliens, or die.","poster":"/cjvTebuqD8wmhchHE286ltVcbX6.jpg","backdrop":"/yTnHa6lgIv8rNneSNBDkBe8MnZe.jpg","rank":3.8,"genres":null,"status":null,"tagline":null,"runtime":null,"imdb_id":null,"type":"movie","credits":2,"release_date":"2016-05-23T00:00:00.000Z"},{"id":287954,"title":"Lolita from Interstellar Space","year":2014,"overview":"An undeniably beautiful alien is sent to Earth to study the complex mating rituals of human beings, which leads to the young interstellar traveler experiencing the passion that surrounds the centuries-old ritual of the species.","poster":"/buoq7zYO4J3ttkEAqEMWelPDC0G.jpg","backdrop":"/mgb6tVEieDYLpQt666ACzGz5cyE.jpg","rank":7,"genres":null,"status":null,"tagline":null,"runtime":null,"imdb_id":null,"type":"movie","credits":3,"release_date":"2014-03-08T00:00:00.000Z"},{"id":336592,"title":"The Science of Interstellar","year":2014,"overview":"The science of Christopher Nolan's sci-fi, Interstellar.","poster":"/6KBD7YSBjCfgBgHwpsQo3G3GGdx.jpg","backdrop":null,"rank":7.8,"genres":null,"status":null,"tagline":null,"runtime":null,"imdb_id":null,"type":"movie","credits":4,"release_date":"2014-11-25T00:00:00.000Z"},{"id":529107,"title":"Inside Interstellar","year":2015,"overview":"Cast and crew of Christopher Nolan's 'Interstellar' discuss project origins, the film's imagery, ambitions, incorporating IMAX footage, the human element within the film, arm shooting locations outside of Calgary, the set construction and design, working with real corn, mechanical characters, including backstory, design, the blend of practical and digital effects in bringing them to life, the differences in the characters, the human performances behind the characters, the creative process behind the film's music, Icelandic locations, vehicle interiors, the processes of simulating the absence of gravity, the crucial end-film visuals and influence and inspiration for future generations","poster":"/vemBplPKQhVe5cRWL7kxtgp15Vq.jpg","backdrop":null,"rank":9,"genres":null,"status":null,"tagline":null,"runtime":null,"imdb_id":null,"type":"movie","credits":5,"release_date":"2015-03-31T00:00:00.000Z"},{"id":552531,"title":"The Prom Goer's Interstellar Excursion","year":null,"overview":"High schooler Bennett lands the prom date of his dreams, Sophie, just days before the dance. Not long after, he witnesses Sophie being abducted by aliens in the middle of the New Mexico desert.","poster":null,"backdrop":null,"rank":0,"genres":null,"status":null,"tagline":null,"runtime":null,"imdb_id":null,"type":"movie","credits":6,"release_date":null},{"id":460616,"title":"Interstellar Civil War: Shadows of the Empire","year":2018,"overview":"The Imperial Empire is attacked by an Alliance of rebels led by fanatical mystics. The ruler, Empress Nobu, the 8th generation of her family, wants to execute a bold plan to rescue a cyborg, Leah C6, trapped on the battle ravaged planet Endor. The Empress believes Leah C6 holds the secret to destroying the Alliance of Rebels before their insurgency can kill millions of citizens of the Empire. She recruits her heroic fleet commander, Lord General Luka Raan and asks him to gather a team from the Empire's elite soldiers, the Galactic Rangers. Raan assembles the team in the ruins of Endor which was attacked by depraved Rebels and outlaws led by, Kindo-Ker, a fanatical mystic in Dark Energy. The Galactic Rangers begin a desperate search to find and rescue Leah C6 before the Alliance Rebels can.","poster":"/1lDY7ZpEKOl3OaIQURjRbmFPfT8.jpg","backdrop":null,"rank":4,"genres":null,"status":null,"tagline":null,"runtime":null,"imdb_id":null,"type":"movie","credits":7,"release_date":"2018-04-15T00:00:00.000Z"},{"id":47662,"title":"Trancers 4: Jack of Swords","year":1994,"overview":"Jack is now back in the future. He had since lost Lena, and finds out that he's lost his other wife Alice to none other than Harris. While heading out for another assignment, something goes awry with the TCL chamber. Jack finds himself in a whole new dimension. He also runs across a different version of trancers. These guys seem to be in control of this planet. Jack manages to assist a rebel group known as the \"Tunnel Rats\" crush the rule of the evil Lord Calaban.","poster":"/69yr3oxBpSgua26RJkFmsm7plTG.jpg","backdrop":"/5ism2HNUGuQi5a3ajYaN9ypMQMf.jpg","rank":5.2,"genres":null,"status":null,"tagline":null,"runtime":null,"imdb_id":null,"type":"movie","credits":8,"release_date":"1994-02-02T00:00:00.000Z"},{"id":47663,"title":"Trancers 5: Sudden Deth","year":1994,"overview":"Jack Deth is back for one more round with the trancers. Jack must attempt to find his way home from the other-dimensional world of Orpheus, where magic works and the trancers were the ruling class (before Trancers IV, that is). Unfortunately, Jack's quest to find the mystical Tiamond in the Castle of Unrelenting Terror may be thwarted by the return of Caliban, king of the trancers who was thought dead.","poster":"/epMaTjPDMbgC8TbW1ZToh4RNv0i.jpg","backdrop":"/an0xpUEX1P1BI80sCpkU1pSoREx.jpg","rank":5,"genres":null,"status":null,"tagline":null,"runtime":null,"imdb_id":null,"type":"movie","credits":9,"release_date":"1994-11-04T00:00:00.000Z"},{"id":261443,"title":"Angry Planet","year":2008,"overview":"A criminal sentenced to life on a prison planet reveals his true purpose: to extract revenge on the killers who murdered his family.","poster":"/ie5luS87ess1c5VgFhbGECJTQVK.jpg","backdrop":"/u4JBwlGZN8hGeLxwu7Q0WmibACp.jpg","rank":4.5,"genres":null,"status":null,"tagline":null,"runtime":null,"imdb_id":null,"type":"movie","credits":10,"release_date":"2008-01-01T00:00:00.000Z"}],"page":1,"total_results":11,"total_pages":1} \ No newline at end of file +{ + "results": [ + { + "id": 157336, + "title": "Interstellar", + "year": 2014, + "overview": "Interstellar chronicles the adventures of a group of explorers who make use of a newly discovered wormhole to surpass the limitations on human space travel and conquer the vast distances involved in an interstellar voyage.", + "poster": "/nBNZadXqJSdt05SHLqgT0HuC5Gm.jpg", + "backdrop": "/xu9zaAevzQ5nnrsXN6JcahLnG4i.jpg", + "rank": 8.2, + "genres": null, + "status": null, + "tagline": null, + "runtime": null, + "imdb_id": null, + "type": "movie", + "release_date": "2014-11-05T00:00:00.000Z" + }, + { + "id": 301959, + "title": "Interstellar: Nolan's Odyssey", + "year": 2014, + "overview": "Behind the scenes of Christopher Nolan's sci-fi drama, which stars Matthew McConaughey and Anne Hathaway", + "poster": "/xZwUIPqBHyJ2QIfMPANOZ1mAld6.jpg", + "backdrop": "/bT5jpIZE50MI0COE8pOeq0kMpQo.jpg", + "rank": 7.9, + "genres": null, + "status": null, + "tagline": null, + "runtime": null, + "imdb_id": null, + "type": "movie", + "credits": 1, + "release_date": "2014-11-05T00:00:00.000Z" + }, + { + "id": 398188, + "title": "Interstellar Wars", + "year": 2016, + "overview": "For Millennia the Aliien force has watched and waited, a brooding menace that has now at last decided to take over the Earth. Communications systems worldwide are sent into chaos by a strange atmospheric interference and this has turned into a global phenomenon. A massive spaceship headed towards Earth and smaller spaceships began to cover entire cities around the world. Suddenly, the wonder turns into horror as the spaceships destroy the cities with energy weapons. When the world counterattacks, the alien ships are invincible to military weapons. The survivors have to use their wits to kill the aliens, or die.", + "poster": "/cjvTebuqD8wmhchHE286ltVcbX6.jpg", + "backdrop": "/yTnHa6lgIv8rNneSNBDkBe8MnZe.jpg", + "rank": 3.8, + "genres": null, + "status": null, + "tagline": null, + "runtime": null, + "imdb_id": null, + "type": "movie", + "credits": 2, + "release_date": "2016-05-23T00:00:00.000Z" + }, + { + "id": 287954, + "title": "Lolita from Interstellar Space", + "year": 2014, + "overview": "An undeniably beautiful alien is sent to Earth to study the complex mating rituals of human beings, which leads to the young interstellar traveler experiencing the passion that surrounds the centuries-old ritual of the species.", + "poster": "/buoq7zYO4J3ttkEAqEMWelPDC0G.jpg", + "backdrop": "/mgb6tVEieDYLpQt666ACzGz5cyE.jpg", + "rank": 7, + "genres": null, + "status": null, + "tagline": null, + "runtime": null, + "imdb_id": null, + "type": "movie", + "credits": 3, + "release_date": "2014-03-08T00:00:00.000Z" + }, + { + "id": 336592, + "title": "The Science of Interstellar", + "year": 2014, + "overview": "The science of Christopher Nolan's sci-fi, Interstellar.", + "poster": "/6KBD7YSBjCfgBgHwpsQo3G3GGdx.jpg", + "backdrop": null, + "rank": 7.8, + "genres": null, + "status": null, + "tagline": null, + "runtime": null, + "imdb_id": null, + "type": "movie", + "credits": 4, + "release_date": "2014-11-25T00:00:00.000Z" + }, + { + "id": 529107, + "title": "Inside Interstellar", + "year": 2015, + "overview": "Cast and crew of Christopher Nolan's 'Interstellar' discuss project origins, the film's imagery, ambitions, incorporating IMAX footage, the human element within the film, arm shooting locations outside of Calgary, the set construction and design, working with real corn, mechanical characters, including backstory, design, the blend of practical and digital effects in bringing them to life, the differences in the characters, the human performances behind the characters, the creative process behind the film's music, Icelandic locations, vehicle interiors, the processes of simulating the absence of gravity, the crucial end-film visuals and influence and inspiration for future generations", + "poster": "/vemBplPKQhVe5cRWL7kxtgp15Vq.jpg", + "backdrop": null, + "rank": 9, + "genres": null, + "status": null, + "tagline": null, + "runtime": null, + "imdb_id": null, + "type": "movie", + "credits": 5, + "release_date": "2015-03-31T00:00:00.000Z" + }, + { + "id": 552531, + "title": "The Prom Goer's Interstellar Excursion", + "year": null, + "overview": "High schooler Bennett lands the prom date of his dreams, Sophie, just days before the dance. Not long after, he witnesses Sophie being abducted by aliens in the middle of the New Mexico desert.", + "poster": null, + "backdrop": null, + "rank": 0, + "genres": null, + "status": null, + "tagline": null, + "runtime": null, + "imdb_id": null, + "type": "movie", + "credits": 6, + "release_date": null + }, + { + "id": 460616, + "title": "Interstellar Civil War: Shadows of the Empire", + "year": 2018, + "overview": "The Imperial Empire is attacked by an Alliance of rebels led by fanatical mystics. The ruler, Empress Nobu, the 8th generation of her family, wants to execute a bold plan to rescue a cyborg, Leah C6, trapped on the battle ravaged planet Endor. The Empress believes Leah C6 holds the secret to destroying the Alliance of Rebels before their insurgency can kill millions of citizens of the Empire. She recruits her heroic fleet commander, Lord General Luka Raan and asks him to gather a team from the Empire's elite soldiers, the Galactic Rangers. Raan assembles the team in the ruins of Endor which was attacked by depraved Rebels and outlaws led by, Kindo-Ker, a fanatical mystic in Dark Energy. The Galactic Rangers begin a desperate search to find and rescue Leah C6 before the Alliance Rebels can.", + "poster": "/1lDY7ZpEKOl3OaIQURjRbmFPfT8.jpg", + "backdrop": null, + "rank": 4, + "genres": null, + "status": null, + "tagline": null, + "runtime": null, + "imdb_id": null, + "type": "movie", + "credits": 7, + "release_date": "2018-04-15T00:00:00.000Z" + }, + { + "id": 47662, + "title": "Trancers 4: Jack of Swords", + "year": 1994, + "overview": "Jack is now back in the future. He had since lost Lena, and finds out that he's lost his other wife Alice to none other than Harris. While heading out for another assignment, something goes awry with the TCL chamber. Jack finds himself in a whole new dimension. He also runs across a different version of trancers. These guys seem to be in control of this planet. Jack manages to assist a rebel group known as the \"Tunnel Rats\" crush the rule of the evil Lord Calaban.", + "poster": "/69yr3oxBpSgua26RJkFmsm7plTG.jpg", + "backdrop": "/5ism2HNUGuQi5a3ajYaN9ypMQMf.jpg", + "rank": 5.2, + "genres": null, + "status": null, + "tagline": null, + "runtime": null, + "imdb_id": null, + "type": "movie", + "credits": 8, + "release_date": "1994-02-02T00:00:00.000Z" + }, + { + "id": 47663, + "title": "Trancers 5: Sudden Deth", + "year": 1994, + "overview": "Jack Deth is back for one more round with the trancers. Jack must attempt to find his way home from the other-dimensional world of Orpheus, where magic works and the trancers were the ruling class (before Trancers IV, that is). Unfortunately, Jack's quest to find the mystical Tiamond in the Castle of Unrelenting Terror may be thwarted by the return of Caliban, king of the trancers who was thought dead.", + "poster": "/epMaTjPDMbgC8TbW1ZToh4RNv0i.jpg", + "backdrop": "/an0xpUEX1P1BI80sCpkU1pSoREx.jpg", + "rank": 5, + "genres": null, + "status": null, + "tagline": null, + "runtime": null, + "imdb_id": null, + "type": "movie", + "credits": 9, + "release_date": "1994-11-04T00:00:00.000Z" + }, + { + "id": 261443, + "title": "Angry Planet", + "year": 2008, + "overview": "A criminal sentenced to life on a prison planet reveals his true purpose: to extract revenge on the killers who murdered his family.", + "poster": "/ie5luS87ess1c5VgFhbGECJTQVK.jpg", + "backdrop": "/u4JBwlGZN8hGeLxwu7Q0WmibACp.jpg", + "rank": 4.5, + "genres": null, + "status": null, + "tagline": null, + "runtime": null, + "imdb_id": null, + "type": "movie", + "credits": 10, + "release_date": "2008-01-01T00:00:00.000Z" + } + ], + "page": 1, + "total_results": 11, + "total_pages": 1 +}