Fix: Linter warnings (#137)
* Automaticly fixable eslint issues, mostly 3 -> 2 space indentation * fix: updated plex_userid to camelcase * Linted and some consistency refactor on middleware * eslint uses ecmaversion 2020 & allow empty catch rule * Started linting source files * Fixed eslint errors & improved a lot of error handling * Set 2 eslint rules as warning temporarly
This commit is contained in:
		| @@ -1,14 +1,20 @@ | |||||||
| { | { | ||||||
| 	"root": true, | 	"root": true, | ||||||
| 	"parserOptions": { | 	"parserOptions": { | ||||||
|  | 		"ecmaVersion": 2020, | ||||||
| 		"sourceType": "module" | 		"sourceType": "module" | ||||||
| 	}, | 	}, | ||||||
| 	"extends": [ | 	"extends": ["eslint-config-airbnb-base", "plugin:prettier/recommended"], | ||||||
| 		"eslint-config-airbnb-base", |  | ||||||
| 		"plugin:prettier/recommended" |  | ||||||
| 	], |  | ||||||
| 	"rules": { | 	"rules": { | ||||||
| 		"no-underscore-dangle": "off", | 		"max-classes-per-file": 1, | ||||||
| 		"no-shadow": "off" | 		"no-empty": [ | ||||||
|  | 			2, | ||||||
|  | 			{ | ||||||
|  | 				"allowEmptyCatch": true | ||||||
|  | 			} | ||||||
|  | 		], | ||||||
|  | 		"no-promise-executor-return": 1, | ||||||
|  | 		"no-shadow": "off", | ||||||
|  | 		"no-underscore-dangle": "off" | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										30
									
								
								src/cache/redis.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										30
									
								
								src/cache/redis.js
									
									
									
									
										vendored
									
									
								
							| @@ -1,32 +1,31 @@ | |||||||
| const { promisify } = require("util"); |  | ||||||
| const configuration = require("../config/configuration").getInstance(); | const configuration = require("../config/configuration").getInstance(); | ||||||
|  |  | ||||||
| let client; | let client; | ||||||
|  |  | ||||||
| try { | try { | ||||||
|   const redis = require("redis"); |   const redis = require("redis"); // eslint-disable-line global-require | ||||||
|   console.log("Trying to connect with redis.."); |   console.log("Trying to connect with redis.."); // eslint-disable-line no-console | ||||||
|   const host = configuration.get("redis", "host"); |   const host = configuration.get("redis", "host"); | ||||||
|   const port = configuration.get("redis", "port"); |   const port = configuration.get("redis", "port"); | ||||||
|  |  | ||||||
|   console.log(`redis://${host}:${port}`); |   console.log(`redis://${host}:${port}`); // eslint-disable-line no-console | ||||||
|   client = redis.createClient({ |   client = redis.createClient({ | ||||||
|     url: `redis://${host}:${port}` |     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", function (err) { |   client.on("error", () => { | ||||||
|     client.quit(); |     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 = { |     client = { | ||||||
|       get: function () { |       get(command) { | ||||||
|         console.log("redis-dummy get", arguments[0]); |         console.log(`redis-dummy get: ${command}`); // eslint-disable-line no-console | ||||||
|         return Promise.resolve(); |         return Promise.resolve(); | ||||||
|       }, |       }, | ||||||
|       set: function () { |       set(command) { | ||||||
|         console.log("redis-dummy set", arguments[0]); |         console.log(`redis-dummy set: ${command}`); // eslint-disable-line no-console | ||||||
|         return Promise.resolve(); |         return Promise.resolve(); | ||||||
|       } |       } | ||||||
|     }; |     }; | ||||||
| @@ -38,10 +37,11 @@ function set(key, value, TTL = 10800) { | |||||||
|  |  | ||||||
|   const json = JSON.stringify(value); |   const json = JSON.stringify(value); | ||||||
|   client.set(key, json, (error, reply) => { |   client.set(key, json, (error, reply) => { | ||||||
|     if (reply == "OK") { |     if (reply === "OK") { | ||||||
|       // successfully set value with key, now set TTL for key |       // successfully set value with key, now set TTL for key | ||||||
|       client.expire(key, TTL, e => { |       client.expire(key, TTL, e => { | ||||||
|         if (e) |         if (e) | ||||||
|  |           // eslint-disable-next-line no-console | ||||||
|           console.error( |           console.error( | ||||||
|             "Unexpected error while setting expiration for key:", |             "Unexpected error while setting expiration for key:", | ||||||
|             key, |             key, | ||||||
| @@ -55,14 +55,14 @@ function set(key, value, TTL = 10800) { | |||||||
|   return value; |   return value; | ||||||
| } | } | ||||||
|  |  | ||||||
| function get() { | function get(key) { | ||||||
|   return new Promise((resolve, reject) => { |   return new Promise((resolve, reject) => { | ||||||
|     client.get(key, (error, reply) => { |     client.get(key, (error, reply) => { | ||||||
|       if (reply == null) { |       if (reply === null) { | ||||||
|         return reject(); |         return reject(); | ||||||
|       } |       } | ||||||
|  |  | ||||||
|       resolve(JSON.parse(reply)); |       return resolve(JSON.parse(reply)); | ||||||
|     }); |     }); | ||||||
|   }); |   }); | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,11 +1,12 @@ | |||||||
| const path = require("path"); | const path = require("path"); | ||||||
| const Field = require("./field.js"); | const Field = require("./field"); | ||||||
|  |  | ||||||
| let instance = null; | let instance = null; | ||||||
|  |  | ||||||
| class Config { | class Config { | ||||||
|   constructor() { |   constructor() { | ||||||
|     this.location = Config.determineLocation(); |     this.location = Config.determineLocation(); | ||||||
|  |     // eslint-disable-next-line import/no-dynamic-require, global-require | ||||||
|     this.fields = require(`${this.location}`); |     this.fields = require(`${this.location}`); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,15 +1,15 @@ | |||||||
| class EnvironmentVariables { | class EnvironmentVariables { | ||||||
|    constructor(variables) { |   constructor(variables) { | ||||||
|       this.variables = variables || process.env; |     this.variables = variables || process.env; | ||||||
|    } |   } | ||||||
|  |  | ||||||
|    get(variable) { |   get(variable) { | ||||||
|       return this.variables[variable]; |     return this.variables[variable]; | ||||||
|    } |   } | ||||||
|  |  | ||||||
|    has(variable) { |   has(variable) { | ||||||
|       return this.get(variable) !== undefined; |     return this.get(variable) !== undefined; | ||||||
|    } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| module.exports = EnvironmentVariables; | module.exports = EnvironmentVariables; | ||||||
|   | |||||||
| @@ -1,49 +1,53 @@ | |||||||
| const Filters = require('./filters.js'); | const Filters = require("./filters"); | ||||||
| const EnvironmentVariables = require('./environmentVariables.js'); | const EnvironmentVariables = require("./environmentVariables"); | ||||||
|  |  | ||||||
| class Field { | class Field { | ||||||
|    constructor(rawValue, environmentVariables) { |   constructor(rawValue, environmentVariables) { | ||||||
|       this.rawValue = rawValue; |     this.rawValue = rawValue; | ||||||
|       this.filters = new Filters(rawValue); |     this.filters = new Filters(rawValue); | ||||||
|       this.valueWithoutFilters = this.filters.removeFiltersFromValue(); |     this.valueWithoutFilters = this.filters.removeFiltersFromValue(); | ||||||
|       this.environmentVariables = new EnvironmentVariables(environmentVariables); |     this.environmentVariables = new EnvironmentVariables(environmentVariables); | ||||||
|    } |   } | ||||||
|  |  | ||||||
|    get value() { |  | ||||||
|       if (this.filters.isEmpty()) { |  | ||||||
|          return this.valueWithoutFilters; |  | ||||||
|       } |  | ||||||
|  |  | ||||||
|       if (this.filters.has('base64') && !this.filters.has('env')) { |  | ||||||
|          return Field.base64Decode(this.valueWithoutFilters); |  | ||||||
|       } |  | ||||||
|  |  | ||||||
|       if (this.environmentVariables.has(this.valueWithoutFilters) && |  | ||||||
|           this.environmentVariables.get(this.valueWithoutFilters) === '') { |  | ||||||
|          return undefined; |  | ||||||
|       } |  | ||||||
|  |  | ||||||
|       if (!this.filters.has('base64') && this.filters.has('env')) { |  | ||||||
|          if (this.environmentVariables.has(this.valueWithoutFilters)) { |  | ||||||
|             return this.environmentVariables.get(this.valueWithoutFilters); |  | ||||||
|          } |  | ||||||
|          return undefined; |  | ||||||
|       } |  | ||||||
|  |  | ||||||
|       if (this.filters.has('env') && this.filters.has('base64')) { |  | ||||||
|          if (this.environmentVariables.has(this.valueWithoutFilters)) { |  | ||||||
|             const encodedEnvironmentVariable = this.environmentVariables.get(this.valueWithoutFilters); |  | ||||||
|             return Field.base64Decode(encodedEnvironmentVariable); |  | ||||||
|          } |  | ||||||
|          return undefined; |  | ||||||
|       } |  | ||||||
|  |  | ||||||
|  |   get value() { | ||||||
|  |     if (this.filters.isEmpty()) { | ||||||
|       return this.valueWithoutFilters; |       return this.valueWithoutFilters; | ||||||
|    } |     } | ||||||
|  |  | ||||||
|    static base64Decode(string) { |     if (this.filters.has("base64") && !this.filters.has("env")) { | ||||||
|       return new Buffer(string, 'base64').toString('utf-8'); |       return Field.base64Decode(this.valueWithoutFilters); | ||||||
|    } |     } | ||||||
|  |  | ||||||
|  |     if ( | ||||||
|  |       this.environmentVariables.has(this.valueWithoutFilters) && | ||||||
|  |       this.environmentVariables.get(this.valueWithoutFilters) === "" | ||||||
|  |     ) { | ||||||
|  |       return undefined; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     if (!this.filters.has("base64") && this.filters.has("env")) { | ||||||
|  |       if (this.environmentVariables.has(this.valueWithoutFilters)) { | ||||||
|  |         return this.environmentVariables.get(this.valueWithoutFilters); | ||||||
|  |       } | ||||||
|  |       return undefined; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     if (this.filters.has("env") && this.filters.has("base64")) { | ||||||
|  |       if (this.environmentVariables.has(this.valueWithoutFilters)) { | ||||||
|  |         const encodedEnvironmentVariable = this.environmentVariables.get( | ||||||
|  |           this.valueWithoutFilters | ||||||
|  |         ); | ||||||
|  |         return Field.base64Decode(encodedEnvironmentVariable); | ||||||
|  |       } | ||||||
|  |       return undefined; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return this.valueWithoutFilters; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   static base64Decode(string) { | ||||||
|  |     return Buffer.from(string, "base64").toString("utf-8"); | ||||||
|  |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| module.exports = Field; | module.exports = Field; | ||||||
|   | |||||||
| @@ -1,34 +1,34 @@ | |||||||
| class Filters { | class Filters { | ||||||
|    constructor(value) { |   constructor(value) { | ||||||
|       this.value = value; |     this.value = value; | ||||||
|       this.delimiter = '|'; |     this.delimiter = "|"; | ||||||
|    } |   } | ||||||
|  |  | ||||||
|    get filters() { |   get filters() { | ||||||
|       return this.value.split(this.delimiter).slice(0, -1); |     return this.value.split(this.delimiter).slice(0, -1); | ||||||
|    } |   } | ||||||
|  |  | ||||||
|    isEmpty() { |   isEmpty() { | ||||||
|       return !this.hasValidType() || this.value.length === 0; |     return !this.hasValidType() || this.value.length === 0; | ||||||
|    } |   } | ||||||
|  |  | ||||||
|    has(filter) { |   has(filter) { | ||||||
|       return this.filters.includes(filter); |     return this.filters.includes(filter); | ||||||
|    } |   } | ||||||
|  |  | ||||||
|    hasValidType() { |   hasValidType() { | ||||||
|       return (typeof this.value === 'string'); |     return typeof this.value === "string"; | ||||||
|    } |   } | ||||||
|  |  | ||||||
|    removeFiltersFromValue() { |   removeFiltersFromValue() { | ||||||
|       if (this.hasValidType() === false) { |     if (this.hasValidType() === false) { | ||||||
|          return this.value; |       return this.value; | ||||||
|       } |     } | ||||||
|  |  | ||||||
|       let filtersCombined = this.filters.join(this.delimiter); |     let filtersCombined = this.filters.join(this.delimiter); | ||||||
|       filtersCombined += this.filters.length >= 1 ? this.delimiter : ''; |     filtersCombined += this.filters.length >= 1 ? this.delimiter : ""; | ||||||
|       return this.value.replace(filtersCombined, ''); |     return this.value.replace(filtersCombined, ""); | ||||||
|    } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| module.exports = Filters; | module.exports = Filters; | ||||||
|   | |||||||
| @@ -72,12 +72,11 @@ class SqliteDatabase { | |||||||
|   /** |   /** | ||||||
|    * Run a SQL query against the database and retrieve the status. |    * Run a SQL query against the database and retrieve the status. | ||||||
|    * @param {String} sql SQL query |    * @param {String} sql SQL query | ||||||
|    * @param {Array} parameters in the SQL query |  | ||||||
|    * @returns {Promise} |    * @returns {Promise} | ||||||
|    */ |    */ | ||||||
|   execute(sql) { |   execute(sql) { | ||||||
|     return new Promise(resolve => { |     return new Promise((resolve, reject) => { | ||||||
|       this.connection.exec(sql, (err, database) => { |       this.connection.exec(sql, err => { | ||||||
|         if (err) { |         if (err) { | ||||||
|           console.log("ERROR: ", err); |           console.log("ERROR: ", err); | ||||||
|           reject(err); |           reject(err); | ||||||
|   | |||||||
| @@ -1,9 +1,8 @@ | |||||||
|  |  | ||||||
| class GitRepository { | class GitRepository { | ||||||
|    static dumpHook(body) { |   static dumpHook(body) { | ||||||
|       /* eslint-disable no-console */ |     /* eslint-disable no-console */ | ||||||
|       console.log(body); |     console.log(body); | ||||||
|    } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| module.exports = GitRepository; | module.exports = GitRepository; | ||||||
|   | |||||||
| @@ -1,19 +1,18 @@ | |||||||
|  |  | ||||||
| class Media { | class Media { | ||||||
|    constructor(title, year, type) { |   constructor(title, year, type) { | ||||||
|       this.title = title; |     this.title = title; | ||||||
|       this.year = year; |     this.year = year; | ||||||
|       this.type = type; |     this.type = type; | ||||||
|    } |   } | ||||||
|  |  | ||||||
|    toString() { |   toString() { | ||||||
|       return `N: ${this.title} | Y: ${this.year} | T: ${this.type}`; |     return `N: ${this.title} | Y: ${this.year} | T: ${this.type}`; | ||||||
|    } |   } | ||||||
|  |  | ||||||
|    print() { |   print() { | ||||||
|       /* eslint-disable no-console */ |     /* eslint-disable no-console */ | ||||||
|       console.log(this.toString()); |     console.log(this.toString()); | ||||||
|    } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| module.exports = Media; | module.exports = Media; | ||||||
|   | |||||||
| @@ -1,15 +1,15 @@ | |||||||
| class MediaInfo { | class MediaInfo { | ||||||
|    constructor() { |   constructor() { | ||||||
|       this.duration = undefined; |     this.duration = undefined; | ||||||
|       this.height = undefined; |     this.height = undefined; | ||||||
|       this.width = undefined; |     this.width = undefined; | ||||||
|       this.bitrate = undefined; |     this.bitrate = undefined; | ||||||
|       this.resolution = undefined; |     this.resolution = undefined; | ||||||
|       this.framerate = undefined; |     this.framerate = undefined; | ||||||
|       this.protocol = undefined; |     this.protocol = undefined; | ||||||
|       this.container = undefined; |     this.container = undefined; | ||||||
|       this.audioCodec = undefined; |     this.audioCodec = undefined; | ||||||
|    } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| module.exports = MediaInfo; | module.exports = MediaInfo; | ||||||
|   | |||||||
| @@ -1,12 +1,12 @@ | |||||||
| class Player { | class Player { | ||||||
|    constructor(device, address) { |   constructor(device, address) { | ||||||
|       this.device = device; |     this.device = device; | ||||||
|       this.ip = address; |     this.ip = address; | ||||||
|       this.platform = undefined; |     this.platform = undefined; | ||||||
|       this.product = undefined; |     this.product = undefined; | ||||||
|       this.title = undefined; |     this.title = undefined; | ||||||
|       this.state = undefined; |     this.state = undefined; | ||||||
|    } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| module.exports = Player; | module.exports = Player; | ||||||
|   | |||||||
| @@ -1,3 +1,5 @@ | |||||||
|  | /* eslint-disable camelcase */ | ||||||
|  |  | ||||||
| const Media = require("./media"); | const Media = require("./media"); | ||||||
|  |  | ||||||
| class Plex extends Media { | class Plex extends Media { | ||||||
|   | |||||||
| @@ -1,3 +1,5 @@ | |||||||
|  | /* eslint-disable camelcase */ | ||||||
|  |  | ||||||
| const Media = require("./media"); | const Media = require("./media"); | ||||||
|  |  | ||||||
| class TMDB extends Media { | class TMDB extends Media { | ||||||
|   | |||||||
| @@ -1,8 +1,8 @@ | |||||||
| class User { | class User { | ||||||
|    constructor(id, title) { |   constructor(id, title) { | ||||||
|       this.id = id; |     this.id = id; | ||||||
|       this.title = title; |     this.title = title; | ||||||
|    } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| module.exports = User; | module.exports = User; | ||||||
|   | |||||||
| @@ -1,12 +1,21 @@ | |||||||
| const request = require("request"); | const request = require("request"); | ||||||
| const configuration = require("../config/configuration").getInstance(); | 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 sendSMS = message => { | ||||||
|   const apiKey = configuration.get("sms", "apikey"); |   const apiKey = configuration.get("sms", "apikey"); | ||||||
|  |  | ||||||
|   if (!apiKey) { |   if (!apiKey) { | ||||||
|     console.warning("api key for sms not set, cannot send sms."); |     console.warning("api key for sms not set, cannot send sms."); // eslint-disable-line no-console | ||||||
|     return null; |     return Promise.resolve(null); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   const sender = configuration.get("sms", "sender"); |   const sender = configuration.get("sms", "sender"); | ||||||
| @@ -23,10 +32,9 @@ const sendSMS = message => { | |||||||
|           recipients: [{ msisdn: `47${recipient}` }] |           recipients: [{ msisdn: `47${recipient}` }] | ||||||
|         } |         } | ||||||
|       }, |       }, | ||||||
|       function (err, r, body) { |       (err, r, body) => { | ||||||
|         console.log(err ? err : body); |         if (err) reject(new SMSUnexpectedError(err || body)); | ||||||
|         console.log("sms provider response:", body); |         resolve(body); | ||||||
|         resolve(); |  | ||||||
|       } |       } | ||||||
|     ); |     ); | ||||||
|   }); |   }); | ||||||
|   | |||||||
| @@ -1,4 +1,3 @@ | |||||||
| const assert = require("assert"); |  | ||||||
| const http = require("http"); | const http = require("http"); | ||||||
| const { URL } = require("url"); | const { URL } = require("url"); | ||||||
| const PythonShell = require("python-shell"); | const PythonShell = require("python-shell"); | ||||||
| @@ -8,12 +7,12 @@ const establishedDatabase = require("../database/database"); | |||||||
| const cache = require("../cache/redis"); | const cache = require("../cache/redis"); | ||||||
|  |  | ||||||
| function getMagnetFromURL(url) { | function getMagnetFromURL(url) { | ||||||
|   return new Promise((resolve, reject) => { |   return new Promise(resolve => { | ||||||
|     const options = new URL(url); |     const options = new URL(url); | ||||||
|     if (options.protocol.includes("magnet")) resolve(url); |     if (options.protocol.includes("magnet")) resolve(url); | ||||||
|  |  | ||||||
|     http.get(options, res => { |     http.get(options, res => { | ||||||
|       if (res.statusCode == 301 || res.statusCode == 302) { |       if (res.statusCode === 301 || res.statusCode === 302) { | ||||||
|         resolve(res.headers.location); |         resolve(res.headers.location); | ||||||
|       } |       } | ||||||
|     }); |     }); | ||||||
| @@ -43,13 +42,14 @@ async function callPythonAddMagnet(url, callback) { | |||||||
|       PythonShell.run("deluge_cli.py", options, callback); |       PythonShell.run("deluge_cli.py", options, callback); | ||||||
|     }) |     }) | ||||||
|     .catch(err => { |     .catch(err => { | ||||||
|       console.log(err); |  | ||||||
|       throw new Error(err); |       throw new Error(err); | ||||||
|     }); |     }); | ||||||
| } | } | ||||||
|  |  | ||||||
| async function SearchPiratebay(query) { | async function SearchPiratebay(_query) { | ||||||
|   if (query && query.includes("+")) { |   let query = String(_query); | ||||||
|  |  | ||||||
|  |   if (query?.includes("+")) { | ||||||
|     query = query.replace("+", "%20"); |     query = query.replace("+", "%20"); | ||||||
|   } |   } | ||||||
|  |  | ||||||
| @@ -62,7 +62,7 @@ async function SearchPiratebay(query) { | |||||||
|       .catch(() => |       .catch(() => | ||||||
|         find(query, (err, results) => { |         find(query, (err, results) => { | ||||||
|           if (err) { |           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")); |             reject(Error("There was a error when searching for torrents")); | ||||||
|           } |           } | ||||||
|  |  | ||||||
| @@ -76,8 +76,8 @@ async function SearchPiratebay(query) { | |||||||
|   ); |   ); | ||||||
| } | } | ||||||
|  |  | ||||||
| async function AddMagnet(magnet, name, tmdb_id) { | function AddMagnet(magnet, name, tmdbId) { | ||||||
|   return await new Promise((resolve, reject) => |   return new Promise((resolve, reject) => | ||||||
|     callPythonAddMagnet(magnet, (err, results) => { |     callPythonAddMagnet(magnet, (err, results) => { | ||||||
|       if (err) { |       if (err) { | ||||||
|         /* eslint-disable no-console */ |         /* eslint-disable no-console */ | ||||||
| @@ -87,13 +87,12 @@ async function AddMagnet(magnet, name, tmdb_id) { | |||||||
|       /* eslint-disable no-console */ |       /* eslint-disable no-console */ | ||||||
|       console.log("result/error:", err, results); |       console.log("result/error:", err, results); | ||||||
|  |  | ||||||
|       database = establishedDatabase; |       const database = establishedDatabase; | ||||||
|       insert_query = |       const insertQuery = | ||||||
|         "INSERT INTO requested_torrent(magnet,torrent_name,tmdb_id) \ |         "INSERT INTO requested_torrent(magnet,torrent_name,tmdb_id) VALUES (?,?,?)"; | ||||||
|          VALUES (?,?,?)"; |  | ||||||
|  |  | ||||||
|       let response = database.run(insert_query, [magnet, name, tmdb_id]); |       const response = database.run(insertQuery, [magnet, name, tmdbId]); | ||||||
|       console.log("Response from requsted_torrent insert: " + response); |       console.log(`Response from requsted_torrent insert: ${response}`); | ||||||
|  |  | ||||||
|       resolve({ success: true }); |       resolve({ success: true }); | ||||||
|     }) |     }) | ||||||
|   | |||||||
| @@ -1,3 +1,5 @@ | |||||||
|  | /* eslint-disable camelcase */ | ||||||
|  |  | ||||||
| const Plex = require("../media_classes/plex"); | const Plex = require("../media_classes/plex"); | ||||||
|  |  | ||||||
| function translateAdded(date_string) { | function translateAdded(date_string) { | ||||||
| @@ -5,10 +7,10 @@ function translateAdded(date_string) { | |||||||
| } | } | ||||||
|  |  | ||||||
| function convertPlexToSeasoned(plex) { | function convertPlexToSeasoned(plex) { | ||||||
|   const title = plex.title; |   const { title } = plex; | ||||||
|   const year = plex.year; |   const { year } = plex; | ||||||
|   const type = plex.type; |   const { type } = plex; | ||||||
|   const summary = plex.summary; |   const { summary } = plex; | ||||||
|   const poster_path = plex.thumb; |   const poster_path = plex.thumb; | ||||||
|   const background_path = plex.art; |   const background_path = plex.art; | ||||||
|   const added = translateAdded(plex.addedAt); |   const added = translateAdded(plex.addedAt); | ||||||
| @@ -27,7 +29,6 @@ function convertPlexToSeasoned(plex) { | |||||||
|     seasons, |     seasons, | ||||||
|     episodes |     episodes | ||||||
|   ); |   ); | ||||||
|   // seasoned.print(); |  | ||||||
|   return seasoned; |   return seasoned; | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,7 +0,0 @@ | |||||||
| const configuration = require("../config/configuration").getInstance(); |  | ||||||
|  |  | ||||||
| function hookDumpController(req, res) { |  | ||||||
|   console.log(req); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| module.exports = hookDumpController; |  | ||||||
| @@ -1,25 +1,25 @@ | |||||||
| class mailTemplate { | class mailTemplate { | ||||||
|    constructor(mediaItem) { |   constructor(mediaItem) { | ||||||
|       this.mediaItem = mediaItem; |     this.mediaItem = mediaItem; | ||||||
|       this.posterURL = 'https://image.tmdb.org/t/p/w600'; |     this.posterURL = "https://image.tmdb.org/t/p/w600"; | ||||||
|    } |   } | ||||||
|  |  | ||||||
|    toText() { |   toText() { | ||||||
|       return `${this.mediaItem.title} (${this.mediaItem.year})`; // plain text body |     return `${this.mediaItem.title} (${this.mediaItem.year})`; // plain text body | ||||||
|    } |   } | ||||||
|  |  | ||||||
|    toHTML() { |   toHTML() { | ||||||
|       const info = { |     const info = { | ||||||
|          name: this.mediaItem.title, |       name: this.mediaItem.title, | ||||||
|          year: `(${this.mediaItem.year})`, |       year: `(${this.mediaItem.year})`, | ||||||
|          poster: this.posterURL + this.mediaItem.poster, |       poster: this.posterURL + this.mediaItem.poster | ||||||
|       }; |     }; | ||||||
|  |  | ||||||
|       return ` |     return ` | ||||||
|          <h1>${info.name} ${info.year}</h1> |          <h1>${info.name} ${info.year}</h1> | ||||||
|          <img src="${info.poster}"> |          <img src="${info.poster}"> | ||||||
|       `; |       `; | ||||||
|    } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| module.exports = mailTemplate; | module.exports = mailTemplate; | ||||||
|   | |||||||
							
								
								
									
										203
									
								
								src/plex/plex.js
									
									
									
									
									
								
							
							
						
						
									
										203
									
								
								src/plex/plex.js
									
									
									
									
									
								
							| @@ -2,58 +2,101 @@ const fetch = require("node-fetch"); | |||||||
| const convertPlexToMovie = require("./convertPlexToMovie"); | const convertPlexToMovie = require("./convertPlexToMovie"); | ||||||
| const convertPlexToShow = require("./convertPlexToShow"); | const convertPlexToShow = require("./convertPlexToShow"); | ||||||
| const convertPlexToEpisode = require("./convertPlexToEpisode"); | const convertPlexToEpisode = require("./convertPlexToEpisode"); | ||||||
|  |  | ||||||
| const { Movie, Show, Person } = require("../tmdb/types"); |  | ||||||
|  |  | ||||||
| const redisCache = require("../cache/redis"); | 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) { |     super(message); | ||||||
|   return encodeURIComponent(str).replace(/[!'()*]/g, function (c) { |     this.statusCode = 408; | ||||||
|     return "%" + c.charCodeAt(0).toString(16).toUpperCase(); |   } | ||||||
|   }); |  | ||||||
| } | } | ||||||
|  |  | ||||||
| const matchingTitleAndYear = (plex, tmdb) => { | class PlexUnexpectedError extends Error { | ||||||
|   let matchingTitle, matchingYear; |   constructor(plexError = null) { | ||||||
|  |     const message = "Unexpected plex error occured."; | ||||||
|  |  | ||||||
|   if (plex["title"] != null && tmdb["title"] != null) { |     super(message); | ||||||
|  |     this.statusCode = 500; | ||||||
|  |     this.plexError = plexError; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | const sanitize = string => string.toLowerCase().replace(/[^\w]/gi, ""); | ||||||
|  | const matchingTitleAndYear = (plex, tmdb) => { | ||||||
|  |   let matchingTitle; | ||||||
|  |   let matchingYear; | ||||||
|  |  | ||||||
|  |   if (plex?.title && tmdb?.title) { | ||||||
|     const plexTitle = sanitize(plex.title); |     const plexTitle = sanitize(plex.title); | ||||||
|     const tmdbTitle = sanitize(tmdb.title); |     const tmdbTitle = sanitize(tmdb.title); | ||||||
|     matchingTitle = plexTitle == tmdbTitle; |     matchingTitle = plexTitle === tmdbTitle; | ||||||
|     matchingTitle = matchingTitle |     matchingTitle = matchingTitle || plexTitle.startsWith(tmdbTitle); | ||||||
|       ? matchingTitle |  | ||||||
|       : plexTitle.startsWith(tmdbTitle); |  | ||||||
|   } else matchingTitle = false; |   } else matchingTitle = false; | ||||||
|  |  | ||||||
|   if (plex["year"] != null && tmdb["year"] != null) |   if (plex?.year && tmdb?.year) matchingYear = plex.year === tmdb.year; | ||||||
|     matchingYear = plex.year == tmdb.year; |  | ||||||
|   else matchingYear = false; |   else matchingYear = false; | ||||||
|  |  | ||||||
|   return matchingTitle && matchingYear; |   return matchingTitle && matchingYear; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| const successfullResponse = response => { | function fixedEncodeURIComponent(str) { | ||||||
|   if (response && response["MediaContainer"]) return response; |   return encodeURIComponent(str).replace(/[!'()*]/g, c => { | ||||||
|  |     return `%${c.charCodeAt(0).toString(16).toUpperCase()}`; | ||||||
|  |   }); | ||||||
|  | } | ||||||
|  |  | ||||||
|   if ( | function matchTmdbAndPlexMedia(plex, tmdb) { | ||||||
|     response == null || |   let match; | ||||||
|     response["status"] == null || |  | ||||||
|     response["statusText"] == null |  | ||||||
|   ) { |  | ||||||
|     throw Error("Unable to decode response"); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   const { status, statusText } = response; |   if (plex === null || tmdb === null) return false; | ||||||
|  |  | ||||||
|   if (status === 200) { |   if (plex instanceof Array) { | ||||||
|     return response.json(); |     const possibleMatches = plex.map(plexItem => | ||||||
|  |       matchingTitleAndYear(plexItem, tmdb) | ||||||
|  |     ); | ||||||
|  |     match = possibleMatches.includes(true); | ||||||
|   } else { |   } else { | ||||||
|     throw { message: statusText, status: status }; |     match = matchingTitleAndYear(plex, tmdb); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   return match; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | const successfullResponse = response => { | ||||||
|  |   const { status, statusText } = response; | ||||||
|  |   if (status !== 200) { | ||||||
|  |     throw new PlexUnexpectedError(statusText); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   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 { | class Plex { | ||||||
|   constructor(ip, port = 32400, cache = null) { |   constructor(ip, port = 32400, cache = null) { | ||||||
|     this.plexIP = ip; |     this.plexIP = ip; | ||||||
| @@ -77,50 +120,29 @@ class Plex { | |||||||
|     return new Promise((resolve, reject) => |     return new Promise((resolve, reject) => | ||||||
|       this.cache |       this.cache | ||||||
|         .get(cacheKey) |         .get(cacheKey) | ||||||
|         .then(machineInfo => resolve(machineInfo["machineIdentifier"])) |         .then(machineInfo => resolve(machineInfo?.machineIdentifier)) | ||||||
|         .catch(() => fetch(url, options)) |         .catch(() => fetch(url, options)) | ||||||
|         .then(response => response.json()) |         .then(response => response.json()) | ||||||
|         .then(machineInfo => |         .then(machineInfo => | ||||||
|           this.cache.set(cacheKey, machineInfo["MediaContainer"], 2628000) |           this.cache.set(cacheKey, machineInfo.MediaContainer, 2628000) | ||||||
|         ) |         ) | ||||||
|         .then(machineInfo => resolve(machineInfo["machineIdentifier"])) |         .then(machineInfo => resolve(machineInfo?.machineIdentifier)) | ||||||
|         .catch(error => { |         .catch(error => { | ||||||
|           if (error != undefined && error.type === "request-timeout") { |           if (error?.type === "request-timeout") { | ||||||
|             reject({ |             reject(new PlexRequestTimeoutError()); | ||||||
|               message: "Plex did not respond", |  | ||||||
|               status: 408, |  | ||||||
|               success: false |  | ||||||
|             }); |  | ||||||
|           } |           } | ||||||
|  |  | ||||||
|           reject(error); |           reject(new PlexUnexpectedError()); | ||||||
|         }) |         }) | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   matchTmdbAndPlexMedia(plex, tmdb) { |  | ||||||
|     let match; |  | ||||||
|  |  | ||||||
|     if (plex == null || tmdb == null) return false; |  | ||||||
|  |  | ||||||
|     if (plex instanceof Array) { |  | ||||||
|       let possibleMatches = plex.map(plexItem => |  | ||||||
|         matchingTitleAndYear(plexItem, tmdb) |  | ||||||
|       ); |  | ||||||
|       match = possibleMatches.includes(true); |  | ||||||
|     } else { |  | ||||||
|       match = matchingTitleAndYear(plex, tmdb); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     return match; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   async existsInPlex(tmdb) { |   async existsInPlex(tmdb) { | ||||||
|     const plexMatch = await this.findPlexItemByTitleAndYear( |     const plexMatch = await this.findPlexItemByTitleAndYear( | ||||||
|       tmdb.title, |       tmdb.title, | ||||||
|       tmdb.year |       tmdb.year | ||||||
|     ); |     ); | ||||||
|     return plexMatch ? true : false; |     return !!plexMatch; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   findPlexItemByTitleAndYear(title, year) { |   findPlexItemByTitleAndYear(title, year) { | ||||||
| @@ -128,10 +150,10 @@ class Plex { | |||||||
|  |  | ||||||
|     return this.search(title).then(plexResults => { |     return this.search(title).then(plexResults => { | ||||||
|       const matchesInPlex = plexResults.map(plex => |       const matchesInPlex = plexResults.map(plex => | ||||||
|         this.matchTmdbAndPlexMedia(plex, query) |         matchTmdbAndPlexMedia(plex, query) | ||||||
|       ); |       ); | ||||||
|       const matchesIndex = matchesInPlex.findIndex(el => el === true); |       const matchesIndex = matchesInPlex.findIndex(el => el === true); | ||||||
|       return matchesInPlex != -1 ? plexResults[matchesIndex] : null; |       return matchesInPlex !== -1 ? plexResults[matchesIndex] : null; | ||||||
|     }); |     }); | ||||||
|   } |   } | ||||||
|  |  | ||||||
| @@ -147,10 +169,10 @@ class Plex { | |||||||
|       matchingObjectInPlexPromise |       matchingObjectInPlexPromise | ||||||
|     ]).then(([machineIdentifier, matchingObjectInPlex]) => { |     ]).then(([machineIdentifier, matchingObjectInPlex]) => { | ||||||
|       if ( |       if ( | ||||||
|         matchingObjectInPlex == false || |         matchingObjectInPlex === false || | ||||||
|         matchingObjectInPlex == null || |         matchingObjectInPlex === null || | ||||||
|         matchingObjectInPlex["key"] == null || |         matchingObjectInPlex.key === null || | ||||||
|         machineIdentifier == null |         machineIdentifier === null | ||||||
|       ) |       ) | ||||||
|         return false; |         return false; | ||||||
|  |  | ||||||
| @@ -176,18 +198,14 @@ class Plex { | |||||||
|         .catch(() => fetch(url, options)) // else fetch fresh data |         .catch(() => fetch(url, options)) // else fetch fresh data | ||||||
|         .then(successfullResponse) |         .then(successfullResponse) | ||||||
|         .then(results => this.cache.set(cacheKey, results, 21600)) // 6 hours |         .then(results => this.cache.set(cacheKey, results, 21600)) // 6 hours | ||||||
|         .then(this.mapResults) |         .then(mapResults) | ||||||
|         .then(resolve) |         .then(resolve) | ||||||
|         .catch(error => { |         .catch(error => { | ||||||
|           if (error != undefined && error.type === "request-timeout") { |           if (error?.type === "request-timeout") { | ||||||
|             reject({ |             reject(new PlexRequestTimeoutError()); | ||||||
|               message: "Plex did not respond", |  | ||||||
|               status: 408, |  | ||||||
|               success: false |  | ||||||
|             }); |  | ||||||
|           } |           } | ||||||
|  |  | ||||||
|           reject(error); |           reject(new PlexUnexpectedError()); | ||||||
|         }) |         }) | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
| @@ -200,40 +218,13 @@ class Plex { | |||||||
|     const query = title; |     const query = title; | ||||||
|     const cacheKey = `${this.cacheTags.search}/${query}*`; |     const cacheKey = `${this.cacheTags.search}/${query}*`; | ||||||
|  |  | ||||||
|     this.cache.del( |     this.cache.del(cacheKey, (error, response) => { | ||||||
|       cacheKey, |       // TODO improve cache key matching by lowercasing it on the backend. | ||||||
|       (error, |       // what do we actually need to check for if the key was deleted or not | ||||||
|       response => { |       // it might be an error or another response code. | ||||||
|         if (response == 1) return true; |       console.log("Unable to delete, key might not exists"); | ||||||
|  |       return response === 1; | ||||||
|         // 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 == null || |  | ||||||
|       response.MediaContainer == null || |  | ||||||
|       response.MediaContainer.Hub == null |  | ||||||
|     ) { |  | ||||||
|       return []; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     return response.MediaContainer.Hub.filter(category => category.size > 0) |  | ||||||
|       .map(category => { |  | ||||||
|         if (category.type === "movie") { |  | ||||||
|           return category.Metadata; |  | ||||||
|         } else if (category.type === "show") { |  | ||||||
|           return category.Metadata.map(convertPlexToShow); |  | ||||||
|         } else if (category.type === "episode") { |  | ||||||
|           return category.Metadata.map(convertPlexToEpisode); |  | ||||||
|         } |  | ||||||
|       }) |  | ||||||
|       .filter(result => result !== undefined); |  | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,18 +1,55 @@ | |||||||
|  | const rp = require("request-promise"); | ||||||
| const convertPlexToSeasoned = require("./convertPlexToSeasoned"); | const convertPlexToSeasoned = require("./convertPlexToSeasoned"); | ||||||
| const convertPlexToStream = require("./convertPlexToStream"); | const convertPlexToStream = require("./convertPlexToStream"); | ||||||
| const rp = require("request-promise"); |  | ||||||
|  | // 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 { | class PlexRepository { | ||||||
|   constructor(plexIP) { |   constructor(plexIP) { | ||||||
|     this.plexIP = plexIP; |     this.plexIP = plexIP; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   inPlex(tmdbResult) { |   inPlex(_tmdbResult) { | ||||||
|     return Promise.resolve() |     const tmdbResult = { ..._tmdbResult }; | ||||||
|       .then(() => this.search(tmdbResult.title)) |     this.search(tmdbResult.title) | ||||||
|       .then(plexResult => this.compareTmdbToPlex(tmdbResult, plexResult)) |       .then(plexResult => addAttributeIfTmdbInPlex(tmdbResult, plexResult)) | ||||||
|       .catch(error => { |       .catch(() => { | ||||||
|         console.log(error); |         /** | ||||||
|  |          * 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; |         tmdbResult.matchedInPlex = false; | ||||||
|         return tmdbResult; |         return tmdbResult; | ||||||
|       }); |       }); | ||||||
| @@ -24,7 +61,7 @@ class PlexRepository { | |||||||
|       `http://${this.plexIP}:32400/search?query=${queryUri}` |       `http://${this.plexIP}:32400/search?query=${queryUri}` | ||||||
|     ); |     ); | ||||||
|     const options = { |     const options = { | ||||||
|       uri: uri, |       uri, | ||||||
|       headers: { |       headers: { | ||||||
|         Accept: "application/json" |         Accept: "application/json" | ||||||
|       }, |       }, | ||||||
| @@ -32,50 +69,13 @@ class PlexRepository { | |||||||
|     }; |     }; | ||||||
|  |  | ||||||
|     return rp(options) |     return rp(options) | ||||||
|       .catch(error => { |       .then(result => mapResults(result)) | ||||||
|         console.log(error); |  | ||||||
|         throw new Error("Unable to search plex."); |  | ||||||
|       }) |  | ||||||
|       .then(result => this.mapResults(result)) |  | ||||||
|       .then(([mappedResults, resultCount]) => ({ |       .then(([mappedResults, resultCount]) => ({ | ||||||
|         results: mappedResults, |         results: mappedResults, | ||||||
|         total_results: resultCount |         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() { |   nowPlaying() { | ||||||
|     const options = { |     const options = { | ||||||
|       uri: `http://${this.plexIP}:32400/status/sessions`, |       uri: `http://${this.plexIP}:32400/status/sessions`, | ||||||
|   | |||||||
| @@ -28,15 +28,15 @@ class RequestRepository { | |||||||
|     }; |     }; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   search(query, type, page) { |   static search(query, type, page) { | ||||||
|     return Promise.resolve() |     return tmdb | ||||||
|       .then(() => tmdb.search(query, type, page)) |       .search(query, type, page) | ||||||
|       .catch(error => Error(`error in the house${error}`)); |       .catch(error => Error(`error in the house${error}`)); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   lookup(identifier, type = "movie") { |   lookup(identifier, type = "movie") { | ||||||
|     return Promise.resolve() |     return tmdb | ||||||
|       .then(() => tmdb.lookup(identifier, type)) |       .lookup(identifier, type) | ||||||
|       .then(tmdbMovie => this.checkID(tmdbMovie)) |       .then(tmdbMovie => this.checkID(tmdbMovie)) | ||||||
|       .then(tmdbMovie => plexRepository.inPlex(tmdbMovie)) |       .then(tmdbMovie => plexRepository.inPlex(tmdbMovie)) | ||||||
|       .catch(error => { |       .catch(error => { | ||||||
| @@ -44,19 +44,17 @@ class RequestRepository { | |||||||
|       }); |       }); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   checkID(tmdbMovie) { |   checkID(_tmdbMovie) { | ||||||
|     return Promise.resolve() |     const tmdbMovie = _tmdbMovie; | ||||||
|       .then(() => |  | ||||||
|         this.database.get(this.queries.checkIfIdRequested, [ |     return this.database | ||||||
|           tmdbMovie.id, |       .get(this.queries.checkIfIdRequested, [tmdbMovie.id, tmdbMovie.type]) | ||||||
|           tmdbMovie.type |  | ||||||
|         ]) |  | ||||||
|       ) |  | ||||||
|       .then((result, error) => { |       .then((result, error) => { | ||||||
|         if (error) { |         if (error) { | ||||||
|           throw new Error(error); |           throw new Error(error); | ||||||
|         } |         } | ||||||
|         tmdbMovie.requested = result ? true : false; |  | ||||||
|  |         tmdbMovie.requested = !!result; | ||||||
|         return tmdbMovie; |         return tmdbMovie; | ||||||
|       }); |       }); | ||||||
|   } |   } | ||||||
| @@ -66,45 +64,42 @@ class RequestRepository { | |||||||
|    * @param {identifier, type} the id of the media object and type of media must be defined |    * @param {identifier, type} the id of the media object and type of media must be defined | ||||||
|    * @returns {Promise} If nothing has gone wrong. |    * @returns {Promise} If nothing has gone wrong. | ||||||
|    */ |    */ | ||||||
|   sendRequest(identifier, type, ip, user_agent, user) { |   sendRequest(identifier, type, ip, userAgent, user) { | ||||||
|     return Promise.resolve() |     return tmdb.lookup(identifier, type).then(movie => { | ||||||
|       .then(() => tmdb.lookup(identifier, type)) |       const username = user === undefined ? undefined : user.username; | ||||||
|       .then(movie => { |       // Add request to database | ||||||
|         const username = user === undefined ? undefined : user.username; |       return this.database.run(this.queries.insertRequest, [ | ||||||
|         // Add request to database |         movie.id, | ||||||
|         return this.database.run(this.queries.insertRequest, [ |         movie.title, | ||||||
|           movie.id, |         movie.year, | ||||||
|           movie.title, |         movie.poster_path, | ||||||
|           movie.year, |         movie.background_path, | ||||||
|           movie.poster_path, |         username, | ||||||
|           movie.background_path, |         ip, | ||||||
|           username, |         userAgent, | ||||||
|           ip, |         movie.type | ||||||
|           user_agent, |       ]); | ||||||
|           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 |  | ||||||
|         ]); |  | ||||||
|       else return this.database.all(this.queries.fetchRequestedItems, page); |  | ||||||
|     }); |     }); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   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) { |   userRequests(username) { | ||||||
|     return Promise.resolve() |     return this.database | ||||||
|       .then(() => this.database.all(this.queries.userRequests, username)) |       .all(this.queries.userRequests, username) | ||||||
|       .catch(error => { |       .catch(error => { | ||||||
|         if (String(error).includes("no such column")) { |         if (String(error).includes("no such column")) { | ||||||
|           throw new Error("Username not found"); |           throw new Error("Username not found"); | ||||||
| @@ -113,8 +108,11 @@ class RequestRepository { | |||||||
|       }) |       }) | ||||||
|       .then(result => { |       .then(result => { | ||||||
|         // TODO do a correct mapping before sending, not just a dump of the database |         // TODO do a correct mapping before sending, not just a dump of the database | ||||||
|         result.map(item => (item.poster = item.poster_path)); |         return result.map(_item => { | ||||||
|         return result; |           const item = { ..._item }; | ||||||
|  |           item.poster = item.poster_path; | ||||||
|  |           return item; | ||||||
|  |         }); | ||||||
|       }); |       }); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,14 +1,14 @@ | |||||||
| class convertStreamToPlayback { | class convertStreamToPlayback { | ||||||
|    constructor(plexStream) { |   constructor(plexStream) { | ||||||
|       this.bitrate = plexStream.bitrate; |     this.bitrate = plexStream.bitrate; | ||||||
|       this.width = plexStream.width; |     this.width = plexStream.width; | ||||||
|       this.height = plexStream.height; |     this.height = plexStream.height; | ||||||
|       this.decision = plexStream.decision; |     this.decision = plexStream.decision; | ||||||
|       this.audioProfile = plexStream.audioProfile; |     this.audioProfile = plexStream.audioProfile; | ||||||
|       this.videoProfile = plexStream.videoProfile; |     this.videoProfile = plexStream.videoProfile; | ||||||
|       this.duration = plexStream.duration; |     this.duration = plexStream.duration; | ||||||
|       this.container = plexStream.container; |     this.container = plexStream.container; | ||||||
|    } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| module.exports = convertStreamToPlayback; | module.exports = convertStreamToPlayback; | ||||||
|   | |||||||
| @@ -9,7 +9,7 @@ class Episode { | |||||||
|     this.rating = null; |     this.rating = null; | ||||||
|     this.views = null; |     this.views = null; | ||||||
|     this.aired = null; |     this.aired = null; | ||||||
|     this.type = 'episode'; |     this.type = "episode"; | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -5,7 +5,7 @@ class Movie { | |||||||
|     this.summary = null; |     this.summary = null; | ||||||
|     this.rating = null; |     this.rating = null; | ||||||
|     this.tagline = null; |     this.tagline = null; | ||||||
|     this.type = 'movie'; |     this.type = "movie"; | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,9 +1,17 @@ | |||||||
| const assert = require("assert"); | const assert = require("assert"); | ||||||
| const configuration = require("../config/configuration").getInstance(); | // const configuration = require("../config/configuration").getInstance(); | ||||||
| const TMDB = require("../tmdb/tmdb"); | // const TMDB = require("../tmdb/tmdb"); | ||||||
| const tmdb = new TMDB(configuration.get("tmdb", "apiKey")); |  | ||||||
| const establishedDatabase = require("../database/database"); | const establishedDatabase = require("../database/database"); | ||||||
| const utils = require("./utils"); |  | ||||||
|  | // 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 { | class RequestRepository { | ||||||
|   constructor(database) { |   constructor(database) { | ||||||
| @@ -18,85 +26,24 @@ class RequestRepository { | |||||||
|         'select count(*) as totalRequests from requests where status != "downloaded"', |         'select count(*) as totalRequests from requests where status != "downloaded"', | ||||||
|       totalRequestsFilteredStatus: |       totalRequestsFilteredStatus: | ||||||
|         "select count(*) as totalRequests from requests where status = ?", |         "select count(*) as totalRequests from requests where status = ?", | ||||||
|       fetchAllSort: `select id, type from request order by ? ?`, |       // fetchAllSort: `select id, type from request order by ? ?`, | ||||||
|       fetchAllFilter: `select id, type from request where ? is "?"`, |       // fetchAllFilter: `select id, type from request where ? is "?"`, | ||||||
|       fetchAllQuery: `select id, type from request where title like "%?%" or year like "%?%"`, |       // fetchAllQuery: `select id, type from request where title like "%?%" or year like "%?%"`, | ||||||
|       fetchAllFilterAndSort: `select id, type from request where ? is "?" order by ? ?`, |       // fetchAllFilterAndSort: `select id, type from request where ? is "?" order by ? ?`, | ||||||
|       downloaded: |       // downloaded: "(select status from requests where id is request.id and type is request.type limit 1)", | ||||||
|         "(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)', |       // deluge: '(select status from deluge_torrent where id is request.id and type is request.type limit 1)', | ||||||
|       // fetchAllFilterStatus: 'select * from request where ' |       // fetchAllFilterStatus: 'select * from request where ' | ||||||
|       readWithoutUserData: |       // readWithoutUserData: "select id, title, year, type, status, date from requests where id is ? and type is ?", | ||||||
|         "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 ?" |       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 = undefined; |  | ||||||
|  |  | ||||||
|     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); |  | ||||||
|       else if (row.type === "show") return tmdb.showInfo(row.id); |  | ||||||
|     }); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   /** |   /** | ||||||
|    * Add tmdb movie|show to requests |    * Add tmdb movie|show to requests | ||||||
|    * @param {tmdb} tmdb class of movie|show to add |    * @param {tmdb} tmdb class of movie|show to add | ||||||
|    * @returns {Promise} |    * @returns {Promise} | ||||||
|    */ |    */ | ||||||
|   requestFromTmdb(tmdb, ip, user_agent, username) { |   requestFromTmdb(tmdb, ip, userAgent, username) { | ||||||
|     return Promise.resolve() |     return Promise.resolve() | ||||||
|       .then(() => this.database.get(this.queries.read, [tmdb.id, tmdb.type])) |       .then(() => this.database.get(this.queries.read, [tmdb.id, tmdb.type])) | ||||||
|       .then(row => |       .then(row => | ||||||
| @@ -111,7 +58,7 @@ class RequestRepository { | |||||||
|           tmdb.backdrop, |           tmdb.backdrop, | ||||||
|           username, |           username, | ||||||
|           ip, |           ip, | ||||||
|           user_agent, |           userAgent, | ||||||
|           tmdb.type |           tmdb.type | ||||||
|         ]) |         ]) | ||||||
|       ) |       ) | ||||||
| @@ -122,7 +69,6 @@ class RequestRepository { | |||||||
|         ) { |         ) { | ||||||
|           throw new Error("This id is already requested", error.message); |           throw new Error("This id is already requested", error.message); | ||||||
|         } |         } | ||||||
|         console.log("Error @ request.addTmdb:", error); |  | ||||||
|         throw new Error("Could not add request"); |         throw new Error("Could not add request"); | ||||||
|       }); |       }); | ||||||
|   } |   } | ||||||
| @@ -152,20 +98,12 @@ class RequestRepository { | |||||||
|   /** |   /** | ||||||
|    * Fetch all requests with optional sort and filter params |    * Fetch all requests with optional sort and filter params | ||||||
|    * @param {String} what we are sorting by |    * @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} params to filter by | ||||||
|    * @param {String} query param to filter result on. Filters on title and year |  | ||||||
|    * @returns {Promise} |    * @returns {Promise} | ||||||
|    */ |    */ | ||||||
|   fetchAll( |   fetchAll(_page = 1, filter = null) { | ||||||
|     page = 1, |  | ||||||
|     sort_by = undefined, |  | ||||||
|     sort_direction = "asc", |  | ||||||
|     filter = undefined, |  | ||||||
|     query = undefined |  | ||||||
|   ) { |  | ||||||
|     // TODO implemented sort and filter |     // TODO implemented sort and filter | ||||||
|     page = parseInt(page); |     const page = parseInt(_page, 10); | ||||||
|     let fetchQuery = this.queries.fetchAll; |     let fetchQuery = this.queries.fetchAll; | ||||||
|     let fetchTotalResults = this.queries.totalRequests; |     let fetchTotalResults = this.queries.totalRequests; | ||||||
|     let fetchParams = [page]; |     let fetchParams = [page]; | ||||||
| @@ -176,26 +114,24 @@ class RequestRepository { | |||||||
|         filter === "downloaded" || |         filter === "downloaded" || | ||||||
|         filter === "requested") |         filter === "requested") | ||||||
|     ) { |     ) { | ||||||
|       console.log("tes"); |  | ||||||
|       fetchQuery = this.queries.fetchAllFilteredStatus; |       fetchQuery = this.queries.fetchAllFilteredStatus; | ||||||
|       fetchTotalResults = this.queries.totalRequestsFilteredStatus; |       fetchTotalResults = this.queries.totalRequestsFilteredStatus; | ||||||
|       fetchParams = [filter, page]; |       fetchParams = [filter, page]; | ||||||
|     } else { |  | ||||||
|       filter = undefined; |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     return Promise.resolve() |     return this.database | ||||||
|       .then(dbQuery => this.database.all(fetchQuery, fetchParams)) |       .all(fetchQuery, fetchParams) | ||||||
|       .then(async rows => { |       .then(async rows => { | ||||||
|         const sqliteResponse = await this.database.get( |         const sqliteResponse = await this.database.get( | ||||||
|           fetchTotalResults, |           fetchTotalResults, | ||||||
|           filter ? filter : undefined |           filter || null | ||||||
|         ); |         ); | ||||||
|         const totalRequests = sqliteResponse["totalRequests"]; |         const { totalRequests } = sqliteResponse; | ||||||
|         const totalPages = Math.ceil(totalRequests / 26); |         const totalPages = Math.ceil(totalRequests / 26); | ||||||
|  |  | ||||||
|         return [ |         return [ | ||||||
|           rows.map(item => { |           rows.map(_item => { | ||||||
|  |             const item = _item; | ||||||
|             item.poster = item.poster_path; |             item.poster = item.poster_path; | ||||||
|             delete item.poster_path; |             delete item.poster_path; | ||||||
|             item.backdrop = item.background_path; |             item.backdrop = item.background_path; | ||||||
| @@ -205,18 +141,18 @@ class RequestRepository { | |||||||
|           totalPages, |           totalPages, | ||||||
|           totalRequests |           totalRequests | ||||||
|         ]; |         ]; | ||||||
|         return Promise.all(this.mapToTmdbByType(rows)); |  | ||||||
|  |         // return mapToTmdbByType(rows); | ||||||
|       }) |       }) | ||||||
|       .then(([result, totalPages, totalRequests]) => |       .then(([result, totalPages, totalRequests]) => | ||||||
|         Promise.resolve({ |         Promise.resolve({ | ||||||
|           results: result, |           results: result, | ||||||
|           total_results: totalRequests, |           total_results: totalRequests, | ||||||
|           page: page, |           page, | ||||||
|           total_pages: totalPages |           total_pages: totalPages | ||||||
|         }) |         }) | ||||||
|       ) |       ) | ||||||
|       .catch(error => { |       .catch(error => { | ||||||
|         console.log(error); |  | ||||||
|         throw error; |         throw error; | ||||||
|       }); |       }); | ||||||
|   } |   } | ||||||
|   | |||||||
| @@ -1,34 +1,48 @@ | |||||||
| // TODO : test title and date are valid matches to columns in the database | // TODO : test title and date are valid matches to columns in the database | ||||||
| const validSortParams = ['title', 'date'] | const validSortParams = ["title", "date"]; | ||||||
| const validSortDirs = ['asc', 'desc'] | const validSortDirs = ["asc", "desc"]; | ||||||
| const validFilterParams = ['movie', 'show', 'seeding', 'downloading', 'paused', 'finished', 'downloaded'] | const validFilterParams = [ | ||||||
|  |   "movie", | ||||||
|  |   "show", | ||||||
|  |   "seeding", | ||||||
|  |   "downloading", | ||||||
|  |   "paused", | ||||||
|  |   "finished", | ||||||
|  |   "downloaded" | ||||||
|  | ]; | ||||||
|  |  | ||||||
| function validSort(by, direction) { | function validSort(by, direction) { | ||||||
|   return new Promise((resolve, reject) => { |   return new Promise((resolve, reject) => { | ||||||
|     if (by === undefined) { |     if (by === undefined) { | ||||||
|       resolve() |       resolve(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     if (validSortParams.includes(by) && validSortDirs.includes(direction)) { |     if (validSortParams.includes(by) && validSortDirs.includes(direction)) { | ||||||
|       resolve() |       resolve(); | ||||||
|     } else { |     } else { | ||||||
|       reject(new Error(`invalid sort parameter, must be of: ${validSortParams} with optional sort directions: ${validSortDirs} appended with ':'`)) |       reject( | ||||||
|  |         new Error( | ||||||
|  |           `invalid sort parameter, must be of: ${validSortParams} with optional sort directions: ${validSortDirs} appended with ':'` | ||||||
|  |         ) | ||||||
|  |       ); | ||||||
|     } |     } | ||||||
|   }); |   }); | ||||||
| } | } | ||||||
|  |  | ||||||
| function validFilter(filter_param) { | function validFilter(filterParam) { | ||||||
|   return new Promise((resolve, reject) => { |   return new Promise((resolve, reject) => { | ||||||
|      if (filter_param === undefined) { |     if (filterParam === undefined) { | ||||||
|       resolve() |       resolve(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     if (filter_param && validFilterParams.includes(filter_param)) { |     if (filterParam && validFilterParams.includes(filterParam)) { | ||||||
|       resolve() |       resolve(); | ||||||
|     } else { |     } else { | ||||||
|       reject(new Error(`filter parameteres must be of type: ${validFilterParams}`)) |       reject( | ||||||
|  |         new Error(`filter parameteres must be of type: ${validFilterParams}`) | ||||||
|  |       ); | ||||||
|     } |     } | ||||||
|   }); |   }); | ||||||
| } | } | ||||||
|  |  | ||||||
| module.exports = { validSort, validFilter } | module.exports = { validSort, validFilter }; | ||||||
|   | |||||||
| @@ -1,5 +1,15 @@ | |||||||
| const establishedDatabase = require("../database/database"); | 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 { | class SearchHistory { | ||||||
|   constructor(database) { |   constructor(database) { | ||||||
|     this.database = database || establishedDatabase; |     this.database = database || establishedDatabase; | ||||||
| @@ -16,18 +26,12 @@ class SearchHistory { | |||||||
|    * @returns {Promise} |    * @returns {Promise} | ||||||
|    */ |    */ | ||||||
|   read(user) { |   read(user) { | ||||||
|     return new Promise((resolve, reject) => |     return this.database | ||||||
|       this.database |       .all(this.queries.read, user) | ||||||
|         .all(this.queries.read, user) |       .then(result => result.map(row => row.search_query)) | ||||||
|         .then((result, error) => { |       .catch(error => { | ||||||
|           if (error) throw new Error(error); |         throw new Error("Unable to get history.", error); | ||||||
|           resolve(result.map(row => row.search_query)); |       }); | ||||||
|         }) |  | ||||||
|         .catch(error => { |  | ||||||
|           console.log("Error when fetching history from database:", error); |  | ||||||
|           reject("Unable to get history."); |  | ||||||
|         }) |  | ||||||
|     ); |  | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   /** |   /** | ||||||
| @@ -41,15 +45,12 @@ class SearchHistory { | |||||||
|       .run(this.queries.create, [searchQuery, username]) |       .run(this.queries.create, [searchQuery, username]) | ||||||
|       .catch(error => { |       .catch(error => { | ||||||
|         if (error.message.includes("FOREIGN")) { |         if (error.message.includes("FOREIGN")) { | ||||||
|           throw new Error("Could not create search history."); |           throw new SearchHistoryCreateDatabaseError( | ||||||
|  |             "Could not create search history." | ||||||
|  |           ); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         throw { |         throw new SearchHistoryCreateDatabaseError(); | ||||||
|           success: false, |  | ||||||
|           status: 500, |  | ||||||
|           message: "An unexpected error occured", |  | ||||||
|           source: "database" |  | ||||||
|         }; |  | ||||||
|       }); |       }); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,7 +1,7 @@ | |||||||
| class Stray { | class Stray { | ||||||
|    constructor(id) { |   constructor(id) { | ||||||
|       this.id = id; |     this.id = id; | ||||||
|    } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| module.exports = Stray; | module.exports = Stray; | ||||||
|   | |||||||
| @@ -1,7 +1,7 @@ | |||||||
| const assert = require("assert"); | const assert = require("assert"); | ||||||
|  | const pythonShell = require("python-shell"); | ||||||
| const Stray = require("./stray"); | const Stray = require("./stray"); | ||||||
| const establishedDatabase = require("../database/database"); | const establishedDatabase = require("../database/database"); | ||||||
| const pythonShell = require("python-shell"); |  | ||||||
|  |  | ||||||
| class StrayRepository { | class StrayRepository { | ||||||
|   constructor(database) { |   constructor(database) { | ||||||
|   | |||||||
| @@ -1,5 +1,19 @@ | |||||||
| const fetch = require("node-fetch"); | 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; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function logTautulliError(error) { | ||||||
|  |   throw new TautulliUnexpectedError(error); | ||||||
|  | } | ||||||
|  |  | ||||||
| class Tautulli { | class Tautulli { | ||||||
|   constructor(apiKey, ip, port) { |   constructor(apiKey, ip, port) { | ||||||
|     this.apiKey = apiKey; |     this.apiKey = apiKey; | ||||||
| @@ -7,67 +21,59 @@ class Tautulli { | |||||||
|     this.port = port; |     this.port = port; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   buildUrlWithCmdAndUserid(cmd, user_id) { |   buildUrlWithCmdAndUserid(cmd, userId) { | ||||||
|     const url = new URL("api/v2", `http://${this.ip}:${this.port}`); |     const url = new URL("api/v2", `http://${this.ip}:${this.port}`); | ||||||
|     url.searchParams.append("apikey", this.apiKey); |     url.searchParams.append("apikey", this.apiKey); | ||||||
|     url.searchParams.append("cmd", cmd); |     url.searchParams.append("cmd", cmd); | ||||||
|     url.searchParams.append("user_id", user_id); |     url.searchParams.append("user_id", userId); | ||||||
|  |  | ||||||
|     return url; |     return url; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   logTautulliError(error) { |   getPlaysByDayOfWeek(plexUserId, days, yAxis) { | ||||||
|     console.error("error fetching from tautulli"); |  | ||||||
|  |  | ||||||
|     throw error; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   getPlaysByDayOfWeek(plex_userid, days, y_axis) { |  | ||||||
|     const url = this.buildUrlWithCmdAndUserid( |     const url = this.buildUrlWithCmdAndUserid( | ||||||
|       "get_plays_by_dayofweek", |       "get_plays_by_dayofweek", | ||||||
|       plex_userid |       plexUserId | ||||||
|     ); |     ); | ||||||
|     url.searchParams.append("time_range", days); |     url.searchParams.append("time_range", days); | ||||||
|     url.searchParams.append("y_axis", y_axis); |     url.searchParams.append("y_axis", yAxis); | ||||||
|  |  | ||||||
|     return fetch(url.href) |     return fetch(url.href) | ||||||
|       .then(resp => resp.json()) |       .then(resp => resp.json()) | ||||||
|       .catch(error => this.logTautulliError(error)); |       .catch(error => logTautulliError(error)); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   getPlaysByDays(plex_userid, days, y_axis) { |   getPlaysByDays(plexUserId, days, yAxis) { | ||||||
|     const url = this.buildUrlWithCmdAndUserid("get_plays_by_date", plex_userid); |     const url = this.buildUrlWithCmdAndUserid("get_plays_by_date", plexUserId); | ||||||
|     url.searchParams.append("time_range", days); |     url.searchParams.append("time_range", days); | ||||||
|     url.searchParams.append("y_axis", y_axis); |     url.searchParams.append("y_axis", yAxis); | ||||||
|  |  | ||||||
|     return fetch(url.href) |     return fetch(url.href) | ||||||
|       .then(resp => resp.json()) |       .then(resp => resp.json()) | ||||||
|       .catch(error => this.logTautulliError(error)); |       .catch(error => logTautulliError(error)); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   watchTimeStats(plex_userid) { |   watchTimeStats(plexUserId) { | ||||||
|     const url = this.buildUrlWithCmdAndUserid( |     const url = this.buildUrlWithCmdAndUserid( | ||||||
|       "get_user_watch_time_stats", |       "get_user_watch_time_stats", | ||||||
|       plex_userid |       plexUserId | ||||||
|     ); |     ); | ||||||
|     url.searchParams.append("grouping", 0); |     url.searchParams.append("grouping", 0); | ||||||
|  |  | ||||||
|     return fetch(url.href) |     return fetch(url.href) | ||||||
|       .then(resp => resp.json()) |       .then(resp => resp.json()) | ||||||
|       .catch(error => this.logTautulliError(error)); |       .catch(error => logTautulliError(error)); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   viewHistory(plex_userid) { |   viewHistory(plexUserId) { | ||||||
|     const url = this.buildUrlWithCmdAndUserid("get_history", plex_userid); |     const url = this.buildUrlWithCmdAndUserid("get_history", plexUserId); | ||||||
|  |  | ||||||
|     url.searchParams.append("start", 0); |     url.searchParams.append("start", 0); | ||||||
|     url.searchParams.append("length", 50); |     url.searchParams.append("length", 50); | ||||||
|  |  | ||||||
|     console.log("fetching url", url.href); |  | ||||||
|  |  | ||||||
|     return fetch(url.href) |     return fetch(url.href) | ||||||
|       .then(resp => resp.json()) |       .then(resp => resp.json()) | ||||||
|       .catch(error => this.logTautulliError(error)); |       .catch(error => logTautulliError(error)); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										285
									
								
								src/tmdb/tmdb.js
									
									
									
									
									
								
							
							
						
						
									
										285
									
								
								src/tmdb/tmdb.js
									
									
									
									
									
								
							| @@ -3,27 +3,86 @@ const redisCache = require("../cache/redis"); | |||||||
|  |  | ||||||
| const { Movie, Show, Person, Credits, ReleaseDates } = require("./types"); | const { Movie, Show, Person, Credits, ReleaseDates } = require("./types"); | ||||||
|  |  | ||||||
| const tmdbErrorResponse = (error, typeString = undefined) => { | class TMDBNotFoundError extends Error { | ||||||
|   if (error.status === 404) { |   constructor(message) { | ||||||
|     let message = error.response.body.status_message; |     super(message); | ||||||
|  |  | ||||||
|     throw { |     this.statusCode = 404; | ||||||
|       status: 404, |   } | ||||||
|       message: message.slice(0, -1) + " in tmdb." | } | ||||||
|     }; |  | ||||||
|  | class TMDBUnauthorizedError extends Error { | ||||||
|  |   constructor(message = "TMDB returned access denied, requires api token.") { | ||||||
|  |     super(message); | ||||||
|  |  | ||||||
|  |     this.statusCode = 401; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class TMDBUnexpectedError extends Error { | ||||||
|  |   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; | ||||||
|  |  | ||||||
|  |     throw new TMDBNotFoundError(`${message.slice(0, -1)} in tmdb.`); | ||||||
|   } else if (error.status === 401) { |   } else if (error.status === 401) { | ||||||
|     throw { |     throw new TMDBUnauthorizedError(error?.response?.body?.status_message); | ||||||
|       status: 401, |   } else if (error?.code === "ENOTFOUND") { | ||||||
|       message: error.response.body.status_message |     throw new TMDBNotReachableError(); | ||||||
|     }; |  | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   throw { |   throw new TMDBUnexpectedError(type, error); | ||||||
|     status: 500, |  | ||||||
|     message: `An unexpected error occured while fetching ${typeString} from tmdb` |  | ||||||
|   }; |  | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * 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 { | class TMDB { | ||||||
|   constructor(apiKey, cache, tmdbLibrary) { |   constructor(apiKey, cache, tmdbLibrary) { | ||||||
|     this.tmdbLibrary = tmdbLibrary || moviedb(apiKey); |     this.tmdbLibrary = tmdbLibrary || moviedb(apiKey); | ||||||
| @@ -53,15 +112,17 @@ class TMDB { | |||||||
|     this.defaultTTL = 86400; |     this.defaultTTL = 86400; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   getFromCacheOrFetchFromTmdb(cacheKey, tmdbMethod, query) { |   async getFromCacheOrFetchFromTmdb(cacheKey, tmdbMethod, query) { | ||||||
|     return new Promise((resolve, reject) => |     try { | ||||||
|       this.cache |       const result = await this.cache.get(cacheKey); | ||||||
|         .get(cacheKey) |       if (!result) throw new Error(); | ||||||
|         .then(resolve) |  | ||||||
|         .catch(() => this.tmdb(tmdbMethod, query)) |       return result; | ||||||
|         .then(resolve) |     } catch { | ||||||
|         .catch(error => reject(tmdbErrorResponse(error, tmdbMethod))) |       return this.tmdb(tmdbMethod, query) | ||||||
|     ); |         .then(result => this.cache.set(cacheKey, result, this.defaultTTL)) | ||||||
|  |         .catch(error => tmdbErrorResponse(error, tmdbMethod)); | ||||||
|  |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   /** |   /** | ||||||
| @@ -75,9 +136,9 @@ class TMDB { | |||||||
|     const query = { id: identifier }; |     const query = { id: identifier }; | ||||||
|     const cacheKey = `tmdb/${this.cacheTags.movieInfo}:${identifier}`; |     const cacheKey = `tmdb/${this.cacheTags.movieInfo}:${identifier}`; | ||||||
|  |  | ||||||
|     return this.getFromCacheOrFetchFromTmdb(cacheKey, "movieInfo", query) |     return this.getFromCacheOrFetchFromTmdb(cacheKey, "movieInfo", query).then( | ||||||
|       .then(movie => this.cache.set(cacheKey, movie, this.defaultTTL)) |       movie => Movie.convertFromTmdbResponse(movie) | ||||||
|       .then(movie => Movie.convertFromTmdbResponse(movie)); |     ); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   /** |   /** | ||||||
| @@ -89,9 +150,11 @@ class TMDB { | |||||||
|     const query = { id: identifier }; |     const query = { id: identifier }; | ||||||
|     const cacheKey = `tmdb/${this.cacheTags.movieCredits}:${identifier}`; |     const cacheKey = `tmdb/${this.cacheTags.movieCredits}:${identifier}`; | ||||||
|  |  | ||||||
|     return this.getFromCacheOrFetchFromTmdb(cacheKey, "movieCredits", query) |     return this.getFromCacheOrFetchFromTmdb( | ||||||
|       .then(credits => this.cache.set(cacheKey, credits, this.defaultTTL)) |       cacheKey, | ||||||
|       .then(credits => Credits.convertFromTmdbResponse(credits)); |       "movieCredits", | ||||||
|  |       query | ||||||
|  |     ).then(credits => Credits.convertFromTmdbResponse(credits)); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   /** |   /** | ||||||
| @@ -107,11 +170,7 @@ class TMDB { | |||||||
|       cacheKey, |       cacheKey, | ||||||
|       "movieReleaseDates", |       "movieReleaseDates", | ||||||
|       query |       query | ||||||
|     ) |     ).then(releaseDates => ReleaseDates.convertFromTmdbResponse(releaseDates)); | ||||||
|       .then(releaseDates => |  | ||||||
|         this.cache.set(cacheKey, releaseDates, this.defaultTTL) |  | ||||||
|       ) |  | ||||||
|       .then(releaseDates => ReleaseDates.convertFromTmdbResponse(releaseDates)); |  | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   /** |   /** | ||||||
| @@ -124,18 +183,18 @@ class TMDB { | |||||||
|     const query = { id: identifier }; |     const query = { id: identifier }; | ||||||
|     const cacheKey = `tmdb/${this.cacheTags.showInfo}:${identifier}`; |     const cacheKey = `tmdb/${this.cacheTags.showInfo}:${identifier}`; | ||||||
|  |  | ||||||
|     return this.getFromCacheOrFetchFromTmdb(cacheKey, "tvInfo", query) |     return this.getFromCacheOrFetchFromTmdb(cacheKey, "tvInfo", query).then( | ||||||
|       .then(show => this.cache.set(cacheKey, show, this.defaultTTL)) |       show => Show.convertFromTmdbResponse(show) | ||||||
|       .then(show => Show.convertFromTmdbResponse(show)); |     ); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   showCredits(identifier) { |   showCredits(identifier) { | ||||||
|     const query = { id: identifier }; |     const query = { id: identifier }; | ||||||
|     const cacheKey = `tmdb/${this.cacheTags.showCredits}:${identifier}`; |     const cacheKey = `tmdb/${this.cacheTags.showCredits}:${identifier}`; | ||||||
|  |  | ||||||
|     return this.getFromCacheOrFetchFromTmdb(cacheKey, "tvCredits", query) |     return this.getFromCacheOrFetchFromTmdb(cacheKey, "tvCredits", query).then( | ||||||
|       .then(credits => this.cache.set(cacheKey, credits, this.defaultTTL)) |       credits => Credits.convertFromTmdbResponse(credits) | ||||||
|       .then(credits => Credits.convertFromTmdbResponse(credits)); |     ); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   /** |   /** | ||||||
| @@ -148,9 +207,9 @@ class TMDB { | |||||||
|     const query = { id: identifier }; |     const query = { id: identifier }; | ||||||
|     const cacheKey = `tmdb/${this.cacheTags.personInfo}:${identifier}`; |     const cacheKey = `tmdb/${this.cacheTags.personInfo}:${identifier}`; | ||||||
|  |  | ||||||
|     return this.getFromCacheOrFetchFromTmdb(cacheKey, "personInfo", query) |     return this.getFromCacheOrFetchFromTmdb(cacheKey, "personInfo", query).then( | ||||||
|       .then(person => this.cache.set(cacheKey, person, this.defaultTTL)) |       person => Person.convertFromTmdbResponse(person) | ||||||
|       .then(person => Person.convertFromTmdbResponse(person)); |     ); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   personCredits(identifier) { |   personCredits(identifier) { | ||||||
| @@ -161,18 +220,18 @@ class TMDB { | |||||||
|       cacheKey, |       cacheKey, | ||||||
|       "personCombinedCredits", |       "personCombinedCredits", | ||||||
|       query |       query | ||||||
|     ) |     ).then(credits => Credits.convertFromTmdbResponse(credits)); | ||||||
|       .then(credits => this.cache.set(cacheKey, credits, this.defaultTTL)) |  | ||||||
|       .then(credits => Credits.convertFromTmdbResponse(credits)); |  | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   multiSearch(search_query, page = 1, include_adult = true) { |   multiSearch(searchQuery, page = 1, includeAdult = true) { | ||||||
|     const query = { query: search_query, page, include_adult }; |     const query = { query: searchQuery, page, include_adult: includeAdult }; | ||||||
|     const cacheKey = `tmdb/${this.cacheTags.multiSearch}:${page}:${search_query}:${include_adult}`; |     const cacheKey = `tmdb/${this.cacheTags.multiSearch}:${page}:${searchQuery}:${includeAdult}`; | ||||||
|  |  | ||||||
|     return this.getFromCacheOrFetchFromTmdb(cacheKey, "searchMulti", query) |     return this.getFromCacheOrFetchFromTmdb( | ||||||
|       .then(response => this.cache.set(cacheKey, response, this.defaultTTL)) |       cacheKey, | ||||||
|       .then(response => this.mapResults(response)); |       "searchMulti", | ||||||
|  |       query | ||||||
|  |     ).then(response => mapResults(response)); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   /** |   /** | ||||||
| @@ -181,13 +240,19 @@ class TMDB { | |||||||
|    * @param {Number} page representing pagination of results |    * @param {Number} page representing pagination of results | ||||||
|    * @returns {Promise} dict with query results, current page and total_pages |    * @returns {Promise} dict with query results, current page and total_pages | ||||||
|    */ |    */ | ||||||
|   movieSearch(search_query, page = 1, include_adult = true) { |   movieSearch(searchQuery, page = 1, includeAdult = true) { | ||||||
|     const tmdbquery = { query: search_query, page, include_adult }; |     const tmdbquery = { | ||||||
|     const cacheKey = `tmdb/${this.cacheTags.movieSearch}:${page}:${search_query}:${include_adult}`; |       query: searchQuery, | ||||||
|  |       page, | ||||||
|  |       include_adult: includeAdult | ||||||
|  |     }; | ||||||
|  |     const cacheKey = `tmdb/${this.cacheTags.movieSearch}:${page}:${searchQuery}:${includeAdult}`; | ||||||
|  |  | ||||||
|     return this.getFromCacheOrFetchFromTmdb(cacheKey, "searchMovie", tmdbquery) |     return this.getFromCacheOrFetchFromTmdb( | ||||||
|       .then(response => this.cache.set(cacheKey, response, this.defaultTTL)) |       cacheKey, | ||||||
|       .then(response => this.mapResults(response, "movie")); |       "searchMovie", | ||||||
|  |       tmdbquery | ||||||
|  |     ).then(response => mapResults(response, "movie")); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   /** |   /** | ||||||
| @@ -196,13 +261,19 @@ class TMDB { | |||||||
|    * @param {Number} page representing pagination of results |    * @param {Number} page representing pagination of results | ||||||
|    * @returns {Promise} dict with query results, current page and total_pages |    * @returns {Promise} dict with query results, current page and total_pages | ||||||
|    */ |    */ | ||||||
|   showSearch(search_query, page = 1, include_adult = true) { |   showSearch(searchQuery, page = 1, includeAdult = true) { | ||||||
|     const tmdbquery = { query: search_query, page, include_adult }; |     const tmdbquery = { | ||||||
|     const cacheKey = `tmdb/${this.cacheTags.showSearch}:${page}:${search_query}:${include_adult}`; |       query: searchQuery, | ||||||
|  |       page, | ||||||
|  |       include_adult: includeAdult | ||||||
|  |     }; | ||||||
|  |     const cacheKey = `tmdb/${this.cacheTags.showSearch}:${page}:${searchQuery}:${includeAdult}`; | ||||||
|  |  | ||||||
|     return this.getFromCacheOrFetchFromTmdb(cacheKey, "searchTv", tmdbquery) |     return this.getFromCacheOrFetchFromTmdb( | ||||||
|       .then(response => this.cache.set(cacheKey, response, this.defaultTTL)) |       cacheKey, | ||||||
|       .then(response => this.mapResults(response, "show")); |       "searchTv", | ||||||
|  |       tmdbquery | ||||||
|  |     ).then(response => mapResults(response, "show")); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   /** |   /** | ||||||
| @@ -211,59 +282,37 @@ class TMDB { | |||||||
|    * @param {Number} page representing pagination of results |    * @param {Number} page representing pagination of results | ||||||
|    * @returns {Promise} dict with query results, current page and total_pages |    * @returns {Promise} dict with query results, current page and total_pages | ||||||
|    */ |    */ | ||||||
|   personSearch(search_query, page = 1, include_adult = true) { |   personSearch(searchQuery, page = 1, includeAdult = true) { | ||||||
|     const tmdbquery = { query: search_query, page, include_adult }; |     const tmdbquery = { | ||||||
|     const cacheKey = `tmdb/${this.cacheTags.personSearch}:${page}:${search_query}:${include_adult}`; |       query: searchQuery, | ||||||
|  |       page, | ||||||
|     return this.getFromCacheOrFetchFromTmdb(cacheKey, "searchPerson", tmdbquery) |       include_adult: includeAdult | ||||||
|       .then(response => this.cache.set(cacheKey, response, this.defaultTTL)) |  | ||||||
|       .then(response => this.mapResults(response, "person")); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   movieList(listname, page = 1) { |  | ||||||
|     const query = { page: 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")); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   showList(listname, page = 1) { |  | ||||||
|     const query = { page: 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. |  | ||||||
|    */ |  | ||||||
|   mapResults(response, type = undefined) { |  | ||||||
|     let results = response.results.map(result => { |  | ||||||
|       if (type === "movie" || result.media_type === "movie") { |  | ||||||
|         const movie = Movie.convertFromTmdbResponse(result); |  | ||||||
|         return movie.createJsonResponse(); |  | ||||||
|       } else if (type === "show" || result.media_type === "tv") { |  | ||||||
|         const show = Show.convertFromTmdbResponse(result); |  | ||||||
|         return show.createJsonResponse(); |  | ||||||
|       } else if (type === "person" || result.media_type === "person") { |  | ||||||
|         const person = Person.convertFromTmdbResponse(result); |  | ||||||
|         return person.createJsonResponse(); |  | ||||||
|       } |  | ||||||
|     }); |  | ||||||
|  |  | ||||||
|     return { |  | ||||||
|       results: results, |  | ||||||
|       page: response.page, |  | ||||||
|       total_results: response.total_results, |  | ||||||
|       total_pages: response.total_pages |  | ||||||
|     }; |     }; | ||||||
|  |     const cacheKey = `tmdb/${this.cacheTags.personSearch}:${page}:${searchQuery}:${includeAdult}`; | ||||||
|  |  | ||||||
|  |     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 => 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 => mapResults(response, "show") | ||||||
|  |     ); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   /** |   /** | ||||||
| @@ -278,7 +327,7 @@ class TMDB { | |||||||
|         if (error) { |         if (error) { | ||||||
|           return reject(error); |           return reject(error); | ||||||
|         } |         } | ||||||
|         resolve(reponse); |         return resolve(reponse); | ||||||
|       }; |       }; | ||||||
|  |  | ||||||
|       if (!argument) { |       if (!argument) { | ||||||
|   | |||||||
| @@ -1,7 +1,7 @@ | |||||||
| const Movie = require('./types/movie.js') | const Movie = require("./types/movie"); | ||||||
| const Show = require('./types/show.js') | const Show = require("./types/show"); | ||||||
| const Person = require('./types/person.js') | const Person = require("./types/person"); | ||||||
| const Credits = require('./types/credits.js') | const Credits = require("./types/credits"); | ||||||
| const ReleaseDates = require('./types/releaseDates.js') | const ReleaseDates = require("./types/releaseDates"); | ||||||
|  |  | ||||||
| module.exports = { Movie, Show, Person, Credits, ReleaseDates } | module.exports = { Movie, Show, Person, Credits, ReleaseDates }; | ||||||
|   | |||||||
| @@ -61,4 +61,4 @@ interface Genre { | |||||||
|   name: string; |   name: string; | ||||||
| } | } | ||||||
|  |  | ||||||
| export { Movie, Show, Person, Genre } | export { Movie, Show, Person, Genre }; | ||||||
|   | |||||||
| @@ -1,65 +1,9 @@ | |||||||
| import Movie from "./movie"; | /* eslint-disable camelcase */ | ||||||
| import Show from "./show"; | const Movie = require("./movie"); | ||||||
|  | const Show = require("./show"); | ||||||
|  |  | ||||||
| class Credits { | class CreditedMovie extends Movie {} | ||||||
|   constructor(id, cast = [], crew = []) { | class CreditedShow extends Show {} | ||||||
|     this.id = id; |  | ||||||
|     this.cast = cast; |  | ||||||
|     this.crew = crew; |  | ||||||
|     this.type = "credits"; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   static convertFromTmdbResponse(response) { |  | ||||||
|     const { id, cast, crew } = response; |  | ||||||
|  |  | ||||||
|     const allCast = cast.map(cast => { |  | ||||||
|       if (cast["media_type"]) { |  | ||||||
|         if (cast.media_type === "movie") { |  | ||||||
|           return CreditedMovie.convertFromTmdbResponse(cast); |  | ||||||
|         } else if (cast.media_type === "tv") { |  | ||||||
|           return CreditedShow.convertFromTmdbResponse(cast); |  | ||||||
|         } |  | ||||||
|       } |  | ||||||
|  |  | ||||||
|       return new CastMember( |  | ||||||
|         cast.character, |  | ||||||
|         cast.gender, |  | ||||||
|         cast.id, |  | ||||||
|         cast.name, |  | ||||||
|         cast.profile_path |  | ||||||
|       ); |  | ||||||
|     }); |  | ||||||
|  |  | ||||||
|     const allCrew = crew.map(crew => { |  | ||||||
|       if (cast["media_type"]) { |  | ||||||
|         if (cast.media_type === "movie") { |  | ||||||
|           return CreditedMovie.convertFromTmdbResponse(cast); |  | ||||||
|         } else if (cast.media_type === "tv") { |  | ||||||
|           return CreditedShow.convertFromTmdbResponse(cast); |  | ||||||
|         } |  | ||||||
|       } |  | ||||||
|  |  | ||||||
|       return new CrewMember( |  | ||||||
|         crew.department, |  | ||||||
|         crew.gender, |  | ||||||
|         crew.id, |  | ||||||
|         crew.job, |  | ||||||
|         crew.name, |  | ||||||
|         crew.profile_path |  | ||||||
|       ); |  | ||||||
|     }); |  | ||||||
|  |  | ||||||
|     return new Credits(id, allCast, allCrew); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   createJsonResponse() { |  | ||||||
|     return { |  | ||||||
|       id: this.id, |  | ||||||
|       cast: this.cast.map(cast => cast.createJsonResponse()), |  | ||||||
|       crew: this.crew.map(crew => crew.createJsonResponse()) |  | ||||||
|     }; |  | ||||||
|   } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| class CastMember { | class CastMember { | ||||||
|   constructor(character, gender, id, name, profile_path) { |   constructor(character, gender, id, name, profile_path) { | ||||||
| @@ -107,7 +51,66 @@ class CrewMember { | |||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| class CreditedMovie extends Movie {} | class Credits { | ||||||
| class CreditedShow extends Show {} |   constructor(id, cast = [], crew = []) { | ||||||
|  |     this.id = id; | ||||||
|  |     this.cast = cast; | ||||||
|  |     this.crew = crew; | ||||||
|  |     this.type = "credits"; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   static convertFromTmdbResponse(response) { | ||||||
|  |     const { id, cast, crew } = response; | ||||||
|  |  | ||||||
|  |     const allCast = cast.map(cast => { | ||||||
|  |       if (cast.media_type) { | ||||||
|  |         if (cast.media_type === "movie") { | ||||||
|  |           return CreditedMovie.convertFromTmdbResponse(cast); | ||||||
|  |         } | ||||||
|  |         if (cast.media_type === "tv") { | ||||||
|  |           return CreditedShow.convertFromTmdbResponse(cast); | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       return new CastMember( | ||||||
|  |         cast.character, | ||||||
|  |         cast.gender, | ||||||
|  |         cast.id, | ||||||
|  |         cast.name, | ||||||
|  |         cast.profile_path | ||||||
|  |       ); | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     const allCrew = crew.map(crew => { | ||||||
|  |       if (cast.media_type) { | ||||||
|  |         if (cast.media_type === "movie") { | ||||||
|  |           return CreditedMovie.convertFromTmdbResponse(cast); | ||||||
|  |         } | ||||||
|  |         if (cast.media_type === "tv") { | ||||||
|  |           return CreditedShow.convertFromTmdbResponse(cast); | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       return new CrewMember( | ||||||
|  |         crew.department, | ||||||
|  |         crew.gender, | ||||||
|  |         crew.id, | ||||||
|  |         crew.job, | ||||||
|  |         crew.name, | ||||||
|  |         crew.profile_path | ||||||
|  |       ); | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     return new Credits(id, allCast, allCrew); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   createJsonResponse() { | ||||||
|  |     return { | ||||||
|  |       id: this.id, | ||||||
|  |       cast: this.cast.map(cast => cast.createJsonResponse()), | ||||||
|  |       crew: this.crew.map(crew => crew.createJsonResponse()) | ||||||
|  |     }; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
| module.exports = Credits; | module.exports = Credits; | ||||||
|   | |||||||
| @@ -1,7 +1,22 @@ | |||||||
|  | /* eslint-disable camelcase */ | ||||||
|  |  | ||||||
| class Movie { | class Movie { | ||||||
|   constructor(id, title, year=undefined, overview=undefined, poster=undefined, backdrop=undefined, |   constructor( | ||||||
|               releaseDate=undefined, rating=undefined, genres=undefined, productionStatus=undefined, |     id, | ||||||
|               tagline=undefined, runtime=undefined, imdb_id=undefined, popularity=undefined) { |     title, | ||||||
|  |     year = undefined, | ||||||
|  |     overview = undefined, | ||||||
|  |     poster = undefined, | ||||||
|  |     backdrop = undefined, | ||||||
|  |     releaseDate = undefined, | ||||||
|  |     rating = undefined, | ||||||
|  |     genres = undefined, | ||||||
|  |     productionStatus = undefined, | ||||||
|  |     tagline = undefined, | ||||||
|  |     runtime = undefined, | ||||||
|  |     imdb_id = undefined, | ||||||
|  |     popularity = undefined | ||||||
|  |   ) { | ||||||
|     this.id = id; |     this.id = id; | ||||||
|     this.title = title; |     this.title = title; | ||||||
|     this.year = year; |     this.year = year; | ||||||
| @@ -16,27 +31,66 @@ class Movie { | |||||||
|     this.runtime = runtime; |     this.runtime = runtime; | ||||||
|     this.imdb_id = imdb_id; |     this.imdb_id = imdb_id; | ||||||
|     this.popularity = popularity; |     this.popularity = popularity; | ||||||
|     this.type = 'movie'; |     this.type = "movie"; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   static convertFromTmdbResponse(response) { |   static convertFromTmdbResponse(response) { | ||||||
|     const { id, title, release_date, overview, poster_path, backdrop_path, vote_average, genres, status, |     const { | ||||||
|             tagline, runtime, imdb_id, popularity } = response; |       id, | ||||||
|  |       title, | ||||||
|  |       release_date, | ||||||
|  |       overview, | ||||||
|  |       poster_path, | ||||||
|  |       backdrop_path, | ||||||
|  |       vote_average, | ||||||
|  |       genres, | ||||||
|  |       status, | ||||||
|  |       tagline, | ||||||
|  |       runtime, | ||||||
|  |       imdb_id, | ||||||
|  |       popularity | ||||||
|  |     } = response; | ||||||
|  |  | ||||||
|     const releaseDate = new Date(release_date); |     const releaseDate = new Date(release_date); | ||||||
|     const year = releaseDate.getFullYear(); |     const year = releaseDate.getFullYear(); | ||||||
|     const genreNames = genres ? genres.map(g => g.name) : undefined |     const genreNames = genres ? genres.map(g => g.name) : undefined; | ||||||
|  |  | ||||||
|     return new Movie(id, title, year, overview, poster_path, backdrop_path, releaseDate, vote_average, genreNames, status, |     return new Movie( | ||||||
|                      tagline, runtime, imdb_id, popularity) |       id, | ||||||
|  |       title, | ||||||
|  |       year, | ||||||
|  |       overview, | ||||||
|  |       poster_path, | ||||||
|  |       backdrop_path, | ||||||
|  |       releaseDate, | ||||||
|  |       vote_average, | ||||||
|  |       genreNames, | ||||||
|  |       status, | ||||||
|  |       tagline, | ||||||
|  |       runtime, | ||||||
|  |       imdb_id, | ||||||
|  |       popularity | ||||||
|  |     ); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   static convertFromPlexResponse(response) { |   static convertFromPlexResponse(response) { | ||||||
|     // console.log('response', response) |     // console.log('response', response) | ||||||
|     const { title, year, rating, tagline, summary } = response; |     const { title, year, rating, tagline, summary } = response; | ||||||
|     const _ = undefined |     const _ = undefined; | ||||||
|  |  | ||||||
|     return new Movie(null, title, year, summary, _, _, _, rating, _, _, tagline) |     return new Movie( | ||||||
|  |       null, | ||||||
|  |       title, | ||||||
|  |       year, | ||||||
|  |       summary, | ||||||
|  |       _, | ||||||
|  |       _, | ||||||
|  |       _, | ||||||
|  |       rating, | ||||||
|  |       _, | ||||||
|  |       _, | ||||||
|  |       tagline | ||||||
|  |     ); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   createJsonResponse() { |   createJsonResponse() { | ||||||
| @@ -55,7 +109,7 @@ class Movie { | |||||||
|       runtime: this.runtime, |       runtime: this.runtime, | ||||||
|       imdb_id: this.imdb_id, |       imdb_id: this.imdb_id, | ||||||
|       type: this.type |       type: this.type | ||||||
|     } |     }; | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,3 +1,5 @@ | |||||||
|  | /* eslint-disable camelcase */ | ||||||
|  |  | ||||||
| class Person { | class Person { | ||||||
|   constructor( |   constructor( | ||||||
|     id, |     id, | ||||||
|   | |||||||
| @@ -1,28 +1,11 @@ | |||||||
| class ReleaseDates {  | const releaseTypeEnum = { | ||||||
|   constructor(id, releases) { |   1: "Premier", | ||||||
|     this.id = id; |   2: "Limited theatrical", | ||||||
|     this.releases = releases; |   3: "Theatrical", | ||||||
|   } |   4: "Digital", | ||||||
|  |   5: "Physical", | ||||||
|   static convertFromTmdbResponse(response) { |   6: "TV" | ||||||
|     const { id, results } = response; | }; | ||||||
|  |  | ||||||
|     const releases = results.map(countryRelease =>  |  | ||||||
|       new Release( |  | ||||||
|         countryRelease.iso_3166_1,  |  | ||||||
|         countryRelease.release_dates.map(rd => new ReleaseDate(rd.certification, rd.iso_639_1, rd.release_date, rd.type, rd.note)) |  | ||||||
|       )) |  | ||||||
|  |  | ||||||
|     return new ReleaseDates(id, releases) |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   createJsonResponse() { |  | ||||||
|     return { |  | ||||||
|       id: this.id, |  | ||||||
|       results: this.releases.map(release => release.createJsonResponse()) |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| class Release { | class Release { | ||||||
|   constructor(country, releaseDates) { |   constructor(country, releaseDates) { | ||||||
| @@ -33,8 +16,10 @@ class Release { | |||||||
|   createJsonResponse() { |   createJsonResponse() { | ||||||
|     return { |     return { | ||||||
|       country: this.country, |       country: this.country, | ||||||
|       release_dates: this.releaseDates.map(releaseDate => releaseDate.createJsonResponse()) |       release_dates: this.releaseDates.map(releaseDate => | ||||||
|     } |         releaseDate.createJsonResponse() | ||||||
|  |       ) | ||||||
|  |     }; | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -47,21 +32,13 @@ class ReleaseDate { | |||||||
|     this.note = note; |     this.note = note; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   releaseTypeLookup(releaseTypeKey) { |   static releaseTypeLookup(releaseTypeKey) { | ||||||
|     const releaseTypeEnum = { |  | ||||||
|       1: 'Premier', |  | ||||||
|       2: 'Limited theatrical', |  | ||||||
|       3: 'Theatrical', |  | ||||||
|       4: 'Digital', |  | ||||||
|       5: 'Physical', |  | ||||||
|       6: 'TV' |  | ||||||
|     } |  | ||||||
|     if (releaseTypeKey <= Object.keys(releaseTypeEnum).length) { |     if (releaseTypeKey <= Object.keys(releaseTypeEnum).length) { | ||||||
|       return releaseTypeEnum[releaseTypeKey] |       return releaseTypeEnum[releaseTypeKey]; | ||||||
|     } else { |  | ||||||
|       // TODO log | Release type not defined, does this need updating? |  | ||||||
|       return null |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     // TODO log | Release type not defined, does this need updating? | ||||||
|  |     return null; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   createJsonResponse() { |   createJsonResponse() { | ||||||
| @@ -71,7 +48,44 @@ class ReleaseDate { | |||||||
|       release_date: this.releaseDate, |       release_date: this.releaseDate, | ||||||
|       type: this.type, |       type: this.type, | ||||||
|       note: this.note |       note: this.note | ||||||
|     } |     }; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class ReleaseDates { | ||||||
|  |   constructor(id, releases) { | ||||||
|  |     this.id = id; | ||||||
|  |     this.releases = releases; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   static convertFromTmdbResponse(response) { | ||||||
|  |     const { id, results } = response; | ||||||
|  |  | ||||||
|  |     const releases = results.map( | ||||||
|  |       countryRelease => | ||||||
|  |         new Release( | ||||||
|  |           countryRelease.iso_3166_1, | ||||||
|  |           countryRelease.release_dates.map( | ||||||
|  |             rd => | ||||||
|  |               new ReleaseDate( | ||||||
|  |                 rd.certification, | ||||||
|  |                 rd.iso_639_1, | ||||||
|  |                 rd.release_date, | ||||||
|  |                 rd.type, | ||||||
|  |                 rd.note | ||||||
|  |               ) | ||||||
|  |           ) | ||||||
|  |         ) | ||||||
|  |     ); | ||||||
|  |  | ||||||
|  |     return new ReleaseDates(id, releases); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   createJsonResponse() { | ||||||
|  |     return { | ||||||
|  |       id: this.id, | ||||||
|  |       results: this.releases.map(release => release.createJsonResponse()) | ||||||
|  |     }; | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,7 +1,20 @@ | |||||||
|  | /* eslint-disable camelcase */ | ||||||
|  |  | ||||||
| class Show { | class Show { | ||||||
|   constructor(id, title, year=undefined, overview=undefined, poster=undefined, backdrop=undefined, |   constructor( | ||||||
|               seasons=undefined, episodes=undefined, rank=undefined, genres=undefined, status=undefined, |     id, | ||||||
|               runtime=undefined) { |     title, | ||||||
|  |     year = undefined, | ||||||
|  |     overview = undefined, | ||||||
|  |     poster = undefined, | ||||||
|  |     backdrop = undefined, | ||||||
|  |     seasons = undefined, | ||||||
|  |     episodes = undefined, | ||||||
|  |     rank = undefined, | ||||||
|  |     genres = undefined, | ||||||
|  |     status = undefined, | ||||||
|  |     runtime = undefined | ||||||
|  |   ) { | ||||||
|     this.id = id; |     this.id = id; | ||||||
|     this.title = title; |     this.title = title; | ||||||
|     this.year = year; |     this.year = year; | ||||||
| @@ -14,18 +27,44 @@ class Show { | |||||||
|     this.genres = genres; |     this.genres = genres; | ||||||
|     this.productionStatus = status; |     this.productionStatus = status; | ||||||
|     this.runtime = runtime; |     this.runtime = runtime; | ||||||
|     this.type = 'show'; |     this.type = "show"; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   static convertFromTmdbResponse(response) { |   static convertFromTmdbResponse(response) { | ||||||
|     const { id, name, first_air_date, overview, poster_path, backdrop_path, number_of_seasons, number_of_episodes, |     const { | ||||||
|             rank, genres, status, episode_run_time, popularity } = response; |       id, | ||||||
|  |       name, | ||||||
|  |       first_air_date, | ||||||
|  |       overview, | ||||||
|  |       poster_path, | ||||||
|  |       backdrop_path, | ||||||
|  |       number_of_seasons, | ||||||
|  |       number_of_episodes, | ||||||
|  |       rank, | ||||||
|  |       genres, | ||||||
|  |       status, | ||||||
|  |       episode_run_time, | ||||||
|  |       popularity | ||||||
|  |     } = response; | ||||||
|  |  | ||||||
|     const year = new Date(first_air_date).getFullYear() |     const year = new Date(first_air_date).getFullYear(); | ||||||
|     const genreNames = genres ? genres.map(g => g.name) : undefined |     const genreNames = genres ? genres.map(g => g.name) : undefined; | ||||||
|  |  | ||||||
|     return new Show(id, name, year, overview, poster_path, backdrop_path, number_of_seasons, number_of_episodes, |     return new Show( | ||||||
|                      rank, genreNames, status, episode_run_time, popularity) |       id, | ||||||
|  |       name, | ||||||
|  |       year, | ||||||
|  |       overview, | ||||||
|  |       poster_path, | ||||||
|  |       backdrop_path, | ||||||
|  |       number_of_seasons, | ||||||
|  |       number_of_episodes, | ||||||
|  |       rank, | ||||||
|  |       genreNames, | ||||||
|  |       status, | ||||||
|  |       episode_run_time, | ||||||
|  |       popularity | ||||||
|  |     ); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   createJsonResponse() { |   createJsonResponse() { | ||||||
| @@ -43,7 +82,7 @@ class Show { | |||||||
|       production_status: this.productionStatus, |       production_status: this.productionStatus, | ||||||
|       runtime: this.runtime, |       runtime: this.runtime, | ||||||
|       type: this.type |       type: this.type | ||||||
|      } |     }; | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,5 +1,5 @@ | |||||||
| const User = require("./user"); |  | ||||||
| const jwt = require("jsonwebtoken"); | const jwt = require("jsonwebtoken"); | ||||||
|  | const User = require("./user"); | ||||||
|  |  | ||||||
| class Token { | class Token { | ||||||
|   constructor(user, admin = false, settings = null) { |   constructor(user, admin = false, settings = null) { | ||||||
| @@ -16,8 +16,8 @@ class Token { | |||||||
|   toString(secret) { |   toString(secret) { | ||||||
|     const { user, admin, settings } = this; |     const { user, admin, settings } = this; | ||||||
|  |  | ||||||
|     let data = { username: user.username, settings }; |     const data = { username: user.username, settings }; | ||||||
|     if (admin) data["admin"] = admin; |     if (admin) data.admin = admin; | ||||||
|  |  | ||||||
|     return jwt.sign(data, secret, { expiresIn: "90d" }); |     return jwt.sign(data, secret, { expiresIn: "90d" }); | ||||||
|   } |   } | ||||||
|   | |||||||
| @@ -1,8 +1,8 @@ | |||||||
| class User { | class User { | ||||||
|    constructor(username, email=undefined) { |   constructor(username, email = undefined) { | ||||||
|       this.username = username; |     this.username = username; | ||||||
|       this.email = email; |     this.email = email; | ||||||
|    } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| module.exports = User; | module.exports = User; | ||||||
|   | |||||||
| @@ -1,6 +1,69 @@ | |||||||
| const assert = require("assert"); | const assert = require("assert"); | ||||||
| const establishedDatabase = require("../database/database"); | 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 { | class UserRepository { | ||||||
|   constructor(database) { |   constructor(database) { | ||||||
|     this.database = database || establishedDatabase; |     this.database = database || establishedDatabase; | ||||||
| @@ -51,8 +114,7 @@ class UserRepository { | |||||||
|         assert(row, "The user does not exist."); |         assert(row, "The user does not exist."); | ||||||
|         return row.password; |         return row.password; | ||||||
|       }) |       }) | ||||||
|       .catch(err => { |       .catch(() => { | ||||||
|         console.log(error); |  | ||||||
|         throw new Error("Unable to find your user."); |         throw new Error("Unable to find your user."); | ||||||
|       }); |       }); | ||||||
|   } |   } | ||||||
| @@ -78,17 +140,7 @@ class UserRepository { | |||||||
|       this.database |       this.database | ||||||
|         .run(this.queries.link, [plexUserID, username]) |         .run(this.queries.link, [plexUserID, username]) | ||||||
|         .then(row => resolve(row)) |         .then(row => resolve(row)) | ||||||
|         .catch(error => { |         .catch(error => reject(new LinkPlexUserError(error))); | ||||||
|           // TODO log this unknown db error |  | ||||||
|           console.error("db error", error); |  | ||||||
|  |  | ||||||
|           reject({ |  | ||||||
|             status: 500, |  | ||||||
|             message: |  | ||||||
|               "An unexpected error occured while linking plex and seasoned accounts", |  | ||||||
|             source: "seasoned database" |  | ||||||
|           }); |  | ||||||
|         }); |  | ||||||
|     }); |     }); | ||||||
|   } |   } | ||||||
|  |  | ||||||
| @@ -102,17 +154,7 @@ class UserRepository { | |||||||
|       this.database |       this.database | ||||||
|         .run(this.queries.unlink, username) |         .run(this.queries.unlink, username) | ||||||
|         .then(row => resolve(row)) |         .then(row => resolve(row)) | ||||||
|         .catch(error => { |         .catch(error => reject(new UnlinkPlexUserError(error))); | ||||||
|           // TODO log this unknown db error |  | ||||||
|           console.log("db error", error); |  | ||||||
|  |  | ||||||
|           reject({ |  | ||||||
|             status: 500, |  | ||||||
|             message: |  | ||||||
|               "An unexpected error occured while unlinking plex and seasoned accounts", |  | ||||||
|             source: "seasoned database" |  | ||||||
|           }); |  | ||||||
|         }); |  | ||||||
|     }); |     }); | ||||||
|   } |   } | ||||||
|  |  | ||||||
| @@ -138,6 +180,7 @@ class UserRepository { | |||||||
|         .get(this.queries.getSettings, username) |         .get(this.queries.getSettings, username) | ||||||
|         .then(async row => { |         .then(async row => { | ||||||
|           if (row == null) { |           if (row == null) { | ||||||
|  |             // eslint-disable-next-line no-console | ||||||
|             console.debug( |             console.debug( | ||||||
|               `settings do not exist for user: ${username}. Creating settings entry.` |               `settings do not exist for user: ${username}. Creating settings entry.` | ||||||
|             ); |             ); | ||||||
| @@ -153,27 +196,13 @@ class UserRepository { | |||||||
|                 reject(error); |                 reject(error); | ||||||
|               } |               } | ||||||
|             } else { |             } else { | ||||||
|               reject({ |               reject(new NoSettingsUserNotFoundError()); | ||||||
|                 status: 404, |  | ||||||
|                 message: "User not found, no settings to get" |  | ||||||
|               }); |  | ||||||
|             } |             } | ||||||
|           } |           } | ||||||
|  |  | ||||||
|           resolve(row); |           resolve(row); | ||||||
|         }) |         }) | ||||||
|         .catch(error => { |         .catch(error => reject(new UnexpectedUserSettingsError(error))); | ||||||
|           console.error( |  | ||||||
|             "Unexpected error occured while fetching settings for your account. Error:", |  | ||||||
|             error |  | ||||||
|           ); |  | ||||||
|           reject({ |  | ||||||
|             status: 500, |  | ||||||
|             message: |  | ||||||
|               "An unexpected error occured while fetching settings for your account", |  | ||||||
|             source: "seasoned database" |  | ||||||
|           }); |  | ||||||
|         }); |  | ||||||
|     }); |     }); | ||||||
|   } |   } | ||||||
|  |  | ||||||
| @@ -184,12 +213,12 @@ class UserRepository { | |||||||
|    * @param {String} emoji |    * @param {String} emoji | ||||||
|    * @returns {Promsie} |    * @returns {Promsie} | ||||||
|    */ |    */ | ||||||
|   updateSettings(username, dark_mode = undefined, emoji = undefined) { |   updateSettings(username, _darkMode = null, _emoji = null) { | ||||||
|     const settings = this.getSettings(username); |     const settings = this.getSettings(username); | ||||||
|     dark_mode = dark_mode !== undefined ? dark_mode : settings.dark_mode; |     const darkMode = _darkMode || settings.darkMode; | ||||||
|     emoji = emoji !== undefined ? emoji : settings.emoji; |     const 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) { |       if (error.status && error.message) { | ||||||
|         return error; |         return error; | ||||||
|       } |       } | ||||||
| @@ -225,32 +254,13 @@ class UserRepository { | |||||||
|    * @param {String} username |    * @param {String} username | ||||||
|    * @returns {Promsie} |    * @returns {Promsie} | ||||||
|    */ |    */ | ||||||
|   dbUpdateSettings(username, dark_mode, emoji) { |   dbUpdateSettings(username, darkMode, emoji) { | ||||||
|     return new Promise((resolve, reject) => |     return new Promise(resolve => | ||||||
|       this.database |       this.database | ||||||
|         .run(this.queries.updateSettings, [username, dark_mode, emoji]) |         .run(this.queries.updateSettings, [username, darkMode, emoji]) | ||||||
|         .then(row => resolve(row)) |         .then(row => resolve(row)) | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| const rejectUnexpectedDatabaseError = ( |  | ||||||
|   message, |  | ||||||
|   status, |  | ||||||
|   error, |  | ||||||
|   reject = null |  | ||||||
| ) => { |  | ||||||
|   console.error(error); |  | ||||||
|   const body = { |  | ||||||
|     status, |  | ||||||
|     message, |  | ||||||
|     source: "seasoned database" |  | ||||||
|   }; |  | ||||||
|  |  | ||||||
|   if (reject == null) { |  | ||||||
|     return new Promise((resolve, reject) => reject(body)); |  | ||||||
|   } |  | ||||||
|   reject(body); |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| module.exports = UserRepository; | module.exports = UserRepository; | ||||||
|   | |||||||
| @@ -50,7 +50,7 @@ class UserSecurity { | |||||||
|     return new Promise((resolve, reject) => { |     return new Promise((resolve, reject) => { | ||||||
|       bcrypt.compare(clearPassword, hash, (error, match) => { |       bcrypt.compare(clearPassword, hash, (error, match) => { | ||||||
|         if (match) resolve(true); |         if (match) resolve(true); | ||||||
|         reject(false); |         reject(error); | ||||||
|       }); |       }); | ||||||
|     }); |     }); | ||||||
|   } |   } | ||||||
| @@ -61,7 +61,7 @@ class UserSecurity { | |||||||
|    * @returns {Promise} |    * @returns {Promise} | ||||||
|    */ |    */ | ||||||
|   static hashPassword(clearPassword) { |   static hashPassword(clearPassword) { | ||||||
|     return new Promise(resolve => { |     return new Promise((resolve, reject) => { | ||||||
|       const saltRounds = 10; |       const saltRounds = 10; | ||||||
|       bcrypt.hash(clearPassword, saltRounds, (error, hash) => { |       bcrypt.hash(clearPassword, saltRounds, (error, hash) => { | ||||||
|         if (error) reject(error); |         if (error) reject(error); | ||||||
|   | |||||||
| @@ -11,7 +11,7 @@ const mustBeAdmin = require("./middleware/mustBeAdmin"); | |||||||
| const mustHaveAccountLinkedToPlex = require("./middleware/mustHaveAccountLinkedToPlex"); | const mustHaveAccountLinkedToPlex = require("./middleware/mustHaveAccountLinkedToPlex"); | ||||||
|  |  | ||||||
| const listController = require("./controllers/list/listController"); | 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 SettingsController = require("./controllers/user/settings"); | ||||||
| const AuthenticatePlexAccountController = require("./controllers/user/authenticatePlexAccount"); | const AuthenticatePlexAccountController = require("./controllers/user/authenticatePlexAccount"); | ||||||
|  |  | ||||||
| @@ -24,7 +24,7 @@ app.use(bodyParser.json()); | |||||||
| app.use(cookieParser()); | app.use(cookieParser()); | ||||||
|  |  | ||||||
| const router = express.Router(); | const router = express.Router(); | ||||||
| const allowedOrigins = configuration.get("webserver", "origins"); | // const allowedOrigins = configuration.get("webserver", "origins"); | ||||||
|  |  | ||||||
| // TODO: All JSON handling in a single router | // TODO: All JSON handling in a single router | ||||||
| // router.use(bodyParser.json()); | // router.use(bodyParser.json()); | ||||||
| @@ -56,18 +56,19 @@ router.get("/", (req, res) => { | |||||||
|   res.send("welcome to seasoned api"); |   res.send("welcome to seasoned api"); | ||||||
| }); | }); | ||||||
|  |  | ||||||
| app.use(Raven.errorHandler()); | // app.use(Raven.errorHandler()); | ||||||
| app.use((err, req, res, next) => { | // app.use((err, req, res) => { | ||||||
|   res.statusCode = 500; | //   res.statusCode = 500; | ||||||
|   res.end(res.sentry + "\n"); | //   res.end(`${res.sentry}\n`); | ||||||
| }); | // }); | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * User |  * User | ||||||
|  */ |  */ | ||||||
| router.post("/v1/user", require("./controllers/user/register.js")); | router.post("/v1/user", require("./controllers/user/register")); | ||||||
| router.post("/v1/user/login", require("./controllers/user/login.js")); | router.post("/v1/user/login", require("./controllers/user/login")); | ||||||
| router.post("/v1/user/logout", require("./controllers/user/logout.js")); | router.post("/v1/user/logout", require("./controllers/user/logout")); | ||||||
|  |  | ||||||
| router.get( | router.get( | ||||||
|   "/v1/user/settings", |   "/v1/user/settings", | ||||||
|   mustBeAuthenticated, |   mustBeAuthenticated, | ||||||
| @@ -81,13 +82,14 @@ router.put( | |||||||
| router.get( | router.get( | ||||||
|   "/v1/user/search_history", |   "/v1/user/search_history", | ||||||
|   mustBeAuthenticated, |   mustBeAuthenticated, | ||||||
|   require("./controllers/user/searchHistory.js") |   require("./controllers/user/searchHistory") | ||||||
| ); | ); | ||||||
| router.get( | router.get( | ||||||
|   "/v1/user/requests", |   "/v1/user/requests", | ||||||
|   mustBeAuthenticated, |   mustBeAuthenticated, | ||||||
|   require("./controllers/user/requests.js") |   require("./controllers/user/requests") | ||||||
| ); | ); | ||||||
|  |  | ||||||
| router.post( | router.post( | ||||||
|   "/v1/user/link_plex", |   "/v1/user/link_plex", | ||||||
|   mustBeAuthenticated, |   mustBeAuthenticated, | ||||||
| @@ -123,46 +125,40 @@ router.get( | |||||||
| /** | /** | ||||||
|  * Seasoned |  * Seasoned | ||||||
|  */ |  */ | ||||||
| router.get("/v1/seasoned/all", require("./controllers/seasoned/readStrays.js")); | router.get("/v1/seasoned/all", require("./controllers/seasoned/readStrays")); | ||||||
| router.get( | router.get( | ||||||
|   "/v1/seasoned/:strayId", |   "/v1/seasoned/:strayId", | ||||||
|   require("./controllers/seasoned/strayById.js") |   require("./controllers/seasoned/strayById") | ||||||
| ); | ); | ||||||
| router.post( | router.post( | ||||||
|   "/v1/seasoned/verify/:strayId", |   "/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/", require("./controllers/search/multiSearch")); | ||||||
| router.get("/v2/search/movie", require("./controllers/search/movieSearch.js")); | router.get("/v2/search/movie", require("./controllers/search/movieSearch")); | ||||||
| router.get("/v2/search/show", require("./controllers/search/showSearch.js")); | router.get("/v2/search/show", require("./controllers/search/showSearch")); | ||||||
| router.get( | router.get("/v2/search/person", require("./controllers/search/personSearch")); | ||||||
|   "/v2/search/person", |  | ||||||
|   require("./controllers/search/personSearch.js") |  | ||||||
| ); |  | ||||||
|  |  | ||||||
| router.get("/v2/movie/now_playing", listController.nowPlayingMovies); | router.get("/v2/movie/now_playing", listController.nowPlayingMovies); | ||||||
| router.get("/v2/movie/popular", listController.popularMovies); | router.get("/v2/movie/popular", listController.popularMovies); | ||||||
| router.get("/v2/movie/top_rated", listController.topRatedMovies); | router.get("/v2/movie/top_rated", listController.topRatedMovies); | ||||||
| router.get("/v2/movie/upcoming", listController.upcomingMovies); | 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( | router.get( | ||||||
|   "/v2/movie/:id/release_dates", |   "/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/now_playing", listController.nowPlayingShows); | ||||||
| router.get("/v2/show/popular", listController.popularShows); | router.get("/v2/show/popular", listController.popularShows); | ||||||
| router.get("/v2/show/top_rated", listController.topRatedShows); | router.get("/v2/show/top_rated", listController.topRatedShows); | ||||||
| router.get("/v2/show/:id/credits", require("./controllers/show/credits.js")); | router.get("/v2/show/:id/credits", require("./controllers/show/credits")); | ||||||
| router.get("/v2/show/:id", require("./controllers/show/info.js")); | router.get("/v2/show/:id", require("./controllers/show/info")); | ||||||
|  |  | ||||||
| router.get( | router.get("/v2/person/:id/credits", require("./controllers/person/credits")); | ||||||
|   "/v2/person/:id/credits", | router.get("/v2/person/:id", require("./controllers/person/info")); | ||||||
|   require("./controllers/person/credits.js") |  | ||||||
| ); |  | ||||||
| router.get("/v2/person/:id", require("./controllers/person/info.js")); |  | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * Plex |  * Plex | ||||||
| @@ -172,40 +168,40 @@ router.get("/v2/plex/search", require("./controllers/plex/search")); | |||||||
| /** | /** | ||||||
|  * List |  * List | ||||||
|  */ |  */ | ||||||
| router.get("/v1/plex/search", require("./controllers/plex/searchMedia.js")); | router.get("/v1/plex/search", require("./controllers/plex/searchMedia")); | ||||||
| router.get("/v1/plex/playing", require("./controllers/plex/plexPlaying.js")); | router.get("/v1/plex/playing", require("./controllers/plex/plexPlaying")); | ||||||
| router.get("/v1/plex/request", require("./controllers/plex/searchRequest.js")); | router.get("/v1/plex/request", require("./controllers/plex/searchRequest")); | ||||||
| router.get( | router.get( | ||||||
|   "/v1/plex/request/:mediaId", |   "/v1/plex/request/:mediaId", | ||||||
|   require("./controllers/plex/readRequest.js") |   require("./controllers/plex/readRequest") | ||||||
| ); | ); | ||||||
| router.post( | router.post( | ||||||
|   "/v1/plex/request/:mediaId", |   "/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( | router.get( | ||||||
|   "/v1/plex/watch-link", |   "/v1/plex/watch-link", | ||||||
|   mustBeAuthenticated, |   mustBeAuthenticated, | ||||||
|   require("./controllers/plex/watchDirectLink.js") |   require("./controllers/plex/watchDirectLink") | ||||||
| ); | ); | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * Requests |  * Requests | ||||||
|  */ |  */ | ||||||
|  |  | ||||||
| router.get("/v2/request", require("./controllers/request/fetchAllRequests.js")); | router.get("/v2/request", require("./controllers/request/fetchAllRequests")); | ||||||
| router.get("/v2/request/:id", require("./controllers/request/getRequest.js")); | router.get("/v2/request/:id", require("./controllers/request/getRequest")); | ||||||
| router.post("/v2/request", require("./controllers/request/requestTmdbId.js")); | router.post("/v2/request", require("./controllers/request/requestTmdbId")); | ||||||
| router.get( | router.get( | ||||||
|   "/v1/plex/requests/all", |   "/v1/plex/requests/all", | ||||||
|   require("./controllers/plex/fetchRequested.js") |   require("./controllers/plex/fetchRequested") | ||||||
| ); | ); | ||||||
| router.put( | router.put( | ||||||
|   "/v1/plex/request/:requestId", |   "/v1/plex/request/:requestId", | ||||||
|   mustBeAuthenticated, |   mustBeAuthenticated, | ||||||
|   require("./controllers/plex/updateRequested.js") |   require("./controllers/plex/updateRequested") | ||||||
| ); | ); | ||||||
|  |  | ||||||
| /** | /** | ||||||
| @@ -213,24 +209,24 @@ router.put( | |||||||
|  */ |  */ | ||||||
| router.get( | router.get( | ||||||
|   "/v1/pirate/search", |   "/v1/pirate/search", | ||||||
|   mustBeAuthenticated, |   mustBeAdmin, | ||||||
|   require("./controllers/pirate/searchTheBay.js") |   require("./controllers/pirate/searchTheBay") | ||||||
| ); | ); | ||||||
| router.post( | router.post( | ||||||
|   "/v1/pirate/add", |   "/v1/pirate/add", | ||||||
|   mustBeAuthenticated, |   mustBeAdmin, | ||||||
|   require("./controllers/pirate/addMagnet.js") |   require("./controllers/pirate/addMagnet") | ||||||
| ); | ); | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * git |  * git | ||||||
|  */ |  */ | ||||||
| router.post("/v1/git/dump", require("./controllers/git/dumpHook.js")); | router.post("/v1/git/dump", require("./controllers/git/dumpHook")); | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * misc |  * misc | ||||||
|  */ |  */ | ||||||
| router.get("/v1/emoji", require("./controllers/misc/emoji.js")); | router.get("/v1/emoji", require("./controllers/misc/emoji")); | ||||||
|  |  | ||||||
| // REGISTER OUR ROUTES ------------------------------- | // REGISTER OUR ROUTES ------------------------------- | ||||||
| // all of our routes will be prefixed with /api | // all of our routes will be prefixed with /api | ||||||
|   | |||||||
| @@ -5,12 +5,8 @@ const gitRepository = new GitRepository(); | |||||||
| function dumpHookController(req, res) { | function dumpHookController(req, res) { | ||||||
|   gitRepository |   gitRepository | ||||||
|     .dumpHook(req.body) |     .dumpHook(req.body) | ||||||
|     .then(() => { |     .then(() => res.status(200)) | ||||||
|       res.status(200); |     .catch(() => res.status(500)); | ||||||
|     }) |  | ||||||
|     .catch(error => { |  | ||||||
|       res.status(500); |  | ||||||
|     }); |  | ||||||
| } | } | ||||||
|  |  | ||||||
| module.exports = dumpHookController; | module.exports = dumpHookController; | ||||||
|   | |||||||
| @@ -1,5 +1,6 @@ | |||||||
| const configuration = require("../../../config/configuration").getInstance(); | const configuration = require("../../../config/configuration").getInstance(); | ||||||
| const TMDB = require("../../../tmdb/tmdb"); | const TMDB = require("../../../tmdb/tmdb"); | ||||||
|  |  | ||||||
| const tmdb = new TMDB(configuration.get("tmdb", "apiKey")); | const tmdb = new TMDB(configuration.get("tmdb", "apiKey")); | ||||||
|  |  | ||||||
| // there should be a translate function from query params to | // there should be a translate function from query params to | ||||||
| @@ -14,21 +15,13 @@ const tmdb = new TMDB(configuration.get("tmdb", "apiKey")); | |||||||
| // + newly created (tv/latest). | // + newly created (tv/latest). | ||||||
| // + movie/latest | // + movie/latest | ||||||
| // | // | ||||||
| function handleError(error, res) { | function handleError(listname, error, res) { | ||||||
|   const { status, message } = error; |   return res.status(error?.statusCode || 500).send({ | ||||||
|  |     success: false, | ||||||
|   if (status && message) { |     message: | ||||||
|     res.status(status).send({ success: false, message }); |       error?.message || | ||||||
|   } else { |       `An unexpected error occured while requesting list with id: ${listname}` | ||||||
|     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) { | function fetchTmdbList(req, res, listname, type) { | ||||||
| @@ -38,15 +31,17 @@ function fetchTmdbList(req, res, listname, type) { | |||||||
|     return tmdb |     return tmdb | ||||||
|       .movieList(listname, page) |       .movieList(listname, page) | ||||||
|       .then(listResponse => res.send(listResponse)) |       .then(listResponse => res.send(listResponse)) | ||||||
|       .catch(error => handleError(error, res)); |       .catch(error => handleError(listname, error, res)); | ||||||
|   } else if (type === "show") { |   } | ||||||
|  |   if (type === "show") { | ||||||
|     return tmdb |     return tmdb | ||||||
|       .showList(listname, page) |       .showList(listname, page) | ||||||
|       .then(listResponse => res.send(listResponse)) |       .then(listResponse => res.send(listResponse)) | ||||||
|       .catch(error => handleError(error, res)); |       .catch(error => handleError(listname, error, res)); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   handleError( |   return handleError( | ||||||
|  |     listname, | ||||||
|     { |     { | ||||||
|       status: 400, |       status: 400, | ||||||
|       message: `'${type}' is not a valid list type.` |       message: `'${type}' is not a valid list type.` | ||||||
|   | |||||||
| @@ -10,20 +10,12 @@ const movieCreditsController = (req, res) => { | |||||||
|     .movieCredits(movieId) |     .movieCredits(movieId) | ||||||
|     .then(credits => res.send(credits.createJsonResponse())) |     .then(credits => res.send(credits.createJsonResponse())) | ||||||
|     .catch(error => { |     .catch(error => { | ||||||
|       const { status, message } = error; |       return res.status(error?.statusCode || 500).send({ | ||||||
|  |         success: false, | ||||||
|       if (status && message) { |         message: | ||||||
|         res.status(status).send({ success: false, message }); |           error?.message || | ||||||
|       } else { |           `An unexpected error occured while requesting credits for movie with id: ${movieId}` | ||||||
|         // 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" |  | ||||||
|           }); |  | ||||||
|       } |  | ||||||
|     }); |     }); | ||||||
| }; | }; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,22 +1,10 @@ | |||||||
| const configuration = require("../../../config/configuration").getInstance(); | const configuration = require("../../../config/configuration").getInstance(); | ||||||
| const TMDB = require("../../../tmdb/tmdb"); | const TMDB = require("../../../tmdb/tmdb"); | ||||||
| const Plex = require("../../../plex/plex"); | const Plex = require("../../../plex/plex"); | ||||||
|  |  | ||||||
| const tmdb = new TMDB(configuration.get("tmdb", "apiKey")); | const tmdb = new TMDB(configuration.get("tmdb", "apiKey")); | ||||||
| const plex = new Plex(configuration.get("plex", "ip")); | 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 { |  | ||||||
|     console.log("caught movieinfo controller error", error); |  | ||||||
|     res.status(500).send({ |  | ||||||
|       message: "An unexpected error occured while requesting movie info" |  | ||||||
|     }); |  | ||||||
|   } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * Controller: Retrieve information for a movie |  * Controller: Retrieve information for a movie | ||||||
|  * @param {Request} req http request variable |  * @param {Request} req http request variable | ||||||
| @@ -25,21 +13,18 @@ function handleError(error, res) { | |||||||
|  */ |  */ | ||||||
| async function movieInfoController(req, res) { | async function movieInfoController(req, res) { | ||||||
|   const movieId = req.params.id; |   const movieId = req.params.id; | ||||||
|   let { credits, release_dates, check_existance } = req.query; |  | ||||||
|  |  | ||||||
|   credits && credits.toLowerCase() === "true" |   let credits = req.query?.credits; | ||||||
|     ? (credits = true) |   let releaseDates = req.query?.release_dates; | ||||||
|     : (credits = false); |   let checkExistance = req.query?.check_existance; | ||||||
|   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 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 (credits) tmdbQueue.push(tmdb.movieCredits(movieId)); | ||||||
|   if (release_dates) tmdbQueue.push(tmdb.movieReleaseDates(movieId)); |   if (releaseDates) tmdbQueue.push(tmdb.movieReleaseDates(movieId)); | ||||||
|  |  | ||||||
|   try { |   try { | ||||||
|     const [Movie, Credits, ReleaseDates] = await Promise.all(tmdbQueue); |     const [Movie, Credits, ReleaseDates] = await Promise.all(tmdbQueue); | ||||||
| @@ -47,24 +32,22 @@ async function movieInfoController(req, res) { | |||||||
|     const movie = Movie.createJsonResponse(); |     const movie = Movie.createJsonResponse(); | ||||||
|     if (Credits) movie.credits = Credits.createJsonResponse(); |     if (Credits) movie.credits = Credits.createJsonResponse(); | ||||||
|     if (ReleaseDates) |     if (ReleaseDates) | ||||||
|       movie.release_dates = ReleaseDates.createJsonResponse().results; |       movie.releaseDates = ReleaseDates.createJsonResponse().results; | ||||||
|  |  | ||||||
|     if (check_existance) { |     if (checkExistance) { | ||||||
|       try { |       try { | ||||||
|         movie.exists_in_plex = await plex.existsInPlex(movie); |         movie.exists_in_plex = await plex.existsInPlex(movie); | ||||||
|       } catch (error) { |       } catch {} | ||||||
|         if (error.status === 401) { |  | ||||||
|           console.log("Unathorized request, check plex server LAN settings"); |  | ||||||
|         } else { |  | ||||||
|           console.log("Unkown error from plex!"); |  | ||||||
|         } |  | ||||||
|         console.log(error?.message); |  | ||||||
|       } |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     res.send(movie); |     return res.send(movie); | ||||||
|   } catch (error) { |   } 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}` | ||||||
|  |     }); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -10,20 +10,12 @@ const movieReleaseDatesController = (req, res) => { | |||||||
|     .movieReleaseDates(movieId) |     .movieReleaseDates(movieId) | ||||||
|     .then(releaseDates => res.send(releaseDates.createJsonResponse())) |     .then(releaseDates => res.send(releaseDates.createJsonResponse())) | ||||||
|     .catch(error => { |     .catch(error => { | ||||||
|       const { status, message } = error; |       return res.status(error?.statusCode || 500).send({ | ||||||
|  |         success: false, | ||||||
|       if (status && message) { |         message: | ||||||
|         res.status(status).send({ success: false, message }); |           error?.message || | ||||||
|       } else { |           `An unexpected error occured while requesting release dates for movie with id: ${movieId}` | ||||||
|         // 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" |  | ||||||
|           }); |  | ||||||
|       } |  | ||||||
|     }); |     }); | ||||||
| }; | }; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,5 +1,6 @@ | |||||||
| const configuration = require("../../../config/configuration").getInstance(); | const configuration = require("../../../config/configuration").getInstance(); | ||||||
| const TMDB = require("../../../tmdb/tmdb"); | const TMDB = require("../../../tmdb/tmdb"); | ||||||
|  |  | ||||||
| const tmdb = new TMDB(configuration.get("tmdb", "apiKey")); | const tmdb = new TMDB(configuration.get("tmdb", "apiKey")); | ||||||
|  |  | ||||||
| const personCreditsController = (req, res) => { | const personCreditsController = (req, res) => { | ||||||
| @@ -9,17 +10,12 @@ const personCreditsController = (req, res) => { | |||||||
|     .personCredits(personId) |     .personCredits(personId) | ||||||
|     .then(credits => res.send(credits)) |     .then(credits => res.send(credits)) | ||||||
|     .catch(error => { |     .catch(error => { | ||||||
|       const { status, message } = error; |       return res.status(error?.statusCode || 500).send({ | ||||||
|  |         success: false, | ||||||
|       if (status && message) { |         message: | ||||||
|         res.status(status).send({ success: false, message }); |           error?.message || | ||||||
|       } else { |           `An unexpected error occured while requesting info for person with id ${personId}.` | ||||||
|         // 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" |  | ||||||
|         }); |  | ||||||
|       } |  | ||||||
|     }); |     }); | ||||||
| }; | }; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,20 +1,8 @@ | |||||||
| const configuration = require("../../../config/configuration").getInstance(); | const configuration = require("../../../config/configuration").getInstance(); | ||||||
| const TMDB = require("../../../tmdb/tmdb"); | const TMDB = require("../../../tmdb/tmdb"); | ||||||
|  |  | ||||||
| const tmdb = new TMDB(configuration.get("tmdb", "apiKey")); | 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 { |  | ||||||
|     console.log("caught personinfo controller error", error); |  | ||||||
|     res.status(500).send({ |  | ||||||
|       message: "An unexpected error occured while requesting person info." |  | ||||||
|     }); |  | ||||||
|   } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * Controller: Retrieve information for a person |  * Controller: Retrieve information for a person | ||||||
|  * @param {Request} req http request variable |  * @param {Request} req http request variable | ||||||
| @@ -25,13 +13,10 @@ function handleError(error, res) { | |||||||
| async function personInfoController(req, res) { | async function personInfoController(req, res) { | ||||||
|   const personId = req.params.id; |   const personId = req.params.id; | ||||||
|   let { credits } = req.query; |   let { credits } = req.query; | ||||||
|   arguments; |  | ||||||
|  |  | ||||||
|   credits && credits.toLowerCase() === "true" |   credits = credits?.toLowerCase() === "true"; | ||||||
|     ? (credits = true) |  | ||||||
|     : (credits = false); |  | ||||||
|  |  | ||||||
|   let tmdbQueue = [tmdb.personInfo(personId)]; |   const tmdbQueue = [tmdb.personInfo(personId)]; | ||||||
|   if (credits) tmdbQueue.push(tmdb.personCredits(personId)); |   if (credits) tmdbQueue.push(tmdb.personCredits(personId)); | ||||||
|  |  | ||||||
|   try { |   try { | ||||||
| @@ -42,7 +27,12 @@ async function personInfoController(req, res) { | |||||||
|  |  | ||||||
|     return res.send(person); |     return res.send(person); | ||||||
|   } catch (error) { |   } 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 person with id: ${personId}` | ||||||
|  |     }); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -8,14 +8,11 @@ | |||||||
| const PirateRepository = require("../../../pirate/pirateRepository"); | const PirateRepository = require("../../../pirate/pirateRepository"); | ||||||
|  |  | ||||||
| function addMagnet(req, res) { | function addMagnet(req, res) { | ||||||
|   const magnet = req.body.magnet; |   const { magnet, name } = req.body; | ||||||
|   const name = req.body.name; |   const tmdbId = req.body?.tmdb_id; | ||||||
|   const tmdb_id = req.body.tmdb_id; |  | ||||||
|  |  | ||||||
|   PirateRepository.AddMagnet(magnet, name, tmdb_id) |   PirateRepository.AddMagnet(magnet, name, tmdbId) | ||||||
|     .then(result => { |     .then(result => res.send(result)) | ||||||
|       res.send(result); |  | ||||||
|     }) |  | ||||||
|     .catch(error => { |     .catch(error => { | ||||||
|       res.status(500).send({ success: false, message: error.message }); |       res.status(500).send({ success: false, message: error.message }); | ||||||
|     }); |     }); | ||||||
|   | |||||||
| @@ -1,4 +1,4 @@ | |||||||
| const RequestRepository = require("../../../plex/requestRepository.js"); | const RequestRepository = require("../../../plex/requestRepository"); | ||||||
|  |  | ||||||
| const requestRepository = new RequestRepository(); | const requestRepository = new RequestRepository(); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -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) { | 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; | module.exports = hookDumpController; | ||||||
|   | |||||||
| @@ -9,7 +9,7 @@ const requestRepository = new RequestRepository(); | |||||||
|  * @returns {Callback} |  * @returns {Callback} | ||||||
|  */ |  */ | ||||||
| function readRequestController(req, res) { | function readRequestController(req, res) { | ||||||
|   const mediaId = req.params.mediaId; |   const { mediaId } = req.params; | ||||||
|   const { type } = req.query; |   const { type } = req.query; | ||||||
|   requestRepository |   requestRepository | ||||||
|     .lookup(mediaId, type) |     .lookup(mediaId, type) | ||||||
|   | |||||||
| @@ -1,5 +1,6 @@ | |||||||
| const configuration = require("../../../config/configuration").getInstance(); | const configuration = require("../../../config/configuration").getInstance(); | ||||||
| const Plex = require("../../../plex/plex"); | const Plex = require("../../../plex/plex"); | ||||||
|  |  | ||||||
| const plex = new Plex(configuration.get("plex", "ip")); | const plex = new Plex(configuration.get("plex", "ip")); | ||||||
|  |  | ||||||
| /** | /** | ||||||
| @@ -16,12 +17,10 @@ function searchPlexController(req, res) { | |||||||
|       if (movies.length > 0) { |       if (movies.length > 0) { | ||||||
|         res.send(movies); |         res.send(movies); | ||||||
|       } else { |       } else { | ||||||
|         res |         res.status(404).send({ | ||||||
|           .status(404) |           success: false, | ||||||
|           .send({ |           message: "Search query did not give any results from plex." | ||||||
|             success: false, |         }); | ||||||
|             message: "Search query did not give any results from plex." |  | ||||||
|           }); |  | ||||||
|       } |       } | ||||||
|     }) |     }) | ||||||
|     .catch(error => { |     .catch(error => { | ||||||
|   | |||||||
| @@ -19,12 +19,10 @@ function searchMediaController(req, res) { | |||||||
|       if (media !== undefined || media.length > 0) { |       if (media !== undefined || media.length > 0) { | ||||||
|         res.send(media); |         res.send(media); | ||||||
|       } else { |       } else { | ||||||
|         res |         res.status(404).send({ | ||||||
|           .status(404) |           success: false, | ||||||
|           .send({ |           message: "Search query did not return any results." | ||||||
|             success: false, |         }); | ||||||
|             message: "Search query did not return any results." |  | ||||||
|           }); |  | ||||||
|       } |       } | ||||||
|     }) |     }) | ||||||
|     .catch(error => { |     .catch(error => { | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| const SearchHistory = require("../../../searchHistory/searchHistory"); | const SearchHistory = require("../../../searchHistory/searchHistory"); | ||||||
| const Cache = require("../../../tmdb/cache"); | const Cache = require("../../../tmdb/cache"); | ||||||
| const RequestRepository = require("../../../plex/requestRepository.js"); | const RequestRepository = require("../../../plex/requestRepository"); | ||||||
|  |  | ||||||
| const cache = new Cache(); | const cache = new Cache(); | ||||||
| const requestRepository = new RequestRepository(cache); | const requestRepository = new RequestRepository(cache); | ||||||
| @@ -10,8 +10,8 @@ function searchRequestController(req, res) { | |||||||
|   const { query, page, type } = req.query; |   const { query, page, type } = req.query; | ||||||
|   const username = req.loggedInUser ? req.loggedInUser.username : null; |   const username = req.loggedInUser ? req.loggedInUser.username : null; | ||||||
|  |  | ||||||
|   Promise.resolve() |   searchHistory | ||||||
|     .then(() => searchHistory.create(username, query)) |     .create(username, query) | ||||||
|     .then(() => requestRepository.search(query, page, type)) |     .then(() => requestRepository.search(query, page, type)) | ||||||
|     .then(searchResult => { |     .then(searchResult => { | ||||||
|       res.send(searchResult); |       res.send(searchResult); | ||||||
|   | |||||||
| @@ -1,6 +1,7 @@ | |||||||
| const configuration = require("../../../config/configuration").getInstance(); | const configuration = require("../../../config/configuration").getInstance(); | ||||||
| const RequestRepository = require("../../../request/request"); | const RequestRepository = require("../../../request/request"); | ||||||
| const TMDB = require("../../../tmdb/tmdb"); | const TMDB = require("../../../tmdb/tmdb"); | ||||||
|  |  | ||||||
| const tmdb = new TMDB(configuration.get("tmdb", "apiKey")); | const tmdb = new TMDB(configuration.get("tmdb", "apiKey")); | ||||||
| const request = new RequestRepository(); | const request = new RequestRepository(); | ||||||
|  |  | ||||||
| @@ -23,16 +24,14 @@ function submitRequestController(req, res) { | |||||||
|   const id = req.params.mediaId; |   const id = req.params.mediaId; | ||||||
|   const type = req.query.type ? req.query.type.toLowerCase() : undefined; |   const type = req.query.type ? req.query.type.toLowerCase() : undefined; | ||||||
|   const ip = req.headers["x-forwarded-for"] || req.connection.remoteAddress; |   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; |   const username = req.loggedInUser ? req.loggedInUser.username : null; | ||||||
|  |  | ||||||
|   let mediaFunction = undefined; |   let mediaFunction; | ||||||
|  |  | ||||||
|   if (type === "movie") { |   if (type === "movie") { | ||||||
|     console.log("movie"); |  | ||||||
|     mediaFunction = tmdbMovieInfo; |     mediaFunction = tmdbMovieInfo; | ||||||
|   } else if (type === "show") { |   } else if (type === "show") { | ||||||
|     console.log("show"); |  | ||||||
|     mediaFunction = tmdbShowInfo; |     mediaFunction = tmdbShowInfo; | ||||||
|   } else { |   } else { | ||||||
|     res.status(422).send({ |     res.status(422).send({ | ||||||
| @@ -48,7 +47,7 @@ function submitRequestController(req, res) { | |||||||
|  |  | ||||||
|   mediaFunction(id) |   mediaFunction(id) | ||||||
|     .then(tmdbMedia => |     .then(tmdbMedia => | ||||||
|       request.requestFromTmdb(tmdbMedia, ip, user_agent, username) |       request.requestFromTmdb(tmdbMedia, ip, userAgent, username) | ||||||
|     ) |     ) | ||||||
|     .then(() => |     .then(() => | ||||||
|       res.send({ success: true, message: "Media item successfully requested" }) |       res.send({ success: true, message: "Media item successfully requested" }) | ||||||
|   | |||||||
| @@ -10,8 +10,8 @@ const requestRepository = new RequestRepository(); | |||||||
|  */ |  */ | ||||||
| function updateRequested(req, res) { | function updateRequested(req, res) { | ||||||
|   const id = req.params.requestId; |   const id = req.params.requestId; | ||||||
|   const type = req.body.type; |   const { type } = req.body; | ||||||
|   const status = req.body.status; |   const { status } = req.body; | ||||||
|  |  | ||||||
|   requestRepository |   requestRepository | ||||||
|     .updateRequestedById(id, type, status) |     .updateRequestedById(id, type, status) | ||||||
|   | |||||||
| @@ -1,5 +1,6 @@ | |||||||
| const configuration = require("../../../config/configuration").getInstance(); | const configuration = require("../../../config/configuration").getInstance(); | ||||||
| const Plex = require("../../../plex/plex"); | const Plex = require("../../../plex/plex"); | ||||||
|  |  | ||||||
| const plex = new Plex(configuration.get("plex", "ip")); | const plex = new Plex(configuration.get("plex", "ip")); | ||||||
|  |  | ||||||
| /** | /** | ||||||
| @@ -15,7 +16,7 @@ function watchDirectLink(req, res) { | |||||||
|   plex |   plex | ||||||
|     .getDirectLinkByTitleAndYear(title, year) |     .getDirectLinkByTitleAndYear(title, year) | ||||||
|     .then(plexDirectLink => { |     .then(plexDirectLink => { | ||||||
|       if (plexDirectLink == false) |       if (plexDirectLink === false) | ||||||
|         res.status(404).send({ success: true, link: null }); |         res.status(404).send({ success: true, link: null }); | ||||||
|       else res.status(200).send({ success: true, link: plexDirectLink }); |       else res.status(200).send({ success: true, link: plexDirectLink }); | ||||||
|     }) |     }) | ||||||
|   | |||||||
| @@ -1,4 +1,5 @@ | |||||||
| const RequestRepository = require("../../../request/request"); | const RequestRepository = require("../../../request/request"); | ||||||
|  |  | ||||||
| const request = new RequestRepository(); | const request = new RequestRepository(); | ||||||
|  |  | ||||||
| /** | /** | ||||||
| @@ -8,19 +9,18 @@ const request = new RequestRepository(); | |||||||
|  * @returns {Callback} |  * @returns {Callback} | ||||||
|  */ |  */ | ||||||
| function fetchAllRequests(req, res) { | function fetchAllRequests(req, res) { | ||||||
|   let { page, filter, sort, query } = req.query; |   const { page, filter } = req.query; | ||||||
|   let sort_by = sort; |  | ||||||
|   let sort_direction = undefined; |  | ||||||
|  |  | ||||||
|   if (sort !== undefined && sort.includes(":")) { |   request | ||||||
|     [sort_by, sort_direction] = sort.split(":"); |     .fetchAll(page, filter) | ||||||
|   } |  | ||||||
|  |  | ||||||
|   Promise.resolve() |  | ||||||
|     .then(() => request.fetchAll(page, sort_by, sort_direction, filter, query)) |  | ||||||
|     .then(result => res.send(result)) |     .then(result => res.send(result)) | ||||||
|     .catch(error => { |     .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` | ||||||
|  |       }); | ||||||
|     }); |     }); | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,4 +1,5 @@ | |||||||
| const RequestRepository = require("../../../request/request"); | const RequestRepository = require("../../../request/request"); | ||||||
|  |  | ||||||
| const request = new RequestRepository(); | const request = new RequestRepository(); | ||||||
|  |  | ||||||
| /** | /** | ||||||
| @@ -8,14 +9,19 @@ const request = new RequestRepository(); | |||||||
|  * @returns {Callback} |  * @returns {Callback} | ||||||
|  */ |  */ | ||||||
| function fetchAllRequests(req, res) { | function fetchAllRequests(req, res) { | ||||||
|   const id = req.params.id; |   const { id } = req.params; | ||||||
|   const { type } = req.query; |   const { type } = req.query; | ||||||
|  |  | ||||||
|   request |   request | ||||||
|     .getRequestByIdAndType(id, type) |     .getRequestByIdAndType(id, type) | ||||||
|     .then(result => res.send(result)) |     .then(result => res.send(result)) | ||||||
|     .catch(error => { |     .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}` | ||||||
|  |       }); | ||||||
|     }); |     }); | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,6 +1,7 @@ | |||||||
| const configuration = require("../../../config/configuration").getInstance(); | const configuration = require("../../../config/configuration").getInstance(); | ||||||
| const TMDB = require("../../../tmdb/tmdb"); | const TMDB = require("../../../tmdb/tmdb"); | ||||||
| const RequestRepository = require("../../../request/request"); | const RequestRepository = require("../../../request/request"); | ||||||
|  |  | ||||||
| const tmdb = new TMDB(configuration.get("tmdb", "apiKey")); | const tmdb = new TMDB(configuration.get("tmdb", "apiKey")); | ||||||
| const request = new RequestRepository(); | const request = new RequestRepository(); | ||||||
| // const { sendSMS } = require("src/notifications/sms"); | // const { sendSMS } = require("src/notifications/sms"); | ||||||
| @@ -23,10 +24,10 @@ function requestTmdbIdController(req, res) { | |||||||
|   const { id, type } = req.body; |   const { id, type } = req.body; | ||||||
|  |  | ||||||
|   const ip = req.headers["x-forwarded-for"] || req.connection.remoteAddress; |   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; |   const username = req.loggedInUser ? req.loggedInUser.username : null; | ||||||
|  |  | ||||||
|   let mediaFunction = undefined; |   let mediaFunction; | ||||||
|  |  | ||||||
|   if (id === undefined || type === undefined) { |   if (id === undefined || type === undefined) { | ||||||
|     res.status(422).send({ |     res.status(422).send({ | ||||||
| @@ -49,7 +50,7 @@ function requestTmdbIdController(req, res) { | |||||||
|   mediaFunction(id) |   mediaFunction(id) | ||||||
|     // .catch((error) => { console.error(error); res.status(404).send({ success: false, error: 'Id not found' }) }) |     // .catch((error) => { console.error(error); res.status(404).send({ success: false, error: 'Id not found' }) }) | ||||||
|     .then(tmdbMedia => { |     .then(tmdbMedia => { | ||||||
|       request.requestFromTmdb(tmdbMedia, ip, user_agent, username); |       request.requestFromTmdb(tmdbMedia, ip, userAgent, username); | ||||||
|  |  | ||||||
|       // TODO enable SMS |       // TODO enable SMS | ||||||
|       // const url = `https://request.movie?${tmdbMedia.type}=${tmdbMedia.id}`; |       // const url = `https://request.movie?${tmdbMedia.type}=${tmdbMedia.id}`; | ||||||
|   | |||||||
| @@ -1,6 +1,7 @@ | |||||||
| const configuration = require("../../../config/configuration").getInstance(); | const configuration = require("../../../config/configuration").getInstance(); | ||||||
| const TMDB = require("../../../tmdb/tmdb"); | const TMDB = require("../../../tmdb/tmdb"); | ||||||
| const SearchHistory = require("../../../searchHistory/searchHistory"); | const SearchHistory = require("../../../searchHistory/searchHistory"); | ||||||
|  |  | ||||||
| const tmdb = new TMDB(configuration.get("tmdb", "apiKey")); | const tmdb = new TMDB(configuration.get("tmdb", "apiKey")); | ||||||
| const searchHistory = new SearchHistory(); | const searchHistory = new SearchHistory(); | ||||||
|  |  | ||||||
| @@ -13,7 +14,7 @@ const searchHistory = new SearchHistory(); | |||||||
| function movieSearchController(req, res) { | function movieSearchController(req, res) { | ||||||
|   const { query, page, adult } = req.query; |   const { query, page, adult } = req.query; | ||||||
|   const username = req.loggedInUser ? req.loggedInUser.username : null; |   const username = req.loggedInUser ? req.loggedInUser.username : null; | ||||||
|   const includeAdult = adult == "true" ? true : false; |   const includeAdult = adult === "true"; | ||||||
|  |  | ||||||
|   if (username) { |   if (username) { | ||||||
|     searchHistory.create(username, query); |     searchHistory.create(username, query); | ||||||
| @@ -23,17 +24,12 @@ function movieSearchController(req, res) { | |||||||
|     .movieSearch(query, page, includeAdult) |     .movieSearch(query, page, includeAdult) | ||||||
|     .then(movieSearchResults => res.send(movieSearchResults)) |     .then(movieSearchResults => res.send(movieSearchResults)) | ||||||
|     .catch(error => { |     .catch(error => { | ||||||
|       const { status, message } = error; |       return res.status(error?.statusCode || 500).send({ | ||||||
|  |         success: false, | ||||||
|       if (status && message) { |         message: | ||||||
|         res.status(status).send({ success: false, message }); |           error?.message || | ||||||
|       } else { |           `An unexpected error occured while searching movies with query: ${query}` | ||||||
|         // 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}` |  | ||||||
|         }); |  | ||||||
|       } |  | ||||||
|     }); |     }); | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,16 +1,10 @@ | |||||||
| const configuration = require("../../..//config/configuration").getInstance(); | const configuration = require("../../../config/configuration").getInstance(); | ||||||
| const TMDB = require("../../../tmdb/tmdb"); | const TMDB = require("../../../tmdb/tmdb"); | ||||||
| const SearchHistory = require("../../../searchHistory/searchHistory"); | const SearchHistory = require("../../../searchHistory/searchHistory"); | ||||||
|  |  | ||||||
| const tmdb = new TMDB(configuration.get("tmdb", "apiKey")); | const tmdb = new TMDB(configuration.get("tmdb", "apiKey")); | ||||||
| const searchHistory = new SearchHistory(); | 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 |  * Controller: Search for multi (movies, shows and people by query and pagey | ||||||
|  * @param {Request} req http request variable |  * @param {Request} req http request variable | ||||||
| @@ -20,26 +14,22 @@ function checkAndCreateJsonResponse(result) { | |||||||
| function multiSearchController(req, res) { | function multiSearchController(req, res) { | ||||||
|   const { query, page, adult } = req.query; |   const { query, page, adult } = req.query; | ||||||
|   const username = req.loggedInUser ? req.loggedInUser.username : null; |   const username = req.loggedInUser ? req.loggedInUser.username : null; | ||||||
|  |   const includeAdult = adult === "true"; | ||||||
|  |  | ||||||
|   if (username) { |   if (username) { | ||||||
|     searchHistory.create(username, query); |     searchHistory.create(username, query); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   return tmdb |   return tmdb | ||||||
|     .multiSearch(query, page, adult) |     .multiSearch(query, page, includeAdult) | ||||||
|     .then(multiSearchResults => res.send(multiSearchResults)) |     .then(multiSearchResults => res.send(multiSearchResults)) | ||||||
|     .catch(error => { |     .catch(error => { | ||||||
|       const { status, message } = error; |       return res.status(error?.statusCode || 500).send({ | ||||||
|  |         success: false, | ||||||
|       if (status && message) { |         message: | ||||||
|         res.status(status).send({ success: false, message }); |           error?.message || | ||||||
|       } else { |           `An unexpected error occured while searching with query: ${query}` | ||||||
|         // 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}` |  | ||||||
|         }); |  | ||||||
|       } |  | ||||||
|     }); |     }); | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,6 +1,7 @@ | |||||||
| const configuration = require("../../../config/configuration").getInstance(); | const configuration = require("../../../config/configuration").getInstance(); | ||||||
| const TMDB = require("../../../tmdb/tmdb"); | const TMDB = require("../../../tmdb/tmdb"); | ||||||
| const SearchHistory = require("../../../searchHistory/searchHistory"); | const SearchHistory = require("../../../searchHistory/searchHistory"); | ||||||
|  |  | ||||||
| const tmdb = new TMDB(configuration.get("tmdb", "apiKey")); | const tmdb = new TMDB(configuration.get("tmdb", "apiKey")); | ||||||
| const searchHistory = new SearchHistory(); | const searchHistory = new SearchHistory(); | ||||||
|  |  | ||||||
| @@ -13,7 +14,7 @@ const searchHistory = new SearchHistory(); | |||||||
| function personSearchController(req, res) { | function personSearchController(req, res) { | ||||||
|   const { query, page, adult } = req.query; |   const { query, page, adult } = req.query; | ||||||
|   const username = req.loggedInUser ? req.loggedInUser.username : null; |   const username = req.loggedInUser ? req.loggedInUser.username : null; | ||||||
|   const includeAdult = adult == "true" ? true : false; |   const includeAdult = adult === "true"; | ||||||
|  |  | ||||||
|   if (username) { |   if (username) { | ||||||
|     searchHistory.create(username, query); |     searchHistory.create(username, query); | ||||||
| @@ -23,17 +24,12 @@ function personSearchController(req, res) { | |||||||
|     .personSearch(query, page, includeAdult) |     .personSearch(query, page, includeAdult) | ||||||
|     .then(persons => res.send(persons)) |     .then(persons => res.send(persons)) | ||||||
|     .catch(error => { |     .catch(error => { | ||||||
|       const { status, message } = error; |       return res.status(error?.statusCode || 500).send({ | ||||||
|  |         success: false, | ||||||
|       if (status && message) { |         message: | ||||||
|         res.status(status).send({ success: false, message }); |           error?.message || | ||||||
|       } else { |           `An unexpected error occured while searching person with query: ${query}` | ||||||
|         // 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}` |  | ||||||
|         }); |  | ||||||
|       } |  | ||||||
|     }); |     }); | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,6 +1,7 @@ | |||||||
| const SearchHistory = require("../../../searchHistory/searchHistory"); | const SearchHistory = require("../../../searchHistory/searchHistory"); | ||||||
| const configuration = require("../../../config/configuration").getInstance(); | const configuration = require("../../../config/configuration").getInstance(); | ||||||
| const TMDB = require("../../../tmdb/tmdb"); | const TMDB = require("../../../tmdb/tmdb"); | ||||||
|  |  | ||||||
| const tmdb = new TMDB(configuration.get("tmdb", "apiKey")); | const tmdb = new TMDB(configuration.get("tmdb", "apiKey")); | ||||||
| const searchHistory = new SearchHistory(); | const searchHistory = new SearchHistory(); | ||||||
|  |  | ||||||
| @@ -13,7 +14,7 @@ const searchHistory = new SearchHistory(); | |||||||
| function showSearchController(req, res) { | function showSearchController(req, res) { | ||||||
|   const { query, page, adult } = req.query; |   const { query, page, adult } = req.query; | ||||||
|   const username = req.loggedInUser ? req.loggedInUser.username : null; |   const username = req.loggedInUser ? req.loggedInUser.username : null; | ||||||
|   const includeAdult = adult == "true" ? true : false; |   const includeAdult = adult === "true"; | ||||||
|  |  | ||||||
|   if (username) { |   if (username) { | ||||||
|     searchHistory.create(username, query); |     searchHistory.create(username, query); | ||||||
| @@ -25,7 +26,12 @@ function showSearchController(req, res) { | |||||||
|       res.send(shows); |       res.send(shows); | ||||||
|     }) |     }) | ||||||
|     .catch(error => { |     .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}` | ||||||
|  |       }); | ||||||
|     }); |     }); | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,5 +1,6 @@ | |||||||
| const configuration = require("../../../config/configuration").getInstance(); | const configuration = require("../../../config/configuration").getInstance(); | ||||||
| const TMDB = require("../../../tmdb/tmdb"); | const TMDB = require("../../../tmdb/tmdb"); | ||||||
|  |  | ||||||
| const tmdb = new TMDB(configuration.get("tmdb", "apiKey")); | const tmdb = new TMDB(configuration.get("tmdb", "apiKey")); | ||||||
|  |  | ||||||
| const showCreditsController = (req, res) => { | const showCreditsController = (req, res) => { | ||||||
| @@ -9,19 +10,12 @@ const showCreditsController = (req, res) => { | |||||||
|     .showCredits(showId) |     .showCredits(showId) | ||||||
|     .then(credits => res.send(credits.createJsonResponse())) |     .then(credits => res.send(credits.createJsonResponse())) | ||||||
|     .catch(error => { |     .catch(error => { | ||||||
|       const { status, message } = error; |       return res.status(error?.statusCode || 500).send({ | ||||||
|  |         success: false, | ||||||
|       if (status && message) { |         message: | ||||||
|         res.status(status).send({ success: false, message }); |           error?.message || | ||||||
|       } else { |           `An unexpected error occured while requesting credits for show with id: ${showId}.` | ||||||
|         // 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" |  | ||||||
|           }); |  | ||||||
|       } |  | ||||||
|     }); |     }); | ||||||
| }; | }; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,22 +1,10 @@ | |||||||
| const configuration = require("../../../config/configuration").getInstance(); | const configuration = require("../../../config/configuration").getInstance(); | ||||||
| const TMDB = require("../../../tmdb/tmdb"); | const TMDB = require("../../../tmdb/tmdb"); | ||||||
| const Plex = require("../../../plex/plex"); | const Plex = require("../../../plex/plex"); | ||||||
|  |  | ||||||
| const tmdb = new TMDB(configuration.get("tmdb", "apiKey")); | const tmdb = new TMDB(configuration.get("tmdb", "apiKey")); | ||||||
| const plex = new Plex(configuration.get("plex", "ip")); | 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 { |  | ||||||
|     console.log("caught showinfo controller error", error); |  | ||||||
|     res.status(500).send({ |  | ||||||
|       message: "An unexpected error occured while requesting show info." |  | ||||||
|     }); |  | ||||||
|   } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * Controller: Retrieve information for a show |  * Controller: Retrieve information for a show | ||||||
|  * @param {Request} req http request variable |  * @param {Request} req http request variable | ||||||
| @@ -26,16 +14,13 @@ function handleError(error, res) { | |||||||
|  |  | ||||||
| async function showInfoController(req, res) { | async function showInfoController(req, res) { | ||||||
|   const showId = req.params.id; |   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 = credits?.toLowerCase() === "true"; | ||||||
|     ? (credits = true) |   checkExistance = checkExistance?.toLowerCase() === "true"; | ||||||
|     : (credits = false); |  | ||||||
|   check_existance && check_existance.toLowerCase() === "true" |  | ||||||
|     ? (check_existance = true) |  | ||||||
|     : (check_existance = false); |  | ||||||
|  |  | ||||||
|   let tmdbQueue = [tmdb.showInfo(showId)]; |   const tmdbQueue = [tmdb.showInfo(showId)]; | ||||||
|   if (credits) tmdbQueue.push(tmdb.showCredits(showId)); |   if (credits) tmdbQueue.push(tmdb.showCredits(showId)); | ||||||
|  |  | ||||||
|   try { |   try { | ||||||
| @@ -44,11 +29,21 @@ async function showInfoController(req, res) { | |||||||
|     const show = Show.createJsonResponse(); |     const show = Show.createJsonResponse(); | ||||||
|     if (credits) show.credits = Credits.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); |     return res.send(show); | ||||||
|   } catch (error) { |   } 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}` | ||||||
|  |     }); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,19 +1,33 @@ | |||||||
| const UserRepository = require("../../../user/userRepository"); |  | ||||||
| const userRepository = new UserRepository(); |  | ||||||
| const fetch = require("node-fetch"); |  | ||||||
| const FormData = require("form-data"); | const FormData = require("form-data"); | ||||||
|  | const UserRepository = require("../../../user/userRepository"); | ||||||
|  |  | ||||||
|  | const userRepository = new UserRepository(); | ||||||
|  |  | ||||||
|  | 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) { | function handleError(error, res) { | ||||||
|   let { status, message, source } = error; |   const status = error?.status; | ||||||
|  |   let { message, source } = error; | ||||||
|  |  | ||||||
|   if (status && message) { |   if (status && message) { | ||||||
|     if (status === 401) { |     if (status === 401) { | ||||||
|       (message = "Unauthorized. Please check plex credentials."), |       message = "Unauthorized. Please check plex credentials."; | ||||||
|         (source = "plex"); |       source = "plex"; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     res.status(status).send({ success: false, message, source }); |     res.status(status).send({ success: false, message, source }); | ||||||
|   } else { |   } else { | ||||||
|  |     // eslint-disable-next-line no-console | ||||||
|     console.log("caught authenticate plex account controller error", error); |     console.log("caught authenticate plex account controller error", error); | ||||||
|     res.status(500).send({ |     res.status(500).send({ | ||||||
|       message: |       message: | ||||||
| @@ -25,11 +39,7 @@ function handleError(error, res) { | |||||||
|  |  | ||||||
| function handleResponse(response) { | function handleResponse(response) { | ||||||
|   if (!response.ok) { |   if (!response.ok) { | ||||||
|     throw { |     throw new PlexAuthenticationError(response.statusText, response.status); | ||||||
|       success: false, |  | ||||||
|       status: response.status, |  | ||||||
|       message: response.statusText |  | ||||||
|     }; |  | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   return response.json(); |   return response.json(); | ||||||
| @@ -63,7 +73,7 @@ function link(req, res) { | |||||||
|  |  | ||||||
|   return plexAuthenticate(username, password) |   return plexAuthenticate(username, password) | ||||||
|     .then(plexUser => userRepository.linkPlexUserId(user.username, plexUser.id)) |     .then(plexUser => userRepository.linkPlexUserId(user.username, plexUser.id)) | ||||||
|     .then(response => |     .then(() => | ||||||
|       res.send({ |       res.send({ | ||||||
|         success: true, |         success: true, | ||||||
|         message: |         message: | ||||||
| @@ -78,7 +88,7 @@ function unlink(req, res) { | |||||||
|  |  | ||||||
|   return userRepository |   return userRepository | ||||||
|     .unlinkPlexUserId(username) |     .unlinkPlexUserId(username) | ||||||
|     .then(response => |     .then(() => | ||||||
|       res.send({ |       res.send({ | ||||||
|         success: true, |         success: true, | ||||||
|         message: "Successfully unlinked plex account from seasoned request." |         message: "Successfully unlinked plex account from seasoned request." | ||||||
|   | |||||||
| @@ -27,7 +27,7 @@ const cookieOptions = { | |||||||
|  */ |  */ | ||||||
| async function loginController(req, res) { | async function loginController(req, res) { | ||||||
|   const user = new User(req.body.username); |   const user = new User(req.body.username); | ||||||
|   const password = req.body.password; |   const { password } = req.body; | ||||||
|  |  | ||||||
|   try { |   try { | ||||||
|     const [loggedIn, isAdmin, settings] = await Promise.all([ |     const [loggedIn, isAdmin, settings] = await Promise.all([ | ||||||
| @@ -43,11 +43,7 @@ async function loginController(req, res) { | |||||||
|       }); |       }); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     const token = new Token( |     const token = new Token(user, isAdmin === 1, settings).toString(secret); | ||||||
|       user, |  | ||||||
|       isAdmin === 1 ? true : false, |  | ||||||
|       settings |  | ||||||
|     ).toString(secret); |  | ||||||
|  |  | ||||||
|     return res.cookie("authorization", token, cookieOptions).status(200).send({ |     return res.cookie("authorization", token, cookieOptions).status(200).send({ | ||||||
|       success: true, |       success: true, | ||||||
|   | |||||||
| @@ -1,12 +1,10 @@ | |||||||
| const User = require("../../../user/user"); | const User = require("../../../user/user"); | ||||||
| const Token = require("../../../user/token"); | const Token = require("../../../user/token"); | ||||||
| const UserSecurity = require("../../../user/userSecurity"); | const UserSecurity = require("../../../user/userSecurity"); | ||||||
| const UserRepository = require("../../../user/userRepository"); |  | ||||||
| const configuration = require("../../../config/configuration").getInstance(); | const configuration = require("../../../config/configuration").getInstance(); | ||||||
|  |  | ||||||
| const secret = configuration.get("authentication", "secret"); | const secret = configuration.get("authentication", "secret"); | ||||||
| const userSecurity = new UserSecurity(); | const userSecurity = new UserSecurity(); | ||||||
| const userRepository = new UserRepository(); |  | ||||||
|  |  | ||||||
| const isProduction = process.env.NODE_ENV === "production"; | const isProduction = process.env.NODE_ENV === "production"; | ||||||
| const cookieOptions = { | const cookieOptions = { | ||||||
| @@ -24,7 +22,7 @@ const cookieOptions = { | |||||||
|  */ |  */ | ||||||
| function registerController(req, res) { | function registerController(req, res) { | ||||||
|   const user = new User(req.body.username, req.body.email); |   const user = new User(req.body.username, req.body.email); | ||||||
|   const password = req.body.password; |   const { password } = req.body; | ||||||
|  |  | ||||||
|   userSecurity |   userSecurity | ||||||
|     .createNewUser(user, password) |     .createNewUser(user, password) | ||||||
|   | |||||||
| @@ -1,4 +1,4 @@ | |||||||
| const RequestRepository = require("../../../plex/requestRepository.js"); | const RequestRepository = require("../../../plex/requestRepository"); | ||||||
|  |  | ||||||
| const requestRepository = new RequestRepository(); | const requestRepository = new RequestRepository(); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,4 +1,5 @@ | |||||||
| const UserRepository = require("../../../user/userRepository"); | const UserRepository = require("../../../user/userRepository"); | ||||||
|  |  | ||||||
| const userRepository = new UserRepository(); | const userRepository = new UserRepository(); | ||||||
| /** | /** | ||||||
|  * Controller: Retrieves settings of a logged in user |  * Controller: Retrieves settings of a logged in user | ||||||
| @@ -22,11 +23,12 @@ const getSettingsController = (req, res) => { | |||||||
| const updateSettingsController = (req, res) => { | const updateSettingsController = (req, res) => { | ||||||
|   const username = req.loggedInUser ? req.loggedInUser.username : null; |   const username = req.loggedInUser ? req.loggedInUser.username : null; | ||||||
|  |  | ||||||
|   const idempotencyKey = req.headers("Idempotency-Key"); // TODO implement better transactions |   // const idempotencyKey = req.headers("Idempotency-Key"); // TODO implement better transactions | ||||||
|   const { dark_mode, emoji } = req.body; |   const emoji = req.body?.emoji; | ||||||
|  |   const darkMode = req.body?.dark_mode; | ||||||
|  |  | ||||||
|   userRepository |   userRepository | ||||||
|     .updateSettings(username, dark_mode, emoji) |     .updateSettings(username, darkMode, emoji) | ||||||
|     .then(settings => { |     .then(settings => { | ||||||
|       res.send({ success: true, settings }); |       res.send({ success: true, settings }); | ||||||
|     }) |     }) | ||||||
|   | |||||||
| @@ -1,5 +1,6 @@ | |||||||
| const configuration = require("../../../config/configuration").getInstance(); | const configuration = require("../../../config/configuration").getInstance(); | ||||||
| const Tautulli = require("../../../tautulli/tautulli"); | const Tautulli = require("../../../tautulli/tautulli"); | ||||||
|  |  | ||||||
| const apiKey = configuration.get("tautulli", "apiKey"); | const apiKey = configuration.get("tautulli", "apiKey"); | ||||||
| const ip = configuration.get("tautulli", "ip"); | const ip = configuration.get("tautulli", "ip"); | ||||||
| const port = configuration.get("tautulli", "port"); | const port = configuration.get("tautulli", "port"); | ||||||
| @@ -10,19 +11,18 @@ function handleError(error, res) { | |||||||
|  |  | ||||||
|   if (status && message) { |   if (status && message) { | ||||||
|     return res.status(status).send({ success: false, message }); |     return res.status(status).send({ success: false, message }); | ||||||
|   } else { |  | ||||||
|     console.log("caught view history controller error", error); |  | ||||||
|     return res.status(500).send({ |  | ||||||
|       message: "An unexpected error occured while fetching view history" |  | ||||||
|     }); |  | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   return res.status(500).send({ | ||||||
|  |     message: "An unexpected error occured while fetching view history" | ||||||
|  |   }); | ||||||
| } | } | ||||||
|  |  | ||||||
| function watchTimeStatsController(req, res) { | function watchTimeStatsController(req, res) { | ||||||
|   const user = req.loggedInUser; |   const user = req.loggedInUser; | ||||||
|  |  | ||||||
|   return tautulli |   return tautulli | ||||||
|     .watchTimeStats(user.plex_userid) |     .watchTimeStats(user.plexUserId) | ||||||
|     .then(data => { |     .then(data => { | ||||||
|       return res.send({ |       return res.send({ | ||||||
|         success: true, |         success: true, | ||||||
| @@ -35,10 +35,11 @@ function watchTimeStatsController(req, res) { | |||||||
|  |  | ||||||
| function getPlaysByDayOfWeekController(req, res) { | function getPlaysByDayOfWeekController(req, res) { | ||||||
|   const user = req.loggedInUser; |   const user = req.loggedInUser; | ||||||
|   const { days, y_axis } = req.query; |   const days = req.query?.days; | ||||||
|  |   const yAxis = req.query?.y_axis; | ||||||
|  |  | ||||||
|   return tautulli |   return tautulli | ||||||
|     .getPlaysByDayOfWeek(user.plex_userid, days, y_axis) |     .getPlaysByDayOfWeek(user.plexUserId, days, yAxis) | ||||||
|     .then(data => |     .then(data => | ||||||
|       res.send({ |       res.send({ | ||||||
|         success: true, |         success: true, | ||||||
| @@ -51,7 +52,8 @@ function getPlaysByDayOfWeekController(req, res) { | |||||||
|  |  | ||||||
| function getPlaysByDaysController(req, res) { | function getPlaysByDaysController(req, res) { | ||||||
|   const user = req.loggedInUser; |   const user = req.loggedInUser; | ||||||
|   const { days, y_axis } = req.query; |   const days = req.query?.days; | ||||||
|  |   const yAxis = req.query?.y_axis; | ||||||
|  |  | ||||||
|   if (days === undefined) { |   if (days === undefined) { | ||||||
|     return res.status(422).send({ |     return res.status(422).send({ | ||||||
| @@ -61,7 +63,7 @@ function getPlaysByDaysController(req, res) { | |||||||
|   } |   } | ||||||
|  |  | ||||||
|   const allowedYAxisDataType = ["plays", "duration"]; |   const allowedYAxisDataType = ["plays", "duration"]; | ||||||
|   if (!allowedYAxisDataType.includes(y_axis)) { |   if (!allowedYAxisDataType.includes(yAxis)) { | ||||||
|     return res.status(422).send({ |     return res.status(422).send({ | ||||||
|       success: false, |       success: false, | ||||||
|       message: `Y axis parameter must be one of values: [${allowedYAxisDataType}]` |       message: `Y axis parameter must be one of values: [${allowedYAxisDataType}]` | ||||||
| @@ -69,7 +71,7 @@ function getPlaysByDaysController(req, res) { | |||||||
|   } |   } | ||||||
|  |  | ||||||
|   return tautulli |   return tautulli | ||||||
|     .getPlaysByDays(user.plex_userid, days, y_axis) |     .getPlaysByDays(user.plexUserId, days, yAxis) | ||||||
|     .then(data => |     .then(data => | ||||||
|       res.send({ |       res.send({ | ||||||
|         success: true, |         success: true, | ||||||
| @@ -86,7 +88,7 @@ function userViewHistoryController(req, res) { | |||||||
|   // and then return 501 Not implemented |   // and then return 501 Not implemented | ||||||
|  |  | ||||||
|   return tautulli |   return tautulli | ||||||
|     .viewHistory(user.plex_userid) |     .viewHistory(user.plexUserId) | ||||||
|     .then(data => { |     .then(data => { | ||||||
|       return res.send({ |       return res.send({ | ||||||
|         success: true, |         success: true, | ||||||
|   | |||||||
| @@ -1,31 +1,31 @@ | |||||||
| const establishedDatabase = require("../../database/database"); | const establishedDatabase = require("../../database/database"); | ||||||
|  |  | ||||||
|  | // eslint-disable-next-line consistent-return | ||||||
| const mustBeAdmin = (req, res, next) => { | const mustBeAdmin = (req, res, next) => { | ||||||
|   let database = establishedDatabase; |   const database = establishedDatabase; | ||||||
|  |  | ||||||
|   if (req.loggedInUser === undefined) { |   if (req.loggedInUser === undefined) { | ||||||
|     return res.status(401).send({ |     res.status(401).send({ | ||||||
|       success: false, |       success: false, | ||||||
|       message: "You must be logged in." |       message: "You must be logged in." | ||||||
|     }); |     }); | ||||||
|   } else { |  | ||||||
|     database |  | ||||||
|       .get( |  | ||||||
|         `SELECT admin FROM user WHERE user_name IS ?`, |  | ||||||
|         req.loggedInUser.username |  | ||||||
|       ) |  | ||||||
|       .then(isAdmin => { |  | ||||||
|         console.log(isAdmin, req.loggedInUser); |  | ||||||
|         if (isAdmin.admin == 0) { |  | ||||||
|           return res.status(401).send({ |  | ||||||
|             success: false, |  | ||||||
|             message: "You must be logged in as a admin." |  | ||||||
|           }); |  | ||||||
|         } |  | ||||||
|       }); |  | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   return next(); |   database | ||||||
|  |     .get( | ||||||
|  |       `SELECT admin FROM user WHERE user_name IS ?`, | ||||||
|  |       req.loggedInUser.username | ||||||
|  |     ) | ||||||
|  |     .then(isAdmin => { | ||||||
|  |       if (isAdmin.admin === 0) { | ||||||
|  |         return res.status(401).send({ | ||||||
|  |           success: false, | ||||||
|  |           message: "You must be logged in as a admin." | ||||||
|  |         }); | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       return next(); | ||||||
|  |     }); | ||||||
| }; | }; | ||||||
|  |  | ||||||
| module.exports = mustBeAdmin; | module.exports = mustBeAdmin; | ||||||
|   | |||||||
| @@ -1,3 +1,4 @@ | |||||||
|  | // eslint-disable-next-line consistent-return | ||||||
| const mustBeAuthenticated = (req, res, next) => { | const mustBeAuthenticated = (req, res, next) => { | ||||||
|   if (req.loggedInUser === undefined) { |   if (req.loggedInUser === undefined) { | ||||||
|     return res.status(401).send({ |     return res.status(401).send({ | ||||||
| @@ -5,7 +6,8 @@ const mustBeAuthenticated = (req, res, next) => { | |||||||
|       message: "You must be logged in." |       message: "You must be logged in." | ||||||
|     }); |     }); | ||||||
|   } |   } | ||||||
|   return next(); |  | ||||||
|  |   next(); | ||||||
| }; | }; | ||||||
|  |  | ||||||
| module.exports = mustBeAuthenticated; | module.exports = mustBeAuthenticated; | ||||||
|   | |||||||
| @@ -1,35 +1,36 @@ | |||||||
| const establishedDatabase = require("../../database/database"); | const establishedDatabase = require("../../database/database"); | ||||||
|  |  | ||||||
|  | /* eslint-disable consistent-return */ | ||||||
| const mustHaveAccountLinkedToPlex = (req, res, next) => { | const mustHaveAccountLinkedToPlex = (req, res, next) => { | ||||||
|   let database = establishedDatabase; |   const database = establishedDatabase; | ||||||
|   const loggedInUser = req.loggedInUser; |   const { loggedInUser } = req; | ||||||
|  |  | ||||||
|   if (loggedInUser === undefined) { |   if (loggedInUser === null) { | ||||||
|     return res.status(401).send({ |     return res.status(401).send({ | ||||||
|       success: false, |       success: false, | ||||||
|       message: "You must have your account linked to a plex account." |       message: "You must have your account linked to a plex account." | ||||||
|     }); |     }); | ||||||
|   } else { |  | ||||||
|     database |  | ||||||
|       .get( |  | ||||||
|         `SELECT plex_userid FROM settings WHERE user_name IS ?`, |  | ||||||
|         loggedInUser.username |  | ||||||
|       ) |  | ||||||
|       .then(row => { |  | ||||||
|         const plex_userid = row.plex_userid; |  | ||||||
|  |  | ||||||
|         if (plex_userid === null || plex_userid === undefined) { |  | ||||||
|           return res.status(403).send({ |  | ||||||
|             success: false, |  | ||||||
|             message: |  | ||||||
|               "No plex account user id found for your user. Please authenticate your plex account at /user/authenticate." |  | ||||||
|           }); |  | ||||||
|         } else { |  | ||||||
|           req.loggedInUser.plex_userid = plex_userid; |  | ||||||
|           return next(); |  | ||||||
|         } |  | ||||||
|       }); |  | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   database | ||||||
|  |     .get( | ||||||
|  |       `SELECT plex_userid FROM settings WHERE user_name IS ?`, | ||||||
|  |       loggedInUser.username | ||||||
|  |     ) | ||||||
|  |     .then(row => { | ||||||
|  |       const plexUserId = row.plex_userid; | ||||||
|  |       if (plexUserId === null) { | ||||||
|  |         return res.status(403).send({ | ||||||
|  |           success: false, | ||||||
|  |           message: | ||||||
|  |             "No plex account user id found for your user. Please authenticate your plex account at /user/authenticate." | ||||||
|  |         }); | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       req.loggedInUser.plexUserId = plexUserId; | ||||||
|  |       next(); | ||||||
|  |     }); | ||||||
| }; | }; | ||||||
|  | /* eslint-enable consistent-return */ | ||||||
|  |  | ||||||
| module.exports = mustHaveAccountLinkedToPlex; | module.exports = mustHaveAccountLinkedToPlex; | ||||||
|   | |||||||
| @@ -11,22 +11,18 @@ const reqTokenToUser = (req, res, next) => { | |||||||
|   const cookieAuthToken = req.cookies.authorization; |   const cookieAuthToken = req.cookies.authorization; | ||||||
|   const headerAuthToken = req.headers.authorization; |   const headerAuthToken = req.headers.authorization; | ||||||
|  |  | ||||||
|   if (cookieAuthToken || headerAuthToken) { |   if (!(cookieAuthToken || headerAuthToken)) { | ||||||
|     try { |     return next(); | ||||||
|       const token = Token.fromString( |  | ||||||
|         cookieAuthToken || headerAuthToken, |  | ||||||
|         secret |  | ||||||
|       ); |  | ||||||
|       req.loggedInUser = token.user; |  | ||||||
|     } catch (error) { |  | ||||||
|       req.loggedInUser = undefined; |  | ||||||
|     } |  | ||||||
|   } else { |  | ||||||
|     // guest session |  | ||||||
|     console.debug("No auth token in header or cookie."); |  | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   next(); |   try { | ||||||
|  |     const token = Token.fromString(cookieAuthToken || headerAuthToken, secret); | ||||||
|  |     req.loggedInUser = token.user; | ||||||
|  |   } catch (error) { | ||||||
|  |     req.loggedInUser = null; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return next(); | ||||||
| }; | }; | ||||||
|  |  | ||||||
| module.exports = reqTokenToUser; | module.exports = reqTokenToUser; | ||||||
|   | |||||||
| @@ -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 | ||||||
|  |   } | ||||||
|  | ] | ||||||
|   | |||||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
		Reference in New Issue
	
	Block a user