Compare commits
	
		
			60 Commits
		
	
	
		
			v1.1.1
			...
			feat/cooki
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 1b7a754224 | |||
| 5d91f1bae7 | |||
| cdcfae56e7 | |||
| f2c77e092d | |||
| d6ac7e55e9 | |||
| a3543090f2 | |||
| 041e944783 | |||
| bfd31ebd23 | |||
| 5036f4ca36 | |||
| 61b59ae3ea | |||
| 92c49ac523 | |||
| f680642f25 | |||
| f89486ae9e | |||
| 4d853565d1 | |||
| 91c81e5cf6 | |||
| 0ecbde9675 | |||
| d8e951c2ef | |||
| 90f3d86511 | |||
| c6791a7027 | |||
| 5b6a2c2651 | |||
| 4f7a22fff1 | |||
| 31b0c998a8 | |||
| 9ce5b476ef | |||
| 554f292e4c | |||
| d8985aaff7 | |||
| be889b8100 | |||
| b5bd672f44 | |||
| 4501bc5302 | |||
| b384e748af | |||
| c676f182b4 | |||
| 95d2b0095b | |||
| 8165cf8e85 | |||
| 14775744b0 | |||
| 559e32c059 | |||
| f4dbaf4c58 | |||
| 1d25914ae0 | |||
| 4d3d8c874c | |||
| 08433523b7 | |||
| fce8879994 | |||
| 505b126043 | |||
| 589bd7b08d | |||
| f0049ffb4e | |||
| 2b25397253 | |||
| 776f83553a | |||
| 815aaedffb | |||
| 578eff30fb | |||
| 943cbe5cb8 | |||
| f89db46bf2 | |||
| 085fb76e11 | |||
| aa4a1c2a57 | |||
| 74340afd16 | |||
| 2672266908 | |||
| f37786aa76 | |||
| 91f64e5cfb | |||
| a4d3123910 | |||
| bc6fe3ed48 | |||
| b23566509f | |||
| 341a07621d | |||
| 259ed9b06f | |||
| cddf06cbcc | 
							
								
								
									
										10
									
								
								.prettierrc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								.prettierrc
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,10 @@ | |||||||
|  | { | ||||||
|  |   "tabWidth": 2, | ||||||
|  |   "useTabs": false, | ||||||
|  |   "semi": true, | ||||||
|  |   "singleQuote": false, | ||||||
|  |   "bracketSpacing": true, | ||||||
|  |   "arrowParens": "avoid", | ||||||
|  |   "vueIndentScriptAndStyle": false, | ||||||
|  |   "trailingComma": "none" | ||||||
|  | } | ||||||
| @@ -7,6 +7,7 @@ script: | |||||||
|    - yarn coverage |    - yarn coverage | ||||||
| before_install: | before_install: | ||||||
|    - cd seasoned_api |    - cd seasoned_api | ||||||
|  |    - cp conf/development.json.example conf/development.json | ||||||
| before_script:  | before_script:  | ||||||
|    - yarn |    - yarn | ||||||
|    - curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter |    - curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter | ||||||
|   | |||||||
| @@ -3,7 +3,8 @@ | |||||||
|     "host": "../shows.db" |     "host": "../shows.db" | ||||||
|   }, |   }, | ||||||
|   "webserver": { |   "webserver": { | ||||||
| 		"port": 31459 |     "port": 31459, | ||||||
|  |     "origins": [] | ||||||
|   }, |   }, | ||||||
|   "tmdb": { |   "tmdb": { | ||||||
|     "apiKey": "" |     "apiKey": "" | ||||||
|   | |||||||
| @@ -7,31 +7,33 @@ | |||||||
|   }, |   }, | ||||||
|   "main": "webserver/server.js", |   "main": "webserver/server.js", | ||||||
|   "scripts": { |   "scripts": { | ||||||
|     "start": "cross-env SEASONED_CONFIG=conf/development.json PROD=true NODE_PATH=. babel-node src/webserver/server.js", |     "start": "cross-env SEASONED_CONFIG=conf/development.json NODE_ENV=production NODE_PATH=. babel-node src/webserver/server.js", | ||||||
|     "test": "cross-env SEASONED_CONFIG=conf/test.json NODE_PATH=. mocha --require @babel/register --recursive test/unit test/system", |     "test": "cross-env SEASONED_CONFIG=conf/test.json NODE_PATH=. mocha --require @babel/register --recursive test/unit test/system", | ||||||
|     "coverage": "cross-env SEASONED_CONFIG=conf/test.json NODE_PATH=. nyc mocha --require @babel/register --recursive test && nyc report --reporter=text-lcov | coveralls", |     "coverage": "cross-env SEASONED_CONFIG=conf/test.json NODE_PATH=. nyc mocha --require @babel/register --recursive test && nyc report --reporter=text-lcov | coveralls", | ||||||
|     "lint": "./node_modules/.bin/eslint src/", |     "lint": "./node_modules/.bin/eslint src/", | ||||||
|     "update": "cross-env SEASONED_CONFIG=conf/development.json NODE_PATH=. node src/plex/updateRequestsInPlex.js", |     "update": "cross-env SEASONED_CONFIG=conf/development.json NODE_PATH=. node scripts/updateRequestsInPlex.js", | ||||||
|     "docs": "yarn apiDocs; yarn classDocs", |     "docs": "yarn apiDocs; yarn classDocs", | ||||||
|     "apiDocs": "", |     "apiDocs": "", | ||||||
|     "classDocs": "./script/generate-class-docs.sh" |     "classDocs": "./script/generate-class-docs.sh" | ||||||
|   }, |   }, | ||||||
|   "dependencies": { |   "dependencies": { | ||||||
|     "axios": "^0.18.0", |     "axios": "^0.18.0", | ||||||
|     "bcrypt": "^3.0.6", |     "bcrypt": "^5.0.1", | ||||||
|     "body-parser": "~1.18.2", |     "body-parser": "~1.18.2", | ||||||
|  |     "cookie-parser": "^1.4.6", | ||||||
|     "cross-env": "~5.1.4", |     "cross-env": "~5.1.4", | ||||||
|     "express": "~4.16.0", |     "express": "~4.16.0", | ||||||
|     "form-data": "^2.5.1", |     "form-data": "^2.5.1", | ||||||
|     "jsonwebtoken": "^8.2.0", |     "jsonwebtoken": "^8.5.1", | ||||||
|     "km-moviedb": "^0.2.12", |     "km-moviedb": "^0.2.12", | ||||||
|     "node-cache": "^4.1.1", |     "node-cache": "^4.1.1", | ||||||
|     "node-fetch": "^2.6.0", |     "node-fetch": "^2.6.0", | ||||||
|     "python-shell": "^0.5.0", |     "python-shell": "^0.5.0", | ||||||
|     "raven": "^2.4.2", |     "raven": "^2.4.2", | ||||||
|  |     "redis": "^3.0.2", | ||||||
|     "request": "^2.87.0", |     "request": "^2.87.0", | ||||||
|     "request-promise": "^4.2", |     "request-promise": "^4.2", | ||||||
|     "sqlite3": "^4.0.0" |     "sqlite3": "^5.0.1" | ||||||
|   }, |   }, | ||||||
|   "devDependencies": { |   "devDependencies": { | ||||||
|     "@babel/core": "^7.5.5", |     "@babel/core": "^7.5.5", | ||||||
|   | |||||||
							
								
								
									
										44
									
								
								seasoned_api/scripts/updateRequestsInPlex.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								seasoned_api/scripts/updateRequestsInPlex.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,44 @@ | |||||||
|  | const Plex = require("src/plex/plex"); | ||||||
|  | const configuration = require("src/config/configuration").getInstance(); | ||||||
|  | const plex = new Plex(configuration.get("plex", "ip")); | ||||||
|  | const establishedDatabase = require("src/database/database"); | ||||||
|  |  | ||||||
|  | const queries = { | ||||||
|  |   getRequestsNotYetInPlex: `SELECT * FROM requests WHERE status = 'requested' OR status = 'downloading'`, | ||||||
|  |   saveNewStatus: `UPDATE requests SET status = ? WHERE id IS ? and type IS ?` | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | const getByStatus = () => | ||||||
|  |   establishedDatabase.all(queries.getRequestsNotYetInPlex); | ||||||
|  |  | ||||||
|  | const checkIfRequestExistInPlex = async request => { | ||||||
|  |   request.existsInPlex = await plex.existsInPlex(request); | ||||||
|  |   return request; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | const commitNewStatus = (status, id, type, title) => { | ||||||
|  |   console.log(type, title, "updated to:", status); | ||||||
|  |   return establishedDatabase.run(queries.saveNewStatus, [status, id, type]); | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | const getNewRequestMatchesInPlex = async () => { | ||||||
|  |   const requests = await getByStatus(); | ||||||
|  |  | ||||||
|  |   return Promise.all(requests.map(checkIfRequestExistInPlex)) | ||||||
|  |     .catch(error => | ||||||
|  |       console.log("error from checking plex for existance:", error) | ||||||
|  |     ) | ||||||
|  |     .then(matchedRequests => | ||||||
|  |       matchedRequests.filter(request => request.existsInPlex) | ||||||
|  |     ); | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | const updateMatchInDb = (match, status) => { | ||||||
|  |   return commitNewStatus(status, match.id, match.type, match.title); | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | getNewRequestMatchesInPlex() | ||||||
|  |   .then(newMatches => | ||||||
|  |     Promise.all(newMatches.map(match => updateMatchInDb(match, "downloaded"))) | ||||||
|  |   ) | ||||||
|  |   .then(() => process.exit(0)); | ||||||
							
								
								
									
										52
									
								
								seasoned_api/src/cache/redis.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								seasoned_api/src/cache/redis.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,52 @@ | |||||||
|  | const redis = require("redis") | ||||||
|  | const client = redis.createClient() | ||||||
|  |  | ||||||
|  | class Cache { | ||||||
|  |   /** | ||||||
|  |    * Retrieve an unexpired cache entry by key. | ||||||
|  |    * @param {String} key of the cache entry | ||||||
|  |    * @returns {Promise} | ||||||
|  |    */ | ||||||
|  |   get(key) { | ||||||
|  |     return new Promise((resolve, reject) => { | ||||||
|  |       client.get(key, (error, reply) => { | ||||||
|  |         if (reply == null) { | ||||||
|  |           return reject(); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         resolve(JSON.parse(reply)); | ||||||
|  |       }); | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   /** | ||||||
|  |    * Insert cache entry with key and value. | ||||||
|  |    * @param {String} key of the cache entry | ||||||
|  |    * @param {String} value of the cache entry | ||||||
|  |    * @param {Number} timeToLive the number of seconds before entry expires | ||||||
|  |    * @returns {Object} | ||||||
|  |    */ | ||||||
|  |   set(key, value, timeToLive = 10800) { | ||||||
|  |     if (value == null || key == null) return null; | ||||||
|  |  | ||||||
|  |     const json = JSON.stringify(value); | ||||||
|  |     client.set(key, json, (error, reply) => { | ||||||
|  |       if (reply == "OK") { | ||||||
|  |         // successfully set value with key, now set TTL for key | ||||||
|  |         client.expire(key, timeToLive, e => { | ||||||
|  |           if (e) | ||||||
|  |             console.error( | ||||||
|  |               "Unexpected error while setting expiration for key:", | ||||||
|  |               key, | ||||||
|  |               ". Error:", | ||||||
|  |               error | ||||||
|  |             ); | ||||||
|  |         }); | ||||||
|  |       } | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     return value; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | module.exports = Cache; | ||||||
| @@ -22,13 +22,13 @@ class Config { | |||||||
|  |  | ||||||
|    get(section, option) { |    get(section, option) { | ||||||
|       if (this.fields[section] === undefined || this.fields[section][option] === undefined) { |       if (this.fields[section] === undefined || this.fields[section][option] === undefined) { | ||||||
|          throw new Error(`Filed "${section} => ${option}" does not exist.`); |          throw new Error(`Field "${section} => ${option}" does not exist.`); | ||||||
|       } |       } | ||||||
|  |  | ||||||
|       const field = new Field(this.fields[section][option]); |       const field = new Field(this.fields[section][option]); | ||||||
|  |  | ||||||
|       if (field.value === '') { |       if (field.value === '') { | ||||||
|          const envField = process.env[['SEASONED', section.toUpperCase(), option.toUpperCase()].join('_')]; |          const envField = process.env[[section.toUpperCase(), option.toUpperCase()].join('_')]; | ||||||
|          if (envField !== undefined && envField.length !== 0) { return envField; } |          if (envField !== undefined && envField.length !== 0) { return envField; } | ||||||
|       } |       } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -31,7 +31,7 @@ CREATE TABLE IF NOT EXISTS search_history ( | |||||||
| ); | ); | ||||||
|  |  | ||||||
| CREATE TABLE IF NOT EXISTS requests( | CREATE TABLE IF NOT EXISTS requests( | ||||||
|     id TEXT, |     id NUMBER, | ||||||
|     title TEXT, |     title TEXT, | ||||||
|     year NUMBER, |     year NUMBER, | ||||||
|     poster_path TEXT DEFAULT NULL, |     poster_path TEXT DEFAULT NULL, | ||||||
|   | |||||||
							
								
								
									
										35
									
								
								seasoned_api/src/notifications/sms.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								seasoned_api/src/notifications/sms.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,35 @@ | |||||||
|  | const request = require("request"); | ||||||
|  | const configuration = require('src/config/configuration').getInstance(); | ||||||
|  |  | ||||||
|  | const sendSMS = (message) => { | ||||||
|  |   const apiKey = configuration.get('sms', 'apikey') | ||||||
|  |  | ||||||
|  |   if (!apiKey) { | ||||||
|  |     console.warning("api key for sms not set, cannot send sms.") | ||||||
|  |     return null | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   const sender = configuration.get('sms', 'sender') | ||||||
|  |   const recipient = configuration.get('sms', 'recipient') | ||||||
|  |  | ||||||
|  |   return new Promise((resolve, reject) => { | ||||||
|  |   request.post( | ||||||
|  |     { | ||||||
|  |       url: `https://gatewayapi.com/rest/mtsms?token=${apiKey}`, | ||||||
|  |       json: true, | ||||||
|  |       body: { | ||||||
|  |         sender, | ||||||
|  |         message, | ||||||
|  |         recipients: [{ msisdn: `47${recipient}` }] | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     function(err, r, body) { | ||||||
|  |       console.log(err ? err : body); | ||||||
|  |       console.log("sms provider response:", body) | ||||||
|  |       resolve() | ||||||
|  |     } | ||||||
|  |   ); | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | module.exports = { sendSMS } | ||||||
| @@ -1,19 +1,21 @@ | |||||||
| const assert = require('assert'); | 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"); | ||||||
|  |  | ||||||
| const establishedDatabase = require('src/database/database'); | const establishedDatabase = require("src/database/database"); | ||||||
|  |  | ||||||
|  | const RedisCache = require("src/cache/redis"); | ||||||
|  | const cache = new RedisCache(); | ||||||
|  |  | ||||||
| function getMagnetFromURL(url) { | function getMagnetFromURL(url) { | ||||||
|   return new Promise((resolve, reject) => { |   return new Promise((resolve, reject) => { | ||||||
|     const options = new URL(url); |     const options = new URL(url); | ||||||
|       if (options.protocol.includes('magnet')) |     if (options.protocol.includes("magnet")) resolve(url); | ||||||
|          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); | ||||||
|       } |       } | ||||||
|     }); |     }); | ||||||
|   }); |   }); | ||||||
| @@ -21,64 +23,82 @@ function getMagnetFromURL(url) { | |||||||
|  |  | ||||||
| async function find(searchterm, callback) { | async function find(searchterm, callback) { | ||||||
|   const options = { |   const options = { | ||||||
|       pythonPath: '../torrent_search/env/bin/python3', |     pythonPath: "../torrent_search/env/bin/python3", | ||||||
|       scriptPath: '../torrent_search', |     scriptPath: "../torrent_search", | ||||||
|       args: [searchterm, '-s', 'jackett', '-f', '--print'] |     args: [searchterm, "-s", "jackett", "--print"] | ||||||
|    } |   }; | ||||||
|  |  | ||||||
|    PythonShell.run('torrentSearch/search.py', options, callback); |   PythonShell.run("torrentSearch/search.py", options, callback); | ||||||
|   // PythonShell does not support return |   // PythonShell does not support return | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
| async function callPythonAddMagnet(url, callback) { | async function callPythonAddMagnet(url, callback) { | ||||||
|   getMagnetFromURL(url) |   getMagnetFromURL(url) | ||||||
|    .then((magnet) => { |     .then(magnet => { | ||||||
|       const options = { |       const options = { | ||||||
|         pythonPath: '../delugeClient/env/bin/python3', |         pythonPath: "../delugeClient/env/bin/python3", | ||||||
|         scriptPath: '../delugeClient', |         scriptPath: "../delugeClient", | ||||||
|         args: ['add', magnet] |         args: ["add", magnet] | ||||||
|       }; |       }; | ||||||
|  |  | ||||||
|       PythonShell.run('deluge_cli.py', options, callback); |       PythonShell.run("deluge_cli.py", options, callback); | ||||||
|     }) |     }) | ||||||
|    .catch((err) => { |     .catch(err => { | ||||||
|       console.log(err); |       console.log(err); | ||||||
|       throw new Error(err); |       throw new Error(err); | ||||||
|    }) |     }); | ||||||
| } | } | ||||||
|  |  | ||||||
| async function SearchPiratebay(query) { | async function SearchPiratebay(query) { | ||||||
|    return await new Promise((resolve, reject) => find(query, (err, results) => { |   if (query && query.includes("+")) { | ||||||
|  |     query = query.replace("+", "%20"); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   const cacheKey = `pirate/${query}`; | ||||||
|  |  | ||||||
|  |   return new Promise((resolve, reject) => | ||||||
|  |     cache | ||||||
|  |       .get(cacheKey) | ||||||
|  |       .then(resolve) | ||||||
|  |       .catch(() => | ||||||
|  |         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); | ||||||
|          reject(Error('There was a error when searching for torrents')); |             reject(Error("There was a error when searching for torrents")); | ||||||
|           } |           } | ||||||
|  |  | ||||||
|           if (results) { |           if (results) { | ||||||
|          resolve(JSON.parse(results, null, '\t')); |             const jsonData = JSON.parse(results[1], null, "\t"); | ||||||
|  |             cache.set(cacheKey, jsonData); | ||||||
|  |             resolve(jsonData); | ||||||
|           } |           } | ||||||
|    })); |         }) | ||||||
|  |       ) | ||||||
|  |   ); | ||||||
| } | } | ||||||
|  |  | ||||||
| async function AddMagnet(magnet, name, tmdb_id) { | async function AddMagnet(magnet, name, tmdb_id) { | ||||||
|    return await new Promise((resolve, reject) => callPythonAddMagnet(magnet, (err, results) => { |   return await new Promise((resolve, reject) => | ||||||
|  |     callPythonAddMagnet(magnet, (err, results) => { | ||||||
|       if (err) { |       if (err) { | ||||||
|         /* eslint-disable no-console */ |         /* eslint-disable no-console */ | ||||||
|         console.log(err); |         console.log(err); | ||||||
|          reject(Error('Enable to add torrent', err))  |         reject(Error("Enable to add torrent", err)); | ||||||
|       } |       } | ||||||
|       /* eslint-disable no-console */ |       /* eslint-disable no-console */ | ||||||
|       console.log('result/error:', err, results); |       console.log("result/error:", err, results); | ||||||
|  |  | ||||||
|       database = establishedDatabase; |       database = establishedDatabase; | ||||||
|       insert_query = "INSERT INTO requested_torrent(magnet,torrent_name,tmdb_id) \ |       insert_query = | ||||||
|  |         "INSERT INTO requested_torrent(magnet,torrent_name,tmdb_id) \ | ||||||
|          VALUES (?,?,?)"; |          VALUES (?,?,?)"; | ||||||
|  |  | ||||||
|       let response = database.run(insert_query, [magnet, name, tmdb_id]); |       let response = database.run(insert_query, [magnet, name, tmdb_id]); | ||||||
|       console.log('Response from requsted_torrent insert: ' + response); |       console.log("Response from requsted_torrent insert: " + response); | ||||||
|  |  | ||||||
|       resolve({ success: true }); |       resolve({ success: true }); | ||||||
|    })); |     }) | ||||||
|  |   ); | ||||||
| } | } | ||||||
|  |  | ||||||
| module.exports = { SearchPiratebay, AddMagnet }; | module.exports = { SearchPiratebay, AddMagnet }; | ||||||
|   | |||||||
| @@ -1,89 +1,240 @@ | |||||||
| const fetch = require('node-fetch') | const fetch = require("node-fetch"); | ||||||
| const convertPlexToMovie = require('src/plex/convertPlexToMovie') | const convertPlexToMovie = require("src/plex/convertPlexToMovie"); | ||||||
| const convertPlexToShow = require('src/plex/convertPlexToShow') | const convertPlexToShow = require("src/plex/convertPlexToShow"); | ||||||
| const convertPlexToEpisode = require('src/plex/convertPlexToEpisode') | const convertPlexToEpisode = require("src/plex/convertPlexToEpisode"); | ||||||
|  |  | ||||||
|  | const { Movie, Show, Person } = require("src/tmdb/types"); | ||||||
|  |  | ||||||
| const { Movie, Show, Person } = require('src/tmdb/types'); | const RedisCache = require("src/cache/redis"); | ||||||
|  | const redisCache = new RedisCache(); | ||||||
|  |  | ||||||
| // const { Movie, }  | const sanitize = string => string.toLowerCase().replace(/[^\w]/gi, ""); | ||||||
| // TODO? import class definitions to compare types ? |  | ||||||
| // what would typescript do? | function fixedEncodeURIComponent(str) { | ||||||
|  |   return encodeURIComponent(str).replace(/[!'()*]/g, function (c) { | ||||||
|  |     return "%" + c.charCodeAt(0).toString(16).toUpperCase(); | ||||||
|  |   }); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | const matchingTitleAndYear = (plex, tmdb) => { | ||||||
|  |   let matchingTitle, matchingYear; | ||||||
|  |  | ||||||
|  |   if (plex["title"] != null && tmdb["title"] != null) { | ||||||
|  |     const plexTitle = sanitize(plex.title); | ||||||
|  |     const tmdbTitle = sanitize(tmdb.title); | ||||||
|  |     matchingTitle = plexTitle == tmdbTitle; | ||||||
|  |     matchingTitle = matchingTitle | ||||||
|  |       ? matchingTitle | ||||||
|  |       : plexTitle.startsWith(tmdbTitle); | ||||||
|  |   } else matchingTitle = false; | ||||||
|  |  | ||||||
|  |   if (plex["year"] != null && tmdb["year"] != null) | ||||||
|  |     matchingYear = plex.year == tmdb.year; | ||||||
|  |   else matchingYear = false; | ||||||
|  |  | ||||||
|  |   return matchingTitle && matchingYear; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | const successfullResponse = response => { | ||||||
|  |   if (response && response["MediaContainer"]) return response; | ||||||
|  |  | ||||||
|  |   if ( | ||||||
|  |     response == null || | ||||||
|  |     response["status"] == null || | ||||||
|  |     response["statusText"] == null | ||||||
|  |   ) { | ||||||
|  |     throw Error("Unable to decode response"); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   const { status, statusText } = response; | ||||||
|  |  | ||||||
|  |   if (status === 200) { | ||||||
|  |     return response.json(); | ||||||
|  |   } else { | ||||||
|  |     throw { message: statusText, status: status }; | ||||||
|  |   } | ||||||
|  | }; | ||||||
|  |  | ||||||
| class Plex { | class Plex { | ||||||
|   constructor(ip, port=32400) { |   constructor(ip, port = 32400, cache = null) { | ||||||
|     this.plexIP = ip |     this.plexIP = ip; | ||||||
|     this.plexPort = port |     this.plexPort = port; | ||||||
|  |  | ||||||
|  |     this.cache = cache || redisCache; | ||||||
|  |     this.cacheTags = { | ||||||
|  |       machineInfo: "plex/mi", | ||||||
|  |       search: "plex/s" | ||||||
|  |     }; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   fetchMachineIdentifier() { | ||||||
|  |     const cacheKey = `${this.cacheTags.machineInfo}`; | ||||||
|  |     const url = `http://${this.plexIP}:${this.plexPort}/`; | ||||||
|  |     const options = { | ||||||
|  |       timeout: 20000, | ||||||
|  |       headers: { Accept: "application/json" } | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     return new Promise((resolve, reject) => | ||||||
|  |       this.cache | ||||||
|  |         .get(cacheKey) | ||||||
|  |         .then(machineInfo => resolve(machineInfo["machineIdentifier"])) | ||||||
|  |         .catch(() => fetch(url, options)) | ||||||
|  |         .then(response => response.json()) | ||||||
|  |         .then(machineInfo => | ||||||
|  |           this.cache.set(cacheKey, machineInfo["MediaContainer"], 2628000) | ||||||
|  |         ) | ||||||
|  |         .then(machineInfo => resolve(machineInfo["machineIdentifier"])) | ||||||
|  |         .catch(error => { | ||||||
|  |           if (error != undefined && error.type === "request-timeout") { | ||||||
|  |             reject({ | ||||||
|  |               message: "Plex did not respond", | ||||||
|  |               status: 408, | ||||||
|  |               success: false | ||||||
|  |             }); | ||||||
|  |           } | ||||||
|  |  | ||||||
|  |           reject(error); | ||||||
|  |         }) | ||||||
|  |     ); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   matchTmdbAndPlexMedia(plex, tmdb) { |   matchTmdbAndPlexMedia(plex, tmdb) { | ||||||
|     if (plex === undefined || tmdb === undefined) |     let match; | ||||||
|       return false |  | ||||||
|  |  | ||||||
|     const sanitize = (string) => string.toLowerCase() |     if (plex == null || tmdb == null) return false; | ||||||
|  |  | ||||||
|     const matchTitle = sanitize(plex.title) === sanitize(tmdb.title) |     if (plex instanceof Array) { | ||||||
|     const matchYear = plex.year === tmdb.year |       let possibleMatches = plex.map(plexItem => | ||||||
|  |         matchingTitleAndYear(plexItem, tmdb) | ||||||
|     return matchTitle && matchYear |       ); | ||||||
|   } |       match = possibleMatches.includes(true); | ||||||
|  |  | ||||||
|   existsInPlex(tmdbMovie) { |  | ||||||
|     return this.search(tmdbMovie.title) |  | ||||||
|       .then(plexMovies => plexMovies.some(plex => this.matchTmdbAndPlexMedia(plex, tmdbMovie))) |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   successfullResponse(response) { |  | ||||||
|     const { status, statusText } = response |  | ||||||
|  |  | ||||||
|     if (status === 200) { |  | ||||||
|       return response.json() |  | ||||||
|     } else { |     } else { | ||||||
|       throw { message: statusText, status: status } |       match = matchingTitleAndYear(plex, tmdb); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     return match; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   async existsInPlex(tmdb) { | ||||||
|  |     const plexMatch = await this.findPlexItemByTitleAndYear( | ||||||
|  |       tmdb.title, | ||||||
|  |       tmdb.year | ||||||
|  |     ); | ||||||
|  |     return plexMatch ? true : false; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   findPlexItemByTitleAndYear(title, year) { | ||||||
|  |     const query = { title, year }; | ||||||
|  |  | ||||||
|  |     return this.search(title).then(plexResults => { | ||||||
|  |       const matchesInPlex = plexResults.map(plex => | ||||||
|  |         this.matchTmdbAndPlexMedia(plex, query) | ||||||
|  |       ); | ||||||
|  |       const matchesIndex = matchesInPlex.findIndex(el => el === true); | ||||||
|  |       return matchesInPlex != -1 ? plexResults[matchesIndex] : null; | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   getDirectLinkByTitleAndYear(title, year) { | ||||||
|  |     const machineIdentifierPromise = this.fetchMachineIdentifier(); | ||||||
|  |     const matchingObjectInPlexPromise = this.findPlexItemByTitleAndYear( | ||||||
|  |       title, | ||||||
|  |       year | ||||||
|  |     ); | ||||||
|  |  | ||||||
|  |     return Promise.all([ | ||||||
|  |       machineIdentifierPromise, | ||||||
|  |       matchingObjectInPlexPromise | ||||||
|  |     ]).then(([machineIdentifier, matchingObjectInPlex]) => { | ||||||
|  |       if ( | ||||||
|  |         matchingObjectInPlex == false || | ||||||
|  |         matchingObjectInPlex == null || | ||||||
|  |         matchingObjectInPlex["key"] == null || | ||||||
|  |         machineIdentifier == null | ||||||
|  |       ) | ||||||
|  |         return false; | ||||||
|  |  | ||||||
|  |       const keyUriComponent = fixedEncodeURIComponent(matchingObjectInPlex.key); | ||||||
|  |       return `https://app.plex.tv/desktop#!/server/${machineIdentifier}/details?key=${keyUriComponent}`; | ||||||
|  |     }); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   search(query) { |   search(query) { | ||||||
|     const url = `http://${this.plexIP}:${this.plexPort}/hubs/search?query=${query}` |     const cacheKey = `${this.cacheTags.search}:${query}`; | ||||||
|  |  | ||||||
|  |     const url = `http://${this.plexIP}:${ | ||||||
|  |       this.plexPort | ||||||
|  |     }/hubs/search?query=${fixedEncodeURIComponent(query)}`; | ||||||
|     const options = { |     const options = { | ||||||
|       timeout: 2000, |       timeout: 20000, | ||||||
|       headers: { 'Accept': 'application/json' } |       headers: { Accept: "application/json" } | ||||||
|     } |     }; | ||||||
|  |  | ||||||
|     return fetch(url, options) |     return new Promise((resolve, reject) => | ||||||
|       .then(this.successfullResponse) |       this.cache | ||||||
|  |         .get(cacheKey) | ||||||
|  |         .catch(() => fetch(url, options)) // else fetch fresh data | ||||||
|  |         .then(successfullResponse) | ||||||
|  |         .then(results => this.cache.set(cacheKey, results, 21600)) // 6 hours | ||||||
|         .then(this.mapResults) |         .then(this.mapResults) | ||||||
|  |         .then(resolve) | ||||||
|         .catch(error => { |         .catch(error => { | ||||||
|         if (error.type === 'request-timeout') { |           if (error != undefined && error.type === "request-timeout") { | ||||||
|           throw { message: 'Plex did not respond', status: 408, success: false } |             reject({ | ||||||
|  |               message: "Plex did not respond", | ||||||
|  |               status: 408, | ||||||
|  |               success: false | ||||||
|  |             }); | ||||||
|           } |           } | ||||||
|  |  | ||||||
|         throw error |           reject(error); | ||||||
|         }) |         }) | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   // this is not guarenteed to work, but if we see a movie or | ||||||
|  |   // show has been imported, this function can be helpfull to call | ||||||
|  |   // in order to try bust the cache preventing movieInfo and | ||||||
|  |   // showInfo from seeing updates through existsInPlex. | ||||||
|  |   bustSearchCacheWithTitle(title) { | ||||||
|  |     const query = title; | ||||||
|  |     const cacheKey = `${this.cacheTags.search}/${query}*`; | ||||||
|  |  | ||||||
|  |     this.cache.del( | ||||||
|  |       cacheKey, | ||||||
|  |       (error, | ||||||
|  |       response => { | ||||||
|  |         if (response == 1) return true; | ||||||
|  |  | ||||||
|  |         // TODO improve cache key matching by lowercasing it on the backend. | ||||||
|  |         // what do we actually need to check for if the key was deleted or not | ||||||
|  |         // it might be an error or another response code. | ||||||
|  |         console.log("Unable to delete, key might not exists"); | ||||||
|  |       }) | ||||||
|  |     ); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   mapResults(response) { |   mapResults(response) { | ||||||
|     if (response === undefined || response.MediaContainer === undefined) { |     if ( | ||||||
|       console.log('response was not valid to map', response) |       response == null || | ||||||
|       return [] |       response.MediaContainer == null || | ||||||
|  |       response.MediaContainer.Hub == null | ||||||
|  |     ) { | ||||||
|  |       return []; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     return response.MediaContainer.Hub |     return response.MediaContainer.Hub.filter(category => category.size > 0) | ||||||
|       .filter(category => category.size > 0) |  | ||||||
|       .map(category => { |       .map(category => { | ||||||
|         if (category.type === 'movie') { |         if (category.type === "movie") { | ||||||
|           return category.Metadata.map(movie => { |           return category.Metadata; | ||||||
|             const ovie = Movie.convertFromPlexResponse(movie) |         } else if (category.type === "show") { | ||||||
|             return ovie.createJsonResponse() |           return category.Metadata.map(convertPlexToShow); | ||||||
|           }) |         } else if (category.type === "episode") { | ||||||
|         } else if (category.type === 'show') { |           return category.Metadata.map(convertPlexToEpisode); | ||||||
|           return category.Metadata.map(convertPlexToShow) |  | ||||||
|         } else if (category.type === 'episode') { |  | ||||||
|           return category.Metadata.map(convertPlexToEpisode) |  | ||||||
|         } |         } | ||||||
|       }) |       }) | ||||||
|       .filter(result => result !== undefined) |       .filter(result => result !== undefined); | ||||||
|       .flat() |  | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,12 +1,10 @@ | |||||||
| const PlexRepository = require('src/plex/plexRepository'); | const PlexRepository = require("src/plex/plexRepository"); | ||||||
| const Cache = require('src/tmdb/cache'); | const configuration = require("src/config/configuration").getInstance(); | ||||||
| const configuration = require('src/config/configuration').getInstance(); | const TMDB = require("src/tmdb/tmdb"); | ||||||
| const TMDB = require('src/tmdb/tmdb'); | const establishedDatabase = require("src/database/database"); | ||||||
| const establishedDatabase = require('src/database/database'); |  | ||||||
|  |  | ||||||
| const plexRepository = new PlexRepository(configuration.get('plex', 'ip')); | const plexRepository = new PlexRepository(configuration.get("plex", "ip")); | ||||||
| const cache = new Cache(); | const tmdb = new TMDB(configuration.get("tmdb", "apiKey")); | ||||||
| const tmdb = new TMDB(cache, configuration.get('tmdb', 'apiKey')); |  | ||||||
|  |  | ||||||
| class RequestRepository { | class RequestRepository { | ||||||
|   constructor(database) { |   constructor(database) { | ||||||
| @@ -14,15 +12,19 @@ class RequestRepository { | |||||||
|     this.queries = { |     this.queries = { | ||||||
|       insertRequest: `INSERT INTO requests(id,title,year,poster_path,background_path,requested_by,ip,user_agent,type) |       insertRequest: `INSERT INTO requests(id,title,year,poster_path,background_path,requested_by,ip,user_agent,type) | ||||||
|           VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, |           VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, | ||||||
|          fetchRequestedItems: 'SELECT * FROM requests ORDER BY date DESC LIMIT 25 OFFSET ?*25-25', |       fetchRequestedItems: | ||||||
|          fetchRequestedItemsByStatus: 'SELECT * FROM requests WHERE status IS ? AND type LIKE ? ORDER BY date DESC LIMIT 25 OFFSET ?*25-25', |         "SELECT * FROM requests ORDER BY date DESC LIMIT 25 OFFSET ?*25-25", | ||||||
|          updateRequestedById: 'UPDATE requests SET status = ? WHERE id is ? AND type is ?', |       fetchRequestedItemsByStatus: | ||||||
|          checkIfIdRequested: 'SELECT * FROM requests WHERE id IS ? AND type IS ?', |         "SELECT * FROM requests WHERE status IS ? AND type LIKE ? ORDER BY date DESC LIMIT 25 OFFSET ?*25-25", | ||||||
|          userRequests: 'SELECT * FROM requests WHERE requested_by IS ? ORDER BY date DESC', |       updateRequestedById: | ||||||
|  |         "UPDATE requests SET status = ? WHERE id is ? AND type is ?", | ||||||
|  |       checkIfIdRequested: "SELECT * FROM requests WHERE id IS ? AND type IS ?", | ||||||
|  |       userRequests: | ||||||
|  |         "SELECT * FROM requests WHERE requested_by IS ? ORDER BY date DESC" | ||||||
|     }; |     }; | ||||||
|     this.cacheTags = { |     this.cacheTags = { | ||||||
|          search: 'se', |       search: "se", | ||||||
|          lookup: 'i', |       lookup: "i" | ||||||
|     }; |     }; | ||||||
|   } |   } | ||||||
|  |  | ||||||
| @@ -32,21 +34,28 @@ class RequestRepository { | |||||||
|       .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 Promise.resolve() | ||||||
|       .then(() => tmdb.lookup(identifier, type)) |       .then(() => tmdb.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 => { | ||||||
|         throw new Error(error); |         throw new Error(error); | ||||||
|       }); |       }); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   checkID(tmdbMovie) { |   checkID(tmdbMovie) { | ||||||
|     return Promise.resolve() |     return Promise.resolve() | ||||||
|          .then(() => this.database.get(this.queries.checkIfIdRequested, [tmdbMovie.id, tmdbMovie.type])) |       .then(() => | ||||||
|  |         this.database.get(this.queries.checkIfIdRequested, [ | ||||||
|  |           tmdbMovie.id, | ||||||
|  |           tmdbMovie.type | ||||||
|  |         ]) | ||||||
|  |       ) | ||||||
|       .then((result, error) => { |       .then((result, error) => { | ||||||
|             if (error) { throw new Error(error); } |         if (error) { | ||||||
|  |           throw new Error(error); | ||||||
|  |         } | ||||||
|         tmdbMovie.requested = result ? true : false; |         tmdbMovie.requested = result ? true : false; | ||||||
|         return tmdbMovie; |         return tmdbMovie; | ||||||
|       }); |       }); | ||||||
| @@ -60,41 +69,61 @@ class RequestRepository { | |||||||
|   sendRequest(identifier, type, ip, user_agent, user) { |   sendRequest(identifier, type, ip, user_agent, user) { | ||||||
|     return Promise.resolve() |     return Promise.resolve() | ||||||
|       .then(() => tmdb.lookup(identifier, type)) |       .then(() => tmdb.lookup(identifier, type)) | ||||||
|       .then((movie) => { |       .then(movie => { | ||||||
|         const username = user === undefined ? undefined : user.username; |         const username = user === undefined ? undefined : user.username; | ||||||
|         // Add request to database |         // Add request to database | ||||||
|          return this.database.run(this.queries.insertRequest, [movie.id, movie.title, movie.year, movie.poster_path, movie.background_path, username, ip, user_agent, movie.type]); |         return this.database.run(this.queries.insertRequest, [ | ||||||
|  |           movie.id, | ||||||
|  |           movie.title, | ||||||
|  |           movie.year, | ||||||
|  |           movie.poster_path, | ||||||
|  |           movie.background_path, | ||||||
|  |           username, | ||||||
|  |           ip, | ||||||
|  |           user_agent, | ||||||
|  |           movie.type | ||||||
|  |         ]); | ||||||
|       }); |       }); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|    fetchRequested(status, page = '1', type = '%') { |   fetchRequested(status, page = "1", type = "%") { | ||||||
|    	return Promise.resolve() |     return Promise.resolve().then(() => { | ||||||
|    	.then(() => { |       if ( | ||||||
| 	      if (status === 'requested' || status === 'downloading' || status === 'downloaded') |         status === "requested" || | ||||||
| 	         return this.database.all(this.queries.fetchRequestedItemsByStatus, [status, type, page]); |         status === "downloading" || | ||||||
| 	      else |         status === "downloaded" | ||||||
| 	         return this.database.all(this.queries.fetchRequestedItems, page); |       ) | ||||||
|    	}) |         return this.database.all(this.queries.fetchRequestedItemsByStatus, [ | ||||||
|  |           status, | ||||||
|  |           type, | ||||||
|  |           page | ||||||
|  |         ]); | ||||||
|  |       else return this.database.all(this.queries.fetchRequestedItems, page); | ||||||
|  |     }); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|    userRequests(user) { |   userRequests(username) { | ||||||
|     return Promise.resolve() |     return Promise.resolve() | ||||||
|          .then(() => this.database.all(this.queries.userRequests, user.username)) |       .then(() => this.database.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"); | ||||||
|         } |         } | ||||||
|             throw new Error('Unable to fetch your requests'); |         throw new Error("Unable to fetch your requests"); | ||||||
|       }) |       }) | ||||||
|          .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) |         result.map(item => (item.poster = item.poster_path)); | ||||||
|             return result |         return result; | ||||||
|       }); |       }); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   updateRequestedById(id, type, status) { |   updateRequestedById(id, type, status) { | ||||||
|       return this.database.run(this.queries.updateRequestedById, [status, id, type]); |     return this.database.run(this.queries.updateRequestedById, [ | ||||||
|  |       status, | ||||||
|  |       id, | ||||||
|  |       type | ||||||
|  |     ]); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,39 +0,0 @@ | |||||||
| const Plex = require('src/plex/plex') |  | ||||||
| const configuration = require('src/config/configuration').getInstance(); |  | ||||||
| const plex = new Plex(configuration.get('plex', 'ip')) |  | ||||||
| const establishedDatabase = require('src/database/database');  |  | ||||||
|  |  | ||||||
| class UpdateRequestsInPlex { |  | ||||||
|   constructor() { |  | ||||||
|      this.database = establishedDatabase; |  | ||||||
|      this.queries = { |  | ||||||
|         getMovies: `SELECT * FROM requests WHERE status = 'requested' OR status = 'downloading'`, |  | ||||||
| //         getMovies: "select * from requests where status is 'reset'", |  | ||||||
|         saveNewStatus: `UPDATE requests SET status = ? WHERE id IS ? and type IS ?`, |  | ||||||
|      } |  | ||||||
|   } |  | ||||||
|   getByStatus() { |  | ||||||
|      return this.database.all(this.queries.getMovies); |  | ||||||
|   } |  | ||||||
|   scrub() { |  | ||||||
|      return this.getByStatus() |  | ||||||
|         .then((requests) => Promise.all(requests.map(movie => plex.existsInPlex(movie)))) |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   commitNewStatus(status, id, type, title) { |  | ||||||
|     console.log(type, title, 'updated to:', status) |  | ||||||
|     this.database.run(this.queries.saveNewStatus, [status, id, type]) |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|     |  | ||||||
|   updateStatus(status) { |  | ||||||
|     this.getByStatus() |  | ||||||
|       .then(requests => Promise.all(requests.map(request => plex.existsInPlex(request)))) |  | ||||||
|       .then(matchedRequests => matchedRequests.filter(request => request.existsInPlex)) |  | ||||||
|       .then(newMatches => newMatches.map(match => this.commitNewStatus(status, match.id, match.type, match.title))) |  | ||||||
|   } |  | ||||||
| } |  | ||||||
| var requestsUpdater = new UpdateRequestsInPlex(); |  | ||||||
| requestsUpdater.updateStatus('downloaded') |  | ||||||
|  |  | ||||||
| module.exports = UpdateRequestsInPlex |  | ||||||
| @@ -1,9 +1,7 @@ | |||||||
| const assert = require('assert') | const assert = require('assert') | ||||||
| const configuration = require('src/config/configuration').getInstance(); | const configuration = require('src/config/configuration').getInstance(); | ||||||
| const Cache = require('src/tmdb/cache'); |  | ||||||
| const TMDB = require('src/tmdb/tmdb'); | const TMDB = require('src/tmdb/tmdb'); | ||||||
| const cache = new Cache(); | const tmdb = new TMDB(configuration.get('tmdb', 'apiKey')); | ||||||
| const tmdb = new TMDB(cache, configuration.get('tmdb', 'apiKey')); |  | ||||||
| const establishedDatabase = require('src/database/database'); | const establishedDatabase = require('src/database/database'); | ||||||
| const utils = require('./utils'); | const utils = require('./utils'); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,4 +1,4 @@ | |||||||
| const fetch = require('node-fetch'); | const fetch = require("node-fetch"); | ||||||
|  |  | ||||||
| class Tautulli { | class Tautulli { | ||||||
|   constructor(apiKey, ip, port) { |   constructor(apiKey, ip, port) { | ||||||
| @@ -8,50 +8,66 @@ class Tautulli { | |||||||
|   } |   } | ||||||
|  |  | ||||||
|   buildUrlWithCmdAndUserid(cmd, user_id) { |   buildUrlWithCmdAndUserid(cmd, user_id) { | ||||||
|     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", user_id); | ||||||
|  |  | ||||||
|     return url |     return url; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   logTautulliError(error) { | ||||||
|  |     console.error("error fetching from tautulli"); | ||||||
|  |  | ||||||
|  |     throw error; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   getPlaysByDayOfWeek(plex_userid, days, y_axis) { |   getPlaysByDayOfWeek(plex_userid, days, y_axis) { | ||||||
|     const url = this.buildUrlWithCmdAndUserid('get_plays_by_dayofweek', plex_userid) |     const url = this.buildUrlWithCmdAndUserid( | ||||||
|     url.searchParams.append('time_range', days) |       "get_plays_by_dayofweek", | ||||||
|     url.searchParams.append('y_axis', y_axis) |       plex_userid | ||||||
|  |     ); | ||||||
|  |     url.searchParams.append("time_range", days); | ||||||
|  |     url.searchParams.append("y_axis", y_axis); | ||||||
|  |  | ||||||
|     return fetch(url.href) |     return fetch(url.href) | ||||||
|       .then(resp => resp.json()) |       .then(resp => resp.json()) | ||||||
|  |       .catch(error => this.logTautulliError(error)); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   getPlaysByDays(plex_userid, days, y_axis) { |   getPlaysByDays(plex_userid, days, y_axis) { | ||||||
|     const url = this.buildUrlWithCmdAndUserid('get_plays_by_date', plex_userid) |     const url = this.buildUrlWithCmdAndUserid("get_plays_by_date", plex_userid); | ||||||
|     url.searchParams.append('time_range', days) |     url.searchParams.append("time_range", days); | ||||||
|     url.searchParams.append('y_axis', y_axis) |     url.searchParams.append("y_axis", y_axis); | ||||||
|  |  | ||||||
|     return fetch(url.href) |     return fetch(url.href) | ||||||
|       .then(resp => resp.json()) |       .then(resp => resp.json()) | ||||||
|  |       .catch(error => this.logTautulliError(error)); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   watchTimeStats(plex_userid) { |   watchTimeStats(plex_userid) { | ||||||
|     const url = this.buildUrlWithCmdAndUserid('get_user_watch_time_stats', plex_userid) |     const url = this.buildUrlWithCmdAndUserid( | ||||||
|     url.searchParams.append('grouping', 0) |       "get_user_watch_time_stats", | ||||||
|  |       plex_userid | ||||||
|  |     ); | ||||||
|  |     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)); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   viewHistory(plex_userid) { |   viewHistory(plex_userid) { | ||||||
|     const url = this.buildUrlWithCmdAndUserid('get_history', plex_userid) |     const url = this.buildUrlWithCmdAndUserid("get_history", plex_userid); | ||||||
|  |  | ||||||
|     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) |     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)); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,31 +1,74 @@ | |||||||
| const moviedb = require('km-moviedb'); | const moviedb = require("km-moviedb"); | ||||||
|  | const RedisCache = require("src/cache/redis"); | ||||||
|  | const redisCache = new RedisCache(); | ||||||
|  |  | ||||||
| const { Movie, Show, Person, Credits, ReleaseDates } = require('src/tmdb/types'); | const { | ||||||
| // const { tmdbInfo } = require('src/tmdb/types') |   Movie, | ||||||
|  |   Show, | ||||||
|  |   Person, | ||||||
|  |   Credits, | ||||||
|  |   ReleaseDates | ||||||
|  | } = require("src/tmdb/types"); | ||||||
|  |  | ||||||
|  | const tmdbErrorResponse = (error, typeString = undefined) => { | ||||||
|  |   if (error.status === 404) { | ||||||
|  |     let message = error.response.body.status_message; | ||||||
|  |  | ||||||
|  |     throw { | ||||||
|  |       status: 404, | ||||||
|  |       message: message.slice(0, -1) + " in tmdb." | ||||||
|  |     }; | ||||||
|  |   } else if (error.status === 401) { | ||||||
|  |     throw { | ||||||
|  |       status: 401, | ||||||
|  |       message: error.response.body.status_message | ||||||
|  |     }; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   throw { | ||||||
|  |     status: 500, | ||||||
|  |     message: `An unexpected error occured while fetching ${typeString} from tmdb` | ||||||
|  |   }; | ||||||
|  | }; | ||||||
|  |  | ||||||
| class TMDB { | class TMDB { | ||||||
|   constructor(cache, apiKey, tmdbLibrary) { |   constructor(apiKey, cache, tmdbLibrary) { | ||||||
|     this.cache = cache; |  | ||||||
|     this.tmdbLibrary = tmdbLibrary || moviedb(apiKey); |     this.tmdbLibrary = tmdbLibrary || moviedb(apiKey); | ||||||
|  |  | ||||||
|  |     this.cache = cache || redisCache; | ||||||
|     this.cacheTags = { |     this.cacheTags = { | ||||||
|       multiSearch: 'mus',  |       multiSearch: "mus", | ||||||
|       movieSearch: 'mos',  |       movieSearch: "mos", | ||||||
|       showSearch: 'ss', |       showSearch: "ss", | ||||||
|       personSearch: 'ps', |       personSearch: "ps", | ||||||
|       movieInfo: 'mi', |       movieInfo: "mi", | ||||||
|       movieCredits: 'mc', |       movieCredits: "mc", | ||||||
|       movieReleaseDates: 'mrd', |       movieReleaseDates: "mrd", | ||||||
|       showInfo: 'si', |       movieImages: "mimg", | ||||||
|       showCredits: 'sc', |       showInfo: "si", | ||||||
|       personInfo: 'pi', |       showCredits: "sc", | ||||||
|       miscNowPlayingMovies: 'npm', |       personInfo: "pi", | ||||||
|       miscPopularMovies: 'pm', |       personCredits: "pc", | ||||||
|       miscTopRatedMovies: 'tpm', |       miscNowPlayingMovies: "npm", | ||||||
|       miscUpcomingMovies: 'um', |       miscPopularMovies: "pm", | ||||||
|       tvOnTheAir: 'toa', |       miscTopRatedMovies: "tpm", | ||||||
|       miscPopularTvs: 'pt', |       miscUpcomingMovies: "um", | ||||||
|       miscTopRatedTvs: 'trt', |       tvOnTheAir: "toa", | ||||||
|  |       miscPopularTvs: "pt", | ||||||
|  |       miscTopRatedTvs: "trt" | ||||||
|     }; |     }; | ||||||
|  |     this.defaultTTL = 86400; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   getFromCacheOrFetchFromTmdb(cacheKey, tmdbMethod, query) { | ||||||
|  |     return new Promise((resolve, reject) => | ||||||
|  |       this.cache | ||||||
|  |         .get(cacheKey) | ||||||
|  |         .then(resolve) | ||||||
|  |         .catch(() => this.tmdb(tmdbMethod, query)) | ||||||
|  |         .then(resolve) | ||||||
|  |         .catch(error => reject(tmdbErrorResponse(error, tmdbMethod))) | ||||||
|  |     ); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   /** |   /** | ||||||
| @@ -37,13 +80,11 @@ class TMDB { | |||||||
|    */ |    */ | ||||||
|   movieInfo(identifier) { |   movieInfo(identifier) { | ||||||
|     const query = { id: identifier }; |     const query = { id: identifier }; | ||||||
|     const cacheKey = `${this.cacheTags.movieInfo}:${identifier}`; |     const cacheKey = `tmdb/${this.cacheTags.movieInfo}:${identifier}`; | ||||||
|  |  | ||||||
|     return this.cache.get(cacheKey) |     return this.getFromCacheOrFetchFromTmdb(cacheKey, "movieInfo", query) | ||||||
|       .catch(() => this.tmdb('movieInfo', query)) |       .then(movie => this.cache.set(cacheKey, movie, this.defaultTTL)) | ||||||
|       .catch(tmdbError => tmdbErrorResponse(tmdbError, 'movie info')) |       .then(movie => Movie.convertFromTmdbResponse(movie)); | ||||||
|       .then(movie => this.cache.set(cacheKey, movie, 1)) |  | ||||||
|       .then(movie => Movie.convertFromTmdbResponse(movie)) |  | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   /** |   /** | ||||||
| @@ -52,14 +93,12 @@ class TMDB { | |||||||
|    * @returns {Promise} movie cast object |    * @returns {Promise} movie cast object | ||||||
|    */ |    */ | ||||||
|   movieCredits(identifier) { |   movieCredits(identifier) { | ||||||
|     const query = { id: identifier } |     const query = { id: identifier }; | ||||||
|     const cacheKey = `${this.cacheTags.movieCredits}:${identifier}` |     const cacheKey = `tmdb/${this.cacheTags.movieCredits}:${identifier}`; | ||||||
|  |  | ||||||
|     return this.cache.get(cacheKey) |     return this.getFromCacheOrFetchFromTmdb(cacheKey, "movieCredits", query) | ||||||
|       .catch(() => this.tmdb('movieCredits', query)) |       .then(credits => this.cache.set(cacheKey, credits, this.defaultTTL)) | ||||||
|       .catch(tmdbError => tmdbErrorResponse(tmdbError, 'movie credits')) |       .then(credits => Credits.convertFromTmdbResponse(credits)); | ||||||
|       .then(credits => this.cache.set(cacheKey, credits, 1)) |  | ||||||
|       .then(credits => Credits.convertFromTmdbResponse(credits)) |  | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   /** |   /** | ||||||
| @@ -69,12 +108,10 @@ class TMDB { | |||||||
|    */ |    */ | ||||||
|   movieReleaseDates(identifier) { |   movieReleaseDates(identifier) { | ||||||
|     const query = { id: identifier } |     const query = { id: identifier } | ||||||
|     const cacheKey = `${this.cacheTags.movieReleaseDates}:${identifier}` |     const cacheKey = `tmdb/${this.cacheTags.movieReleaseDates}:${identifier}` | ||||||
|  |  | ||||||
|     return this.cache.get(cacheKey) |     return this.getFromCacheOrFetchFromTmdb(cacheKey, 'movieReleaseDates', query) | ||||||
|       .catch(() => this.tmdb('movieReleaseDates', query)) |       .then(releaseDates => this.cache.set(cacheKey, releaseDates, this.defaultTTL)) | ||||||
|       .catch(tmdbError => tmdbErrorResponse(tmdbError, 'movie release dates')) |  | ||||||
|       .then(releaseDates => this.cache.set(cacheKey, releaseDates, 1)) |  | ||||||
|       .then(releaseDates => ReleaseDates.convertFromTmdbResponse(releaseDates)) |       .then(releaseDates => ReleaseDates.convertFromTmdbResponse(releaseDates)) | ||||||
|   } |   } | ||||||
|   |   | ||||||
| @@ -86,24 +123,20 @@ class TMDB { | |||||||
|    */ |    */ | ||||||
|   showInfo(identifier) { |   showInfo(identifier) { | ||||||
|     const query = { id: identifier }; |     const query = { id: identifier }; | ||||||
|     const cacheKey = `${this.cacheTags.showInfo}:${identifier}`; |     const cacheKey = `tmdb/${this.cacheTags.showInfo}:${identifier}`; | ||||||
|  |  | ||||||
|     return this.cache.get(cacheKey) |     return this.getFromCacheOrFetchFromTmdb(cacheKey, "tvInfo", query) | ||||||
|       .catch(() => this.tmdb('tvInfo', query)) |       .then(show => this.cache.set(cacheKey, show, this.defaultTTL)) | ||||||
|       .catch(tmdbError => tmdbErrorResponse(tmdbError, 'tv info')) |       .then(show => Show.convertFromTmdbResponse(show)); | ||||||
|       .then(show => this.cache.set(cacheKey, show, 1)) |  | ||||||
|       .then(show => Show.convertFromTmdbResponse(show)) |  | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   showCredits(identifier) { |   showCredits(identifier) { | ||||||
|     const query = { id: identifier } |     const query = { id: identifier }; | ||||||
|     const cacheKey = `${this.cacheTags.showCredits}:${identifier}` |     const cacheKey = `tmdb/${this.cacheTags.showCredits}:${identifier}`; | ||||||
|  |  | ||||||
|     return this.cache.get(cacheKey) |     return this.getFromCacheOrFetchFromTmdb(cacheKey, "tvCredits", query) | ||||||
|       .catch(() => this.tmdb('tvCredits', query)) |       .then(credits => this.cache.set(cacheKey, credits, this.defaultTTL)) | ||||||
|       .catch(tmdbError => tmdbErrorResponse(tmdbError, 'show credits')) |       .then(credits => Credits.convertFromTmdbResponse(credits)); | ||||||
|       .then(credits => this.cache.set(cacheKey, credits, 1)) |  | ||||||
|       .then(credits => Credits.convertFromTmdbResponse(credits)) |  | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   /** |   /** | ||||||
| @@ -114,22 +147,32 @@ class TMDB { | |||||||
|    */ |    */ | ||||||
|   personInfo(identifier) { |   personInfo(identifier) { | ||||||
|     const query = { id: identifier }; |     const query = { id: identifier }; | ||||||
|     const cacheKey = `${this.cacheTags.personInfo}:${identifier}`; |     const cacheKey = `tmdb/${this.cacheTags.personInfo}:${identifier}`; | ||||||
|  |  | ||||||
|     return this.cache.get(cacheKey) |     return this.getFromCacheOrFetchFromTmdb(cacheKey, "personInfo", query) | ||||||
|       .catch(() => this.tmdb('personInfo', query)) |       .then(person => this.cache.set(cacheKey, person, this.defaultTTL)) | ||||||
|       .catch(tmdbError => tmdbErrorResponse(tmdbError, 'person info')) |       .then(person => Person.convertFromTmdbResponse(person)); | ||||||
|       .then(person => this.cache.set(cacheKey, person, 1)) |  | ||||||
|       .then(person => Person.convertFromTmdbResponse(person)) |  | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   multiSearch(search_query, page=1) { |   personCredits(identifier) { | ||||||
|     const query = { query: search_query, page: page }; |     const query = { id: identifier }; | ||||||
|     const cacheKey = `${this.cacheTags.multiSearch}:${page}:${search_query}`; |     const cacheKey = `tmdb/${this.cacheTags.personCredits}:${identifier}`; | ||||||
|     return this.cache.get(cacheKey) |  | ||||||
|       .catch(() => this.tmdb('searchMulti', query)) |     return this.getFromCacheOrFetchFromTmdb( | ||||||
|       .catch(tmdbError => tmdbErrorResponse(tmdbError, 'search results')) |       cacheKey, | ||||||
|       .then(response => this.cache.set(cacheKey, response, 1)) |       "personCombinedCredits", | ||||||
|  |       query | ||||||
|  |     ) | ||||||
|  |       .then(credits => this.cache.set(cacheKey, credits, this.defaultTTL)) | ||||||
|  |       .then(credits => Credits.convertFromTmdbResponse(credits)); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   multiSearch(search_query, page = 1, include_adult = true) { | ||||||
|  |     const query = { query: search_query, page, include_adult }; | ||||||
|  |     const cacheKey = `tmdb/${this.cacheTags.multiSearch}:${page}:${search_query}:${include_adult}`; | ||||||
|  |  | ||||||
|  |     return this.getFromCacheOrFetchFromTmdb(cacheKey, "searchMulti", query) | ||||||
|  |       .then(response => this.cache.set(cacheKey, response, this.defaultTTL)) | ||||||
|       .then(response => this.mapResults(response)); |       .then(response => this.mapResults(response)); | ||||||
|   } |   } | ||||||
|  |  | ||||||
| @@ -139,15 +182,13 @@ 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(query, page=1) { |   movieSearch(search_query, page = 1, include_adult = true) { | ||||||
|     const tmdbquery = { query: query, page: page }; |     const tmdbquery = { query: search_query, page, include_adult }; | ||||||
|     const cacheKey = `${this.cacheTags.movieSearch}:${page}:${query}`; |     const cacheKey = `tmdb/${this.cacheTags.movieSearch}:${page}:${search_query}:${include_adult}`; | ||||||
|  |  | ||||||
|     return this.cache.get(cacheKey) |     return this.getFromCacheOrFetchFromTmdb(cacheKey, "searchMovie", tmdbquery) | ||||||
|       .catch(() => this.tmdb('searchMovie', tmdbquery)) |       .then(response => this.cache.set(cacheKey, response, this.defaultTTL)) | ||||||
|       .catch(tmdbError => tmdbErrorResponse(tmdbError, 'movie search results')) |       .then(response => this.mapResults(response, "movie")); | ||||||
|       .then(response => this.cache.set(cacheKey, response, 1)) |  | ||||||
|       .then(response => this.mapResults(response, 'movie')) |  | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   /** |   /** | ||||||
| @@ -156,15 +197,13 @@ 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(query, page=1) { |   showSearch(search_query, page = 1, include_adult = true) { | ||||||
|     const tmdbquery = { query: query, page: page }; |     const tmdbquery = { query: search_query, page, include_adult }; | ||||||
|     const cacheKey = `${this.cacheTags.showSearch}:${page}:${query}`; |     const cacheKey = `tmdb/${this.cacheTags.showSearch}:${page}:${search_query}:${include_adult}`; | ||||||
|  |  | ||||||
|     return this.cache.get(cacheKey) |     return this.getFromCacheOrFetchFromTmdb(cacheKey, "searchTv", tmdbquery) | ||||||
|       .catch(() => this.tmdb('searchTv', tmdbquery)) |       .then(response => this.cache.set(cacheKey, response, this.defaultTTL)) | ||||||
|       .catch(tmdbError => tmdbErrorResponse(tmdbError, 'tv search results')) |       .then(response => this.mapResults(response, "show")); | ||||||
|       .then(response => this.cache.set(cacheKey, response, 1)) |  | ||||||
|       .then(response => this.mapResults(response, 'show')) |  | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   /** |   /** | ||||||
| @@ -173,37 +212,31 @@ 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(query, page=1) { |   personSearch(search_query, page = 1, include_adult = true) { | ||||||
|  |     const tmdbquery = { query: search_query, page, include_adult }; | ||||||
|  |     const cacheKey = `tmdb/${this.cacheTags.personSearch}:${page}:${search_query}:${include_adult}`; | ||||||
|  |  | ||||||
|     const tmdbquery = { query: query, page: page, include_adult: true }; |     return this.getFromCacheOrFetchFromTmdb(cacheKey, "searchPerson", tmdbquery) | ||||||
|     const cacheKey = `${this.cacheTags.personSearch}:${page}:${query}`; |       .then(response => this.cache.set(cacheKey, response, this.defaultTTL)) | ||||||
|  |       .then(response => this.mapResults(response, "person")); | ||||||
|     return this.cache.get(cacheKey) |  | ||||||
|       .catch(() => this.tmdb('searchPerson', tmdbquery)) |  | ||||||
|       .catch(tmdbError => tmdbErrorResponse(tmdbError, 'person search results')) |  | ||||||
|       .then(response => this.cache.set(cacheKey, response, 1)) |  | ||||||
|       .then(response => this.mapResults(response, 'person')) |  | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   movieList(listname, page = 1) { |   movieList(listname, page = 1) { | ||||||
|     const query = { page: page }; |     const query = { page: page }; | ||||||
|     const cacheKey = `${this.cacheTags[listname]}:${page}`; |     const cacheKey = `tmdb/${this.cacheTags[listname]}:${page}`; | ||||||
|     return this.cache.get(cacheKey) |  | ||||||
|       .catch(() => this.tmdb(listname, query)) |     return this.getFromCacheOrFetchFromTmdb(cacheKey, listname, query) | ||||||
|       .catch(tmdbError => this.tmdbErrorResponse(tmdbError, 'movie list ' + listname)) |       .then(response => this.cache.set(cacheKey, response, this.defaultTTL)) | ||||||
|       .then(response => this.cache.set(cacheKey, response, 1)) |       .then(response => this.mapResults(response, "movie")); | ||||||
|       .then(response => this.mapResults(response, 'movie')) |  | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   showList(listname, page = 1) { |   showList(listname, page = 1) { | ||||||
|     const query = { page: page }; |     const query = { page: page }; | ||||||
|     const cacheKey = `${this.cacheTags[listname]}:${page}`; |     const cacheKey = `tmdb/${this.cacheTags[listname]}:${page}`; | ||||||
|  |  | ||||||
|     return this.cache.get(cacheKey) |     return this.getFromCacheOrFetchFromTmdb(cacheKey, listName, query) | ||||||
|       .catch(() => this.tmdb(listname, query)) |       .then(response => this.cache.set(cacheKey, response, this.defaultTTL)) | ||||||
|       .catch(tmdbError => this.tmdbErrorResponse(tmdbError, 'show list ' + listname)) |       .then(response => this.mapResults(response, "show")); | ||||||
|       .then(response => this.cache.set(cacheKey, response, 1)) |  | ||||||
|       .then(response => this.mapResults(response, 'show')) |  | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   /** |   /** | ||||||
| @@ -213,28 +246,25 @@ class TMDB { | |||||||
|    * @returns {Promise} dict with tmdb results, mapped as movie/show objects. |    * @returns {Promise} dict with tmdb results, mapped as movie/show objects. | ||||||
|    */ |    */ | ||||||
|   mapResults(response, type = undefined) { |   mapResults(response, type = undefined) { | ||||||
|     // console.log(response.results) |  | ||||||
|     // response.results.map(te => console.table(te)) |  | ||||||
|  |  | ||||||
|     let results = response.results.map(result => { |     let results = response.results.map(result => { | ||||||
|       if (type === 'movie' || result.media_type === 'movie') { |       if (type === "movie" || result.media_type === "movie") { | ||||||
|         const movie = Movie.convertFromTmdbResponse(result) |         const movie = Movie.convertFromTmdbResponse(result); | ||||||
|         return movie.createJsonResponse() |         return movie.createJsonResponse(); | ||||||
|       } else if (type === 'show' || result.media_type === 'tv') { |       } else if (type === "show" || result.media_type === "tv") { | ||||||
|         const show = Show.convertFromTmdbResponse(result) |         const show = Show.convertFromTmdbResponse(result); | ||||||
|         return show.createJsonResponse() |         return show.createJsonResponse(); | ||||||
|       } else if (type === 'person' || result.media_type === 'person') { |       } else if (type === "person" || result.media_type === "person") { | ||||||
|         const person = Person.convertFromTmdbResponse(result) |         const person = Person.convertFromTmdbResponse(result); | ||||||
|         return person.createJsonResponse() |         return person.createJsonResponse(); | ||||||
|       } |       } | ||||||
|     }) |     }); | ||||||
|  |  | ||||||
|     return { |     return { | ||||||
|       results: results, |       results: results, | ||||||
|       page: response.page, |       page: response.page, | ||||||
|       total_results: response.total_results, |       total_results: response.total_results, | ||||||
|       total_pages: response.total_pages |       total_pages: response.total_pages | ||||||
|     } |     }; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   /** |   /** | ||||||
| @@ -259,28 +289,6 @@ class TMDB { | |||||||
|       } |       } | ||||||
|     }); |     }); | ||||||
|   } |   } | ||||||
|  |  | ||||||
| } |  | ||||||
|  |  | ||||||
| function tmdbErrorResponse(error, typeString=undefined) { |  | ||||||
|   if (error.status === 404) { |  | ||||||
|     let message = error.response.body.status_message; |  | ||||||
|  |  | ||||||
|     throw { |  | ||||||
|       status: 404, |  | ||||||
|       message: message.slice(0, -1) + " in tmdb." |  | ||||||
|     } |  | ||||||
|   } else if (error.status === 401) { |  | ||||||
|     throw { |  | ||||||
|       status: 401, |  | ||||||
|       message: error.response.body.status_message |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   throw { |  | ||||||
|     status: 500, |  | ||||||
|     message: `An unexpected error occured while fetching ${typeString} from tmdb` |  | ||||||
|   } |  | ||||||
| } | } | ||||||
|  |  | ||||||
| module.exports = TMDB; | module.exports = TMDB; | ||||||
|   | |||||||
| @@ -1,7 +1,7 @@ | |||||||
| import Movie from './types/movie.js' | const Movie = require('./types/movie.js') | ||||||
| import Show from './types/show.js' | const Show = require('./types/show.js') | ||||||
| import Person from './types/person.js' | const Person = require('./types/person.js') | ||||||
| import Credits from './types/credits.js' | const Credits = require('./types/credits.js') | ||||||
| import ReleaseDates from './types/releaseDates.js' | const ReleaseDates = require('./types/releaseDates.js') | ||||||
|  |  | ||||||
| module.exports = { Movie, Show, Person, Credits, ReleaseDates } | module.exports = { Movie, Show, Person, Credits, ReleaseDates } | ||||||
|   | |||||||
| @@ -1,20 +1,55 @@ | |||||||
|  | import Movie from "./movie"; | ||||||
|  | import Show from "./show"; | ||||||
|  |  | ||||||
| class Credits { | class Credits { | ||||||
|   constructor(id, cast = [], crew = []) { |   constructor(id, cast = [], crew = []) { | ||||||
|     this.id = id; |     this.id = id; | ||||||
|     this.cast = cast; |     this.cast = cast; | ||||||
|     this.crew = crew; |     this.crew = crew; | ||||||
|     this.type = 'credits'; |     this.type = "credits"; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   static convertFromTmdbResponse(response) { |   static convertFromTmdbResponse(response) { | ||||||
|     const { id, cast, crew } = response; |     const { id, cast, crew } = response; | ||||||
|  |  | ||||||
|     const allCast = cast.map(cast =>  |     const allCast = cast.map(cast => { | ||||||
|       new CastMember(cast.character, cast.gender, cast.id, cast.name, cast.profile_path)) |       if (cast["media_type"]) { | ||||||
|     const allCrew = crew.map(crew => |         if (cast.media_type === "movie") { | ||||||
|       new CrewMember(crew.department, crew.gender, crew.id, crew.job, crew.name, crew.profile_path)) |           return CreditedMovie.convertFromTmdbResponse(cast); | ||||||
|  |         } else if (cast.media_type === "tv") { | ||||||
|  |           return CreditedShow.convertFromTmdbResponse(cast); | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |  | ||||||
|     return new Credits(id, allCast, allCrew) |       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() { |   createJsonResponse() { | ||||||
| @@ -22,7 +57,7 @@ class Credits { | |||||||
|       id: this.id, |       id: this.id, | ||||||
|       cast: this.cast.map(cast => cast.createJsonResponse()), |       cast: this.cast.map(cast => cast.createJsonResponse()), | ||||||
|       crew: this.crew.map(crew => crew.createJsonResponse()) |       crew: this.crew.map(crew => crew.createJsonResponse()) | ||||||
|     } |     }; | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -33,7 +68,7 @@ class CastMember { | |||||||
|     this.id = id; |     this.id = id; | ||||||
|     this.name = name; |     this.name = name; | ||||||
|     this.profile_path = profile_path; |     this.profile_path = profile_path; | ||||||
|     this.type = 'cast member'; |     this.type = "person"; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   createJsonResponse() { |   createJsonResponse() { | ||||||
| @@ -44,7 +79,7 @@ class CastMember { | |||||||
|       name: this.name, |       name: this.name, | ||||||
|       profile_path: this.profile_path, |       profile_path: this.profile_path, | ||||||
|       type: this.type |       type: this.type | ||||||
|     } |     }; | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -56,7 +91,7 @@ class CrewMember { | |||||||
|     this.job = job; |     this.job = job; | ||||||
|     this.name = name; |     this.name = name; | ||||||
|     this.profile_path = profile_path; |     this.profile_path = profile_path; | ||||||
|     this.type = 'crew member'; |     this.type = "person"; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   createJsonResponse() { |   createJsonResponse() { | ||||||
| @@ -68,8 +103,11 @@ class CrewMember { | |||||||
|       name: this.name, |       name: this.name, | ||||||
|       profile_path: this.profile_path, |       profile_path: this.profile_path, | ||||||
|       type: this.type |       type: this.type | ||||||
|     } |     }; | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | class CreditedMovie extends Movie {} | ||||||
|  | class CreditedShow extends Show {} | ||||||
|  |  | ||||||
| module.exports = Credits; | module.exports = Credits; | ||||||
|   | |||||||
| @@ -1,23 +1,54 @@ | |||||||
| class Person { | class Person { | ||||||
|   constructor(id, name, poster=undefined, birthday=undefined, deathday=undefined, |   constructor( | ||||||
|               adult=undefined, knownForDepartment=undefined) { |     id, | ||||||
|  |     name, | ||||||
|  |     poster = undefined, | ||||||
|  |     birthday = undefined, | ||||||
|  |     deathday = undefined, | ||||||
|  |     adult = undefined, | ||||||
|  |     placeOfBirth = undefined, | ||||||
|  |     biography = undefined, | ||||||
|  |     knownForDepartment = undefined | ||||||
|  |   ) { | ||||||
|     this.id = id; |     this.id = id; | ||||||
|     this.name = name; |     this.name = name; | ||||||
|     this.poster = poster; |     this.poster = poster; | ||||||
|     this.birthday = birthday; |     this.birthday = birthday; | ||||||
|     this.deathday = deathday; |     this.deathday = deathday; | ||||||
|     this.adult = adult; |     this.adult = adult; | ||||||
|  |     this.placeOfBirth = placeOfBirth; | ||||||
|  |     this.biography = biography; | ||||||
|     this.knownForDepartment = knownForDepartment; |     this.knownForDepartment = knownForDepartment; | ||||||
|     this.type = 'person'; |     this.type = "person"; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   static convertFromTmdbResponse(response) { |   static convertFromTmdbResponse(response) { | ||||||
|     const { id, name, poster, birthday, deathday, adult, known_for_department } = response; |     const { | ||||||
|  |       id, | ||||||
|  |       name, | ||||||
|  |       profile_path, | ||||||
|  |       birthday, | ||||||
|  |       deathday, | ||||||
|  |       adult, | ||||||
|  |       place_of_birth, | ||||||
|  |       biography, | ||||||
|  |       known_for_department | ||||||
|  |     } = response; | ||||||
|  |  | ||||||
|     const birthDay = new Date(birthday) |     const birthDay = new Date(birthday); | ||||||
|     const deathDay = deathday ? new Date(deathday) : null |     const deathDay = deathday ? new Date(deathday) : null; | ||||||
|  |  | ||||||
|     return new Person(id, name, poster, birthDay, deathDay, adult, known_for_department) |     return new Person( | ||||||
|  |       id, | ||||||
|  |       name, | ||||||
|  |       profile_path, | ||||||
|  |       birthDay, | ||||||
|  |       deathDay, | ||||||
|  |       adult, | ||||||
|  |       place_of_birth, | ||||||
|  |       biography, | ||||||
|  |       known_for_department | ||||||
|  |     ); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   createJsonResponse() { |   createJsonResponse() { | ||||||
| @@ -27,10 +58,12 @@ class Person { | |||||||
|       poster: this.poster, |       poster: this.poster, | ||||||
|       birthday: this.birthday, |       birthday: this.birthday, | ||||||
|       deathday: this.deathday, |       deathday: this.deathday, | ||||||
|  |       place_of_birth: this.placeOfBirth, | ||||||
|  |       biography: this.biography, | ||||||
|       known_for_department: this.knownForDepartment, |       known_for_department: this.knownForDepartment, | ||||||
|       adult: this.adult, |       adult: this.adult, | ||||||
|       type: this.type |       type: this.type | ||||||
|     } |     }; | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,10 +1,11 @@ | |||||||
| const User = require('src/user/user'); | const User = require("src/user/user"); | ||||||
| const jwt = require('jsonwebtoken'); | const jwt = require("jsonwebtoken"); | ||||||
|  |  | ||||||
| class Token { | class Token { | ||||||
|   constructor(user, admin=false) { |   constructor(user, admin = false, settings = null) { | ||||||
|     this.user = user; |     this.user = user; | ||||||
|     this.admin = admin; |     this.admin = admin; | ||||||
|  |     this.settings = settings; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   /** |   /** | ||||||
| @@ -13,14 +14,12 @@ class Token { | |||||||
|    * @returns {String} |    * @returns {String} | ||||||
|    */ |    */ | ||||||
|   toString(secret) { |   toString(secret) { | ||||||
|     const username = this.user.username; |     const { user, admin, settings } = this; | ||||||
|     const admin = this.admin; |  | ||||||
|     let data = { username } |  | ||||||
|  |  | ||||||
|     if (admin) |     let data = { username: user.username, settings }; | ||||||
|       data = { ...data, admin } |     if (admin) data["admin"] = admin; | ||||||
|  |  | ||||||
|     return jwt.sign(data, secret, { expiresIn: '90d' }); |     return jwt.sign(data, secret, { expiresIn: "90d" }); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   /** |   /** | ||||||
| @@ -30,15 +29,12 @@ class Token { | |||||||
|    * @returns {Token} |    * @returns {Token} | ||||||
|    */ |    */ | ||||||
|   static fromString(jwtToken, secret) { |   static fromString(jwtToken, secret) { | ||||||
|     let username = null; |     const token = jwt.verify(jwtToken, secret, { clockTolerance: 10000 }); | ||||||
|  |     if (token.username == null) throw new Error("Malformed token"); | ||||||
|  |  | ||||||
|     const token = jwt.verify(jwtToken, secret, { clockTolerance: 10000 }) |     const { username, admin, settings } = token; | ||||||
|     if (token.username === undefined || token.username === null) |     const user = new User(username); | ||||||
|       throw new Error('Malformed token') |     return new Token(user, admin, settings); | ||||||
|  |  | ||||||
|     username = token.username |  | ||||||
|     const user = new User(username) |  | ||||||
|     return new Token(user) |  | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,20 +1,21 @@ | |||||||
| const assert = require('assert'); | const assert = require("assert"); | ||||||
| const establishedDatabase = require('src/database/database'); | const establishedDatabase = require("src/database/database"); | ||||||
|  |  | ||||||
| class UserRepository { | class UserRepository { | ||||||
|   constructor(database) { |   constructor(database) { | ||||||
|     this.database = database || establishedDatabase; |     this.database = database || establishedDatabase; | ||||||
|     this.queries = { |     this.queries = { | ||||||
|       read: 'select * from user where lower(user_name) = lower(?)', |       read: "select * from user where lower(user_name) = lower(?)", | ||||||
|       create: 'insert into user (user_name) values (?)', |       create: "insert into user (user_name) values (?)", | ||||||
|       change: 'update user set password = ? where user_name = ?', |       change: "update user set password = ? where user_name = ?", | ||||||
|       retrieveHash: 'select * from user where user_name = ?', |       retrieveHash: "select * from user where user_name = ?", | ||||||
|       getAdminStateByUser: 'select admin from user where user_name = ?', |       getAdminStateByUser: "select admin from user where user_name = ?", | ||||||
|       link: 'update settings set plex_userid = ? where user_name = ?', |       link: "update settings set plex_userid = ? where user_name = ?", | ||||||
|       unlink: 'update settings set plex_userid = null where user_name = ?', |       unlink: "update settings set plex_userid = null where user_name = ?", | ||||||
|       createSettings: 'insert into settings (user_name) values (?)', |       createSettings: "insert into settings (user_name) values (?)", | ||||||
|       updateSettings: 'update settings set user_name = ?, dark_mode = ?, emoji = ?', |       updateSettings: | ||||||
|       getSettings: 'select * from settings where user_name = ?' |         "update settings set user_name = ?, dark_mode = ?, emoji = ?", | ||||||
|  |       getSettings: "select * from settings where user_name = ?" | ||||||
|     }; |     }; | ||||||
|   } |   } | ||||||
|  |  | ||||||
| @@ -24,13 +25,17 @@ class UserRepository { | |||||||
|    * @returns {Promise} |    * @returns {Promise} | ||||||
|    */ |    */ | ||||||
|   create(user) { |   create(user) { | ||||||
|     return this.database.get(this.queries.read, user.username) |     return this.database | ||||||
|  |       .get(this.queries.read, user.username) | ||||||
|       .then(() => this.database.run(this.queries.create, user.username)) |       .then(() => this.database.run(this.queries.create, user.username)) | ||||||
|       .catch((error) => { |       .catch(error => { | ||||||
|         if (error.name === 'AssertionError' || error.message.endsWith('user_name')) { |         if ( | ||||||
|            throw new Error('That username is already registered'); |           error.name === "AssertionError" || | ||||||
|  |           error.message.endsWith("user_name") | ||||||
|  |         ) { | ||||||
|  |           throw new Error("That username is already registered"); | ||||||
|         } |         } | ||||||
|         throw Error(error) |         throw Error(error); | ||||||
|       }); |       }); | ||||||
|   } |   } | ||||||
|  |  | ||||||
| @@ -40,12 +45,16 @@ class UserRepository { | |||||||
|    * @returns {Promise} |    * @returns {Promise} | ||||||
|    */ |    */ | ||||||
|   retrieveHash(user) { |   retrieveHash(user) { | ||||||
|     return this.database.get(this.queries.retrieveHash, user.username) |     return this.database | ||||||
|  |       .get(this.queries.retrieveHash, user.username) | ||||||
|       .then(row => { |       .then(row => { | ||||||
|         assert(row, 'The user does not exist.'); |         assert(row, "The user does not exist."); | ||||||
|         return row.password; |         return row.password; | ||||||
|       }) |       }) | ||||||
|       .catch(err => { console.log(error); throw new Error('Unable to find your user.'); }) |       .catch(err => { | ||||||
|  |         console.log(error); | ||||||
|  |         throw new Error("Unable to find your user."); | ||||||
|  |       }); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   /** |   /** | ||||||
| @@ -55,7 +64,7 @@ class UserRepository { | |||||||
|    * @returns {Promise} |    * @returns {Promise} | ||||||
|    */ |    */ | ||||||
|   changePassword(user, password) { |   changePassword(user, password) { | ||||||
|     return this.database.run(this.queries.change, [password, user.username]) |     return this.database.run(this.queries.change, [password, user.username]); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   /** |   /** | ||||||
| @@ -66,19 +75,21 @@ class UserRepository { | |||||||
|    */ |    */ | ||||||
|   linkPlexUserId(username, plexUserID) { |   linkPlexUserId(username, plexUserID) { | ||||||
|     return new Promise((resolve, reject) => { |     return new Promise((resolve, reject) => { | ||||||
|       this.database.run(this.queries.link, [plexUserID, username]) |       this.database | ||||||
|  |         .run(this.queries.link, [plexUserID, username]) | ||||||
|         .then(row => resolve(row)) |         .then(row => resolve(row)) | ||||||
|         .catch(error => { |         .catch(error => { | ||||||
|           // TODO log this unknown db error |           // TODO log this unknown db error | ||||||
|           console.log('db error', error) |           console.error("db error", error); | ||||||
|  |  | ||||||
|           reject({ |           reject({ | ||||||
|             status: 500, |             status: 500, | ||||||
|             message: 'An unexpected error occured while linking plex and seasoned accounts', |             message: | ||||||
|             source: 'seasoned database' |               "An unexpected error occured while linking plex and seasoned accounts", | ||||||
|           }) |             source: "seasoned database" | ||||||
|         }) |           }); | ||||||
|     }) |         }); | ||||||
|  |     }); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   /** |   /** | ||||||
| @@ -88,19 +99,21 @@ class UserRepository { | |||||||
|    */ |    */ | ||||||
|   unlinkPlexUserId(username) { |   unlinkPlexUserId(username) { | ||||||
|     return new Promise((resolve, reject) => { |     return new Promise((resolve, reject) => { | ||||||
|       this.database.run(this.queries.unlink, username) |       this.database | ||||||
|  |         .run(this.queries.unlink, username) | ||||||
|         .then(row => resolve(row)) |         .then(row => resolve(row)) | ||||||
|         .catch(error => { |         .catch(error => { | ||||||
|           // TODO log this unknown db error |           // TODO log this unknown db error | ||||||
|           console.log('db error', error) |           console.log("db error", error); | ||||||
|  |  | ||||||
|           reject({ |           reject({ | ||||||
|             status: 500, |             status: 500, | ||||||
|             message: 'An unexpected error occured while unlinking plex and seasoned accounts', |             message: | ||||||
|             source: 'seasoned database' |               "An unexpected error occured while unlinking plex and seasoned accounts", | ||||||
|           }) |             source: "seasoned database" | ||||||
|         }) |           }); | ||||||
|     }) |         }); | ||||||
|  |     }); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   /** |   /** | ||||||
| @@ -109,8 +122,9 @@ class UserRepository { | |||||||
|    * @returns {Promsie} |    * @returns {Promsie} | ||||||
|    */ |    */ | ||||||
|   checkAdmin(user) { |   checkAdmin(user) { | ||||||
|     return this.database.get(this.queries.getAdminStateByUser, user.username) |     return this.database | ||||||
|       .then((row) => row.admin); |       .get(this.queries.getAdminStateByUser, user.username) | ||||||
|  |       .then(row => row.admin); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   /** |   /** | ||||||
| @@ -120,37 +134,47 @@ class UserRepository { | |||||||
|    */ |    */ | ||||||
|   getSettings(username) { |   getSettings(username) { | ||||||
|     return new Promise((resolve, reject) => { |     return new Promise((resolve, reject) => { | ||||||
|       this.database.get(this.queries.getSettings, username) |       this.database | ||||||
|         .then(async (row) => { |         .get(this.queries.getSettings, username) | ||||||
|  |         .then(async row => { | ||||||
|           if (row == null) { |           if (row == null) { | ||||||
|             console.log(`settings do not exist for user: ${username}. Creating settings entry.`) |             console.debug( | ||||||
|  |               `settings do not exist for user: ${username}. Creating settings entry.` | ||||||
|  |             ); | ||||||
|  |  | ||||||
|             const userExistsWithUsername = await this.database.get('select * from user where user_name is ?', username) |             const userExistsWithUsername = await this.database.get( | ||||||
|  |               "select * from user where user_name is ?", | ||||||
|  |               username | ||||||
|  |             ); | ||||||
|             if (userExistsWithUsername !== undefined) { |             if (userExistsWithUsername !== undefined) { | ||||||
|               try { |               try { | ||||||
|                 resolve(this.dbCreateSettings(username)) |                 resolve(this.dbCreateSettings(username)); | ||||||
|               } catch (error) { |               } catch (error) { | ||||||
|                 reject(error) |                 reject(error); | ||||||
|               } |               } | ||||||
|             } else { |             } else { | ||||||
|               reject({ |               reject({ | ||||||
|                 status: 404, |                 status: 404, | ||||||
|                 message: 'User not found, no settings to get' |                 message: "User not found, no settings to get" | ||||||
|               }) |               }); | ||||||
|             } |             } | ||||||
|           } |           } | ||||||
|  |  | ||||||
|           resolve(row) |           resolve(row); | ||||||
|         }) |         }) | ||||||
|         .catch(error => { |         .catch(error => { | ||||||
|           console.error('Unexpected error occured while fetching settings for your account. Error:', error) |           console.error( | ||||||
|  |             "Unexpected error occured while fetching settings for your account. Error:", | ||||||
|  |             error | ||||||
|  |           ); | ||||||
|           reject({ |           reject({ | ||||||
|             status: 500, |             status: 500, | ||||||
|             message: 'An unexpected error occured while fetching settings for your account', |             message: | ||||||
|             source: 'seasoned database' |               "An unexpected error occured while fetching settings for your account", | ||||||
|           }) |             source: "seasoned database" | ||||||
|         }) |           }); | ||||||
|     }) |         }); | ||||||
|  |     }); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   /** |   /** | ||||||
| @@ -161,21 +185,21 @@ class UserRepository { | |||||||
|    * @returns {Promsie} |    * @returns {Promsie} | ||||||
|    */ |    */ | ||||||
|   updateSettings(username, dark_mode = undefined, emoji = undefined) { |   updateSettings(username, dark_mode = undefined, emoji = undefined) { | ||||||
|     const settings = this.getSettings(username) |     const settings = this.getSettings(username); | ||||||
|     dark_mode = dark_mode !== undefined ? dark_mode : settings.dark_mode |     dark_mode = dark_mode !== undefined ? dark_mode : settings.dark_mode; | ||||||
|     emoji = emoji !== undefined ? emoji : settings.emoji |     emoji = emoji !== undefined ? emoji : settings.emoji; | ||||||
|  |  | ||||||
|     return this.dbUpdateSettings(username, dark_mode, emoji) |     return this.dbUpdateSettings(username, dark_mode, emoji).catch(error => { | ||||||
|       .catch(error => { |  | ||||||
|       if (error.status && error.message) { |       if (error.status && error.message) { | ||||||
|           return error |         return error; | ||||||
|       } |       } | ||||||
|  |  | ||||||
|       return { |       return { | ||||||
|         status: 500, |         status: 500, | ||||||
|           message: 'An unexpected error occured while updating settings for your account' |         message: | ||||||
|         } |           "An unexpected error occured while updating settings for your account" | ||||||
|       }) |       }; | ||||||
|  |     }); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   /** |   /** | ||||||
| @@ -184,9 +208,16 @@ class UserRepository { | |||||||
|    * @returns {Promsie} |    * @returns {Promsie} | ||||||
|    */ |    */ | ||||||
|   dbCreateSettings(username) { |   dbCreateSettings(username) { | ||||||
|     return this.database.run(this.queries.createSettings, username) |     return this.database | ||||||
|  |       .run(this.queries.createSettings, username) | ||||||
|       .then(() => this.database.get(this.queries.getSettings, username)) |       .then(() => this.database.get(this.queries.getSettings, username)) | ||||||
|       .catch(error => rejectUnexpectedDatabaseError('Unexpected error occured while creating settings', 503, error)) |       .catch(error => | ||||||
|  |         rejectUnexpectedDatabaseError( | ||||||
|  |           "Unexpected error occured while creating settings", | ||||||
|  |           503, | ||||||
|  |           error | ||||||
|  |         ) | ||||||
|  |       ); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   /** |   /** | ||||||
| @@ -196,24 +227,30 @@ class UserRepository { | |||||||
|    */ |    */ | ||||||
|   dbUpdateSettings(username, dark_mode, emoji) { |   dbUpdateSettings(username, dark_mode, emoji) { | ||||||
|     return new Promise((resolve, reject) => |     return new Promise((resolve, reject) => | ||||||
|       this.database.run(this.queries.updateSettings, [username, dark_mode, emoji]) |       this.database | ||||||
|       .then(row => resolve(row))) |         .run(this.queries.updateSettings, [username, dark_mode, emoji]) | ||||||
|  |         .then(row => resolve(row)) | ||||||
|  |     ); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | const rejectUnexpectedDatabaseError = ( | ||||||
| const rejectUnexpectedDatabaseError = (message, status, error, reject=null) => { |   message, | ||||||
|   console.error(error) |   status, | ||||||
|  |   error, | ||||||
|  |   reject = null | ||||||
|  | ) => { | ||||||
|  |   console.error(error); | ||||||
|   const body = { |   const body = { | ||||||
|     status, |     status, | ||||||
|     message, |     message, | ||||||
|     source: 'seasoned database' |     source: "seasoned database" | ||||||
|   } |   }; | ||||||
|  |  | ||||||
|   if (reject == null) { |   if (reject == null) { | ||||||
|     return new Promise((resolve, reject) => reject(body)) |     return new Promise((resolve, reject) => reject(body)); | ||||||
|   } |  | ||||||
|   reject(body) |  | ||||||
|   } |   } | ||||||
|  |   reject(body); | ||||||
|  | }; | ||||||
|  |  | ||||||
| module.exports = UserRepository; | module.exports = UserRepository; | ||||||
|   | |||||||
| @@ -1,5 +1,5 @@ | |||||||
| const bcrypt = require('bcrypt'); | const bcrypt = require("bcrypt"); | ||||||
| const UserRepository = require('src/user/userRepository'); | const UserRepository = require("src/user/userRepository"); | ||||||
|  |  | ||||||
| class UserSecurity { | class UserSecurity { | ||||||
|   constructor(database) { |   constructor(database) { | ||||||
| @@ -13,15 +13,15 @@ class UserSecurity { | |||||||
|    * @returns {Promise} |    * @returns {Promise} | ||||||
|    */ |    */ | ||||||
|   createNewUser(user, clearPassword) { |   createNewUser(user, clearPassword) { | ||||||
|     if (user.username.trim() === '') { |     if (user.username.trim() === "") { | ||||||
|       throw new Error('The username is empty.'); |       throw new Error("The username is empty."); | ||||||
|     } else if (clearPassword.trim() === '') { |     } else if (clearPassword.trim() === "") { | ||||||
|       throw new Error('The password is empty.'); |       throw new Error("The password is empty."); | ||||||
|     } else { |     } else { | ||||||
|       return Promise.resolve() |       return this.userRepository | ||||||
|         .then(() => this.userRepository.create(user)) |         .create(user) | ||||||
|         .then(() => UserSecurity.hashPassword(clearPassword)) |         .then(() => UserSecurity.hashPassword(clearPassword)) | ||||||
|         .then(hash => this.userRepository.changePassword(user, hash)) |         .then(hash => this.userRepository.changePassword(user, hash)); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
| @@ -32,10 +32,12 @@ class UserSecurity { | |||||||
|    * @returns {Promise} |    * @returns {Promise} | ||||||
|    */ |    */ | ||||||
|   login(user, clearPassword) { |   login(user, clearPassword) { | ||||||
|     return Promise.resolve() |     return this.userRepository | ||||||
|       .then(() => this.userRepository.retrieveHash(user)) |       .retrieveHash(user) | ||||||
|       .then(hash => UserSecurity.compareHashes(hash, clearPassword)) |       .then(hash => UserSecurity.compareHashes(hash, clearPassword)) | ||||||
|       .catch(() => { throw new Error('Incorrect username or password.'); }); |       .catch(() => { | ||||||
|  |         throw new Error("Incorrect username or password."); | ||||||
|  |       }); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   /** |   /** | ||||||
| @@ -47,9 +49,8 @@ class UserSecurity { | |||||||
|   static compareHashes(hash, clearPassword) { |   static compareHashes(hash, clearPassword) { | ||||||
|     return new Promise((resolve, reject) => { |     return new Promise((resolve, reject) => { | ||||||
|       bcrypt.compare(clearPassword, hash, (error, match) => { |       bcrypt.compare(clearPassword, hash, (error, match) => { | ||||||
|         if (match) |         if (match) resolve(true); | ||||||
|           resolve() |         reject(false); | ||||||
|         reject() |  | ||||||
|       }); |       }); | ||||||
|     }); |     }); | ||||||
|   } |   } | ||||||
| @@ -60,9 +61,11 @@ class UserSecurity { | |||||||
|    * @returns {Promise} |    * @returns {Promise} | ||||||
|    */ |    */ | ||||||
|   static hashPassword(clearPassword) { |   static hashPassword(clearPassword) { | ||||||
|     return new Promise((resolve) => { |     return new Promise(resolve => { | ||||||
|       const saltRounds = 10; |       const saltRounds = 10; | ||||||
|       bcrypt.hash(clearPassword, saltRounds, (error, hash) => { |       bcrypt.hash(clearPassword, saltRounds, (error, hash) => { | ||||||
|  |         if (error) reject(error); | ||||||
|  |  | ||||||
|         resolve(hash); |         resolve(hash); | ||||||
|       }); |       }); | ||||||
|     }); |     }); | ||||||
|   | |||||||
| @@ -1,146 +1,239 @@ | |||||||
| const express = require('express'); | const express = require("express"); | ||||||
| const Raven = require('raven'); | const Raven = require("raven"); | ||||||
| const bodyParser = require('body-parser'); | const cookieParser = require("cookie-parser"); | ||||||
| const tokenToUser = require('./middleware/tokenToUser'); | const bodyParser = require("body-parser"); | ||||||
| const mustBeAuthenticated = require('./middleware/mustBeAuthenticated'); |  | ||||||
| const mustBeAdmin = require('./middleware/mustBeAdmin'); |  | ||||||
| const mustHaveAccountLinkedToPlex = require('./middleware/mustHaveAccountLinkedToPlex'); |  | ||||||
| const configuration = require('src/config/configuration').getInstance(); |  | ||||||
|  |  | ||||||
| const listController = require('./controllers/list/listController'); | const configuration = require("src/config/configuration").getInstance(); | ||||||
| const tautulli = require('./controllers/user/viewHistory.js'); |  | ||||||
| const SettingsController = require('./controllers/user/settings'); | const reqTokenToUser = require("./middleware/reqTokenToUser"); | ||||||
| const AuthenticatePlexAccountController = require('./controllers/user/AuthenticatePlexAccount'); | const mustBeAuthenticated = require("./middleware/mustBeAuthenticated"); | ||||||
|  | const mustBeAdmin = require("./middleware/mustBeAdmin"); | ||||||
|  | const mustHaveAccountLinkedToPlex = require("./middleware/mustHaveAccountLinkedToPlex"); | ||||||
|  |  | ||||||
|  | const listController = require("./controllers/list/listController"); | ||||||
|  | const tautulli = require("./controllers/user/viewHistory.js"); | ||||||
|  | const SettingsController = require("./controllers/user/settings"); | ||||||
|  | const AuthenticatePlexAccountController = require("./controllers/user/authenticatePlexAccount"); | ||||||
|  |  | ||||||
| // TODO: Have our raven router check if there is a value, if not don't enable raven. | // TODO: Have our raven router check if there is a value, if not don't enable raven. | ||||||
| Raven.config(configuration.get('raven', 'DSN')).install(); | Raven.config(configuration.get("raven", "DSN")).install(); | ||||||
|  |  | ||||||
| const app = express(); // define our app using express | const app = express(); // define our app using express | ||||||
| app.use(Raven.requestHandler()); | app.use(Raven.requestHandler()); | ||||||
| app.use(bodyParser.json()); | app.use(bodyParser.json()); | ||||||
|  | app.use(cookieParser()); | ||||||
|  |  | ||||||
| const router = express.Router(); | const router = express.Router(); | ||||||
| const allowedOrigins = ['https://kevinmidboe.com', 'http://localhost:8080']; | 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()); | ||||||
| app.use(bodyParser.urlencoded({ extended: true })); | app.use(bodyParser.urlencoded({ extended: true })); | ||||||
|  |  | ||||||
| /* Decode the Authorization header if provided */ | /* Check header and cookie for authentication and set req.loggedInUser */ | ||||||
| router.use(tokenToUser); | router.use(reqTokenToUser); | ||||||
|  |  | ||||||
| // TODO: Should have a separate middleware/router for handling headers. | // TODO: Should have a separate middleware/router for handling headers. | ||||||
| router.use((req, res, next) => { | router.use((req, res, next) => { | ||||||
|   // TODO add logging of all incoming |   // TODO add logging of all incoming | ||||||
|   const origin = req.headers.origin; |   // const origin = req.headers.origin; | ||||||
|    if (allowedOrigins.indexOf(origin) > -1) { |   // if (allowedOrigins.indexOf(origin) > -1) { | ||||||
|        res.setHeader('Access-Control-Allow-Origin', origin); |   //   res.setHeader("Access-Control-Allow-Origin", origin); | ||||||
|    } |   // } | ||||||
|    res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization, loggedinuser'); |  | ||||||
|    res.header('Access-Control-Allow-Methods', 'POST, GET, PUT'); |   res.header( | ||||||
|  |     "Access-Control-Allow-Headers", | ||||||
|  |     "Content-Type, Authorization, loggedinuser, set-cookie" | ||||||
|  |   ); | ||||||
|  |  | ||||||
|  |   res.header("Access-Control-Allow-Credentials", "true"); | ||||||
|  |   res.header("Access-Control-Allow-Methods", "POST, GET, PUT, OPTIONS"); | ||||||
|  |  | ||||||
|   next(); |   next(); | ||||||
| }); | }); | ||||||
|  |  | ||||||
| router.get('/', function mainHandler(req, res) { | router.get("/", (req, res) => { | ||||||
|    throw new Error('Broke!'); |   res.send("welcome to seasoned api"); | ||||||
| }); | }); | ||||||
|  |  | ||||||
| app.use(Raven.errorHandler()); | app.use(Raven.errorHandler()); | ||||||
| app.use(function onError(err, req, res, next) { | app.use((err, req, res, next) => { | ||||||
|   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.js")); | ||||||
| router.post('/v1/user/login', require('./controllers/user/login.js')); | router.post("/v1/user/login", require("./controllers/user/login.js")); | ||||||
| router.get('/v1/user/settings', mustBeAuthenticated, SettingsController.getSettingsController); | router.post("/v1/user/logout", require("./controllers/user/logout.js")); | ||||||
| router.put('/v1/user/settings', mustBeAuthenticated, SettingsController.updateSettingsController); | router.get( | ||||||
| router.get('/v1/user/search_history', mustBeAuthenticated, require('./controllers/user/searchHistory.js')); |   "/v1/user/settings", | ||||||
| router.get('/v1/user/requests', mustBeAuthenticated, require('./controllers/user/requests.js')); |   mustBeAuthenticated, | ||||||
| router.post('/v1/user/link_plex', mustBeAuthenticated, AuthenticatePlexAccountController.link); |   SettingsController.getSettingsController | ||||||
| router.post('/v1/user/unlink_plex', mustBeAuthenticated, AuthenticatePlexAccountController.unlink); | ); | ||||||
|  | router.put( | ||||||
|  |   "/v1/user/settings", | ||||||
|  |   mustBeAuthenticated, | ||||||
|  |   SettingsController.updateSettingsController | ||||||
|  | ); | ||||||
|  | router.get( | ||||||
|  |   "/v1/user/search_history", | ||||||
|  |   mustBeAuthenticated, | ||||||
|  |   require("./controllers/user/searchHistory.js") | ||||||
|  | ); | ||||||
|  | router.get( | ||||||
|  |   "/v1/user/requests", | ||||||
|  |   mustBeAuthenticated, | ||||||
|  |   require("./controllers/user/requests.js") | ||||||
|  | ); | ||||||
|  | router.post( | ||||||
|  |   "/v1/user/link_plex", | ||||||
|  |   mustBeAuthenticated, | ||||||
|  |   AuthenticatePlexAccountController.link | ||||||
|  | ); | ||||||
|  | router.post( | ||||||
|  |   "/v1/user/unlink_plex", | ||||||
|  |   mustBeAuthenticated, | ||||||
|  |   AuthenticatePlexAccountController.unlink | ||||||
|  | ); | ||||||
|  |  | ||||||
| router.get('/v1/user/view_history', mustHaveAccountLinkedToPlex, tautulli.userViewHistoryController); | router.get( | ||||||
| router.get('/v1/user/watch_time', mustHaveAccountLinkedToPlex, tautulli.watchTimeStatsController); |   "/v1/user/view_history", | ||||||
| router.get('/v1/user/plays_by_day', mustHaveAccountLinkedToPlex, tautulli.getPlaysByDaysController); |   mustHaveAccountLinkedToPlex, | ||||||
| router.get('/v1/user/plays_by_dayofweek', mustHaveAccountLinkedToPlex, tautulli.getPlaysByDayOfWeekController); |   tautulli.userViewHistoryController | ||||||
|  | ); | ||||||
|  | router.get( | ||||||
|  |   "/v1/user/watch_time", | ||||||
|  |   mustHaveAccountLinkedToPlex, | ||||||
|  |   tautulli.watchTimeStatsController | ||||||
|  | ); | ||||||
|  | router.get( | ||||||
|  |   "/v1/user/plays_by_day", | ||||||
|  |   mustHaveAccountLinkedToPlex, | ||||||
|  |   tautulli.getPlaysByDaysController | ||||||
|  | ); | ||||||
|  | router.get( | ||||||
|  |   "/v1/user/plays_by_dayofweek", | ||||||
|  |   mustHaveAccountLinkedToPlex, | ||||||
|  |   tautulli.getPlaysByDayOfWeekController | ||||||
|  | ); | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * Seasoned |  * Seasoned | ||||||
|  */ |  */ | ||||||
| router.get('/v1/seasoned/all', require('./controllers/seasoned/readStrays.js')); | router.get("/v1/seasoned/all", require("./controllers/seasoned/readStrays.js")); | ||||||
| router.get('/v1/seasoned/:strayId', require('./controllers/seasoned/strayById.js')); | router.get( | ||||||
| router.post('/v1/seasoned/verify/:strayId', require('./controllers/seasoned/verifyStray.js')); |   "/v1/seasoned/:strayId", | ||||||
|  |   require("./controllers/seasoned/strayById.js") | ||||||
|  | ); | ||||||
|  | router.post( | ||||||
|  |   "/v1/seasoned/verify/:strayId", | ||||||
|  |   require("./controllers/seasoned/verifyStray.js") | ||||||
|  | ); | ||||||
|  |  | ||||||
| router.get('/v2/search/', require('./controllers/search/multiSearch.js')); | router.get("/v2/search/", require("./controllers/search/multiSearch.js")); | ||||||
| router.get('/v2/search/movie', require('./controllers/search/movieSearch.js')); | router.get("/v2/search/movie", require("./controllers/search/movieSearch.js")); | ||||||
| router.get('/v2/search/show', require('./controllers/search/showSearch.js')); | router.get("/v2/search/show", require("./controllers/search/showSearch.js")); | ||||||
| router.get('/v2/search/person', require('./controllers/search/personSearch.js')); | router.get( | ||||||
|  |   "/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/release_dates", | ||||||
|  |   require("./controllers/movie/releaseDates.js") | ||||||
|  | ); | ||||||
|  | router.get("/v2/movie/:id", require("./controllers/movie/info.js")); | ||||||
|  |  | ||||||
| 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", require("./controllers/show/info.js")); | ||||||
|  |  | ||||||
| router.get('/v2/movie/:id/credits', require('./controllers/movie/credits.js')); | router.get( | ||||||
| router.get('/v2/movie/:id/release_dates', require('./controllers/movie/releaseDates.js')); |   "/v2/person/:id/credits", | ||||||
| router.get('/v2/show/:id/credits', require('./controllers/show/credits.js')); |   require("./controllers/person/credits.js") | ||||||
|  | ); | ||||||
| router.get('/v2/movie/:id', require('./controllers/movie/info.js')); | router.get("/v2/person/:id", require("./controllers/person/info.js")); | ||||||
| router.get('/v2/show/:id', require('./controllers/show/info.js')); |  | ||||||
| router.get('/v2/person/:id', require('./controllers/person/info.js')); |  | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * Plex |  * Plex | ||||||
|  */ |  */ | ||||||
| router.get('/v2/plex/search', require('./controllers/plex/search')); | 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.js")); | ||||||
| router.get('/v1/plex/playing', require('./controllers/plex/plexPlaying.js')); | router.get("/v1/plex/playing", require("./controllers/plex/plexPlaying.js")); | ||||||
| router.get('/v1/plex/request', require('./controllers/plex/searchRequest.js')); | router.get("/v1/plex/request", require("./controllers/plex/searchRequest.js")); | ||||||
| router.get('/v1/plex/request/:mediaId', require('./controllers/plex/readRequest.js')); | router.get( | ||||||
| router.post('/v1/plex/request/:mediaId', require('./controllers/plex/submitRequest.js')); |   "/v1/plex/request/:mediaId", | ||||||
| router.post('/v1/plex/hook', require('./controllers/plex/hookDump.js')); |   require("./controllers/plex/readRequest.js") | ||||||
|  | ); | ||||||
|  | router.post( | ||||||
|  |   "/v1/plex/request/:mediaId", | ||||||
|  |   require("./controllers/plex/submitRequest.js") | ||||||
|  | ); | ||||||
|  | router.post("/v1/plex/hook", require("./controllers/plex/hookDump.js")); | ||||||
|  |  | ||||||
|  | router.get( | ||||||
|  |   "/v1/plex/watch-link", | ||||||
|  |   mustBeAuthenticated, | ||||||
|  |   require("./controllers/plex/watchDirectLink.js") | ||||||
|  | ); | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * Requests |  * Requests | ||||||
|  */ |  */ | ||||||
|  |  | ||||||
| router.get('/v2/request', require('./controllers/request/fetchAllRequests.js')); | router.get("/v2/request", require("./controllers/request/fetchAllRequests.js")); | ||||||
| router.get('/v2/request/:id', require('./controllers/request/getRequest.js')); | router.get("/v2/request/:id", require("./controllers/request/getRequest.js")); | ||||||
| router.post('/v2/request', require('./controllers/request/requestTmdbId.js')); | router.post("/v2/request", require("./controllers/request/requestTmdbId.js")); | ||||||
| router.get('/v1/plex/requests/all', require('./controllers/plex/fetchRequested.js')); | router.get( | ||||||
| router.put('/v1/plex/request/:requestId', mustBeAuthenticated, require('./controllers/plex/updateRequested.js')); |   "/v1/plex/requests/all", | ||||||
|  |   require("./controllers/plex/fetchRequested.js") | ||||||
|  | ); | ||||||
|  | router.put( | ||||||
|  |   "/v1/plex/request/:requestId", | ||||||
|  |   mustBeAuthenticated, | ||||||
|  |   require("./controllers/plex/updateRequested.js") | ||||||
|  | ); | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * Pirate |  * Pirate | ||||||
|  */ |  */ | ||||||
| router.get('/v1/pirate/search', mustBeAuthenticated, require('./controllers/pirate/searchTheBay.js')); | router.get( | ||||||
| router.post('/v1/pirate/add', mustBeAuthenticated, require('./controllers/pirate/addMagnet.js')); |   "/v1/pirate/search", | ||||||
|  |   mustBeAuthenticated, | ||||||
|  |   require("./controllers/pirate/searchTheBay.js") | ||||||
|  | ); | ||||||
|  | router.post( | ||||||
|  |   "/v1/pirate/add", | ||||||
|  |   mustBeAuthenticated, | ||||||
|  |   require("./controllers/pirate/addMagnet.js") | ||||||
|  | ); | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * git |  * git | ||||||
|  */ |  */ | ||||||
| router.post('/v1/git/dump', require('./controllers/git/dumpHook.js')); | router.post("/v1/git/dump", require("./controllers/git/dumpHook.js")); | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * misc |  * misc | ||||||
|  */ |  */ | ||||||
|  router.get('/v1/emoji', require('./controllers/misc/emoji.js')); | router.get("/v1/emoji", require("./controllers/misc/emoji.js")); | ||||||
|  |  | ||||||
| // REGISTER OUR ROUTES ------------------------------- | // REGISTER OUR ROUTES ------------------------------- | ||||||
| // all of our routes will be prefixed with /api | // all of our routes will be prefixed with /api | ||||||
| app.use('/api', router); | app.use("/api", router); | ||||||
|  |  | ||||||
| module.exports = app; | module.exports = app; | ||||||
|   | |||||||
| @@ -1,8 +1,6 @@ | |||||||
| const configuration = require('src/config/configuration').getInstance(); | const configuration = require('src/config/configuration').getInstance(); | ||||||
| const Cache = require('src/tmdb/cache'); |  | ||||||
| const TMDB = require('src/tmdb/tmdb'); | const TMDB = require('src/tmdb/tmdb'); | ||||||
| const cache = new Cache(); | const tmdb = new TMDB(configuration.get('tmdb', 'apiKey')); | ||||||
| const tmdb = new TMDB(cache, configuration.get('tmdb', 'apiKey')); |  | ||||||
|  |  | ||||||
| // there should be a translate function from query params to  | // there should be a translate function from query params to  | ||||||
| // tmdb list that is valid. Should it be a helper function or does it  | // tmdb list that is valid. Should it be a helper function or does it  | ||||||
|   | |||||||
| @@ -1,9 +1,7 @@ | |||||||
| const configuration = require('src/config/configuration').getInstance(); | const configuration = require('src/config/configuration').getInstance(); | ||||||
| const Cache = require('src/tmdb/cache'); |  | ||||||
| const TMDB = require('src/tmdb/tmdb'); | const TMDB = require('src/tmdb/tmdb'); | ||||||
|  |  | ||||||
| const cache = new Cache(); | const tmdb = new TMDB(configuration.get('tmdb', 'apiKey')); | ||||||
| const tmdb = new TMDB(cache, configuration.get('tmdb', 'apiKey')); |  | ||||||
|  |  | ||||||
| const movieCreditsController = (req, res) => { | const movieCreditsController = (req, res) => { | ||||||
|   const movieId = req.params.id; |   const movieId = req.params.id; | ||||||
|   | |||||||
| @@ -1,19 +1,19 @@ | |||||||
| const configuration = require('src/config/configuration').getInstance(); | const configuration = require("src/config/configuration").getInstance(); | ||||||
| const Cache = require('src/tmdb/cache'); | const TMDB = require("src/tmdb/tmdb"); | ||||||
| const TMDB = require('src/tmdb/tmdb'); | const Plex = require("src/plex/plex"); | ||||||
| const Plex = require('src/plex/plex'); | const tmdb = new TMDB(configuration.get("tmdb", "apiKey")); | ||||||
| const cache = new Cache(); | const plex = new Plex(configuration.get("plex", "ip")); | ||||||
| const tmdb = new TMDB(cache, configuration.get('tmdb', 'apiKey')); |  | ||||||
| const plex = new Plex(configuration.get('plex', 'ip')); |  | ||||||
|  |  | ||||||
| function handleError(error, res) { | function handleError(error, res) { | ||||||
|   const { status, message } = error; |   const { status, message } = error; | ||||||
|  |  | ||||||
|   if (status && message) { |   if (status && message) { | ||||||
|     res.status(status).send({ success: false, message }) |     res.status(status).send({ success: false, message }); | ||||||
|   } else { |   } else { | ||||||
|     console.log('caught movieinfo controller error', error) |     console.log("caught movieinfo controller error", error); | ||||||
|     res.status(500).send({ message: 'An unexpected error occured while requesting movie info'}) |     res.status(500).send({ | ||||||
|  |       message: "An unexpected error occured while requesting movie info" | ||||||
|  |     }); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -27,31 +27,44 @@ async function movieInfoController(req, res) { | |||||||
|   const movieId = req.params.id; |   const movieId = req.params.id; | ||||||
|   let { credits, release_dates, check_existance } = req.query; |   let { credits, release_dates, check_existance } = req.query; | ||||||
|  |  | ||||||
|   credits && credits.toLowerCase() === 'true' ? credits = true : credits = false |   credits && credits.toLowerCase() === "true" | ||||||
|   release_dates && release_dates.toLowerCase() === 'true' ? release_dates = true : release_dates = false |     ? (credits = true) | ||||||
|   check_existance && check_existance.toLowerCase() === 'true' ? check_existance = true : check_existance = false |     : (credits = false); | ||||||
|  |   release_dates && release_dates.toLowerCase() === "true" | ||||||
|  |     ? (release_dates = true) | ||||||
|  |     : (release_dates = false); | ||||||
|  |   check_existance && check_existance.toLowerCase() === "true" | ||||||
|  |     ? (check_existance = true) | ||||||
|  |     : (check_existance = false); | ||||||
|  |  | ||||||
|   let tmdbQueue = [tmdb.movieInfo(movieId)] |   let tmdbQueue = [tmdb.movieInfo(movieId)]; | ||||||
|   if (credits) |   if (credits) tmdbQueue.push(tmdb.movieCredits(movieId)); | ||||||
|     tmdbQueue.push(tmdb.movieCredits(movieId)) |   if (release_dates) tmdbQueue.push(tmdb.movieReleaseDates(movieId)); | ||||||
|   if (release_dates) |  | ||||||
|     tmdbQueue.push(tmdb.movieReleaseDates(movieId)) |  | ||||||
|  |  | ||||||
|   try { |   try { | ||||||
|     const [ Movie, Credits, ReleaseDates ] = await Promise.all(tmdbQueue) |     const [Movie, Credits, ReleaseDates] = await Promise.all(tmdbQueue); | ||||||
|  |  | ||||||
|     const movie = Movie.createJsonResponse() |     const movie = Movie.createJsonResponse(); | ||||||
|     if (Credits) |     if (Credits) movie.credits = Credits.createJsonResponse(); | ||||||
|       movie.credits = Credits.createJsonResponse() |  | ||||||
|     if (ReleaseDates) |     if (ReleaseDates) | ||||||
|       movie.release_dates = ReleaseDates.createJsonResponse().results |       movie.release_dates = ReleaseDates.createJsonResponse().results; | ||||||
|  |  | ||||||
|     if (check_existance) |     if (check_existance) { | ||||||
|       movie.exists_in_plex = await plex.existsInPlex(movie) |       try { | ||||||
|    |         movie.exists_in_plex = await plex.existsInPlex(movie); | ||||||
|     res.send(movie) |  | ||||||
|       } catch (error) { |       } catch (error) { | ||||||
|     handleError(error, res) |         if (error.status === 401) { | ||||||
|  |           console.log("Unathorized request, check plex server LAN settings"); | ||||||
|  |         } else { | ||||||
|  |           console.log("Unkown error from plex!"); | ||||||
|  |         } | ||||||
|  |         console.log(error); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     res.send(movie); | ||||||
|  |   } catch (error) { | ||||||
|  |     handleError(error, res); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,9 +1,7 @@ | |||||||
| const configuration = require('src/config/configuration').getInstance(); | const configuration = require('src/config/configuration').getInstance(); | ||||||
| const Cache = require('src/tmdb/cache'); |  | ||||||
| const TMDB = require('src/tmdb/tmdb'); | const TMDB = require('src/tmdb/tmdb'); | ||||||
|  |  | ||||||
| const cache = new Cache(); | const tmdb = new TMDB(configuration.get('tmdb', 'apiKey')); | ||||||
| const tmdb = new TMDB(cache, configuration.get('tmdb', 'apiKey')); |  | ||||||
|  |  | ||||||
| const movieReleaseDatesController = (req, res) => { | const movieReleaseDatesController = (req, res) => { | ||||||
|   const movieId = req.params.id; |   const movieId = req.params.id; | ||||||
|   | |||||||
							
								
								
									
										26
									
								
								seasoned_api/src/webserver/controllers/person/credits.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								seasoned_api/src/webserver/controllers/person/credits.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | |||||||
|  | const configuration = require("src/config/configuration").getInstance(); | ||||||
|  | const TMDB = require("src/tmdb/tmdb"); | ||||||
|  | const tmdb = new TMDB(configuration.get("tmdb", "apiKey")); | ||||||
|  |  | ||||||
|  | const personCreditsController = (req, res) => { | ||||||
|  |   const personId = req.params.id; | ||||||
|  |  | ||||||
|  |   return tmdb | ||||||
|  |     .personCredits(personId) | ||||||
|  |     .then(credits => res.send(credits)) | ||||||
|  |     .catch(error => { | ||||||
|  |       const { status, message } = error; | ||||||
|  |  | ||||||
|  |       if (status && message) { | ||||||
|  |         res.status(status).send({ success: false, message }); | ||||||
|  |       } else { | ||||||
|  |         // TODO log unhandled errors | ||||||
|  |         console.log("caugth show credits controller error", error); | ||||||
|  |         res.status(500).send({ | ||||||
|  |           message: "An unexpected error occured while requesting person credits" | ||||||
|  |         }); | ||||||
|  |       } | ||||||
|  |     }); | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | module.exports = personCreditsController; | ||||||
| @@ -1,8 +1,19 @@ | |||||||
| const configuration = require('src/config/configuration').getInstance(); | const configuration = require("src/config/configuration").getInstance(); | ||||||
| const Cache = require('src/tmdb/cache'); | const TMDB = require("src/tmdb/tmdb"); | ||||||
| const TMDB = require('src/tmdb/tmdb'); | const tmdb = new TMDB(configuration.get("tmdb", "apiKey")); | ||||||
| const cache = new Cache(); |  | ||||||
| const tmdb = new TMDB(cache, 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 | ||||||
| @@ -11,15 +22,28 @@ const tmdb = new TMDB(cache, configuration.get('tmdb', 'apiKey')); | |||||||
|  * @returns {Callback} |  * @returns {Callback} | ||||||
|  */ |  */ | ||||||
|  |  | ||||||
| function personInfoController(req, res) { | async function personInfoController(req, res) { | ||||||
|   const personId = req.params.id; |   const personId = req.params.id; | ||||||
|  |   let { credits } = req.query; | ||||||
|  |   arguments; | ||||||
|  |  | ||||||
|  |   credits && credits.toLowerCase() === "true" | ||||||
|  |     ? (credits = true) | ||||||
|  |     : (credits = false); | ||||||
|  |  | ||||||
|   tmdb.personInfo(personId) |   let tmdbQueue = [tmdb.personInfo(personId)]; | ||||||
|   .then(person => res.send(person.createJsonResponse())) |   if (credits) tmdbQueue.push(tmdb.personCredits(personId)); | ||||||
|   .catch(error => { |  | ||||||
|     res.status(404).send({ success: false, message: error.message }); |   try { | ||||||
|   }); |     const [Person, Credits] = await Promise.all(tmdbQueue); | ||||||
|  |  | ||||||
|  |     const person = Person.createJsonResponse(); | ||||||
|  |     if (credits) person.credits = Credits.createJsonResponse(); | ||||||
|  |  | ||||||
|  |     return res.send(person); | ||||||
|  |   } catch (error) { | ||||||
|  |     handleError(error, res); | ||||||
|  |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| module.exports = personInfoController; | module.exports = personInfoController; | ||||||
|   | |||||||
| @@ -5,7 +5,7 @@ | |||||||
|  * @Last Modified time: 2018-02-26 19:56:32 |  * @Last Modified time: 2018-02-26 19:56:32 | ||||||
|  */ |  */ | ||||||
|  |  | ||||||
| const PirateRepository = require('src/pirate/pirateRepository'); | const PirateRepository = require("src/pirate/pirateRepository"); | ||||||
| // const pirateRepository = new PirateRepository(); | // const pirateRepository = new PirateRepository(); | ||||||
|  |  | ||||||
| /** | /** | ||||||
| @@ -18,7 +18,7 @@ function updateRequested(req, res) { | |||||||
|   const { query, page, type } = req.query; |   const { query, page, type } = req.query; | ||||||
|  |  | ||||||
|   PirateRepository.SearchPiratebay(query, page, type) |   PirateRepository.SearchPiratebay(query, page, type) | ||||||
|       .then((result) => { |     .then(result => { | ||||||
|       res.send({ success: true, results: result }); |       res.send({ success: true, results: result }); | ||||||
|     }) |     }) | ||||||
|     .catch(error => { |     .catch(error => { | ||||||
|   | |||||||
| @@ -1,21 +1,19 @@ | |||||||
| const SearchHistory = require('src/searchHistory/searchHistory'); | const SearchHistory = require("src/searchHistory/searchHistory"); | ||||||
| const Cache = require('src/tmdb/cache'); | const Cache = require("src/tmdb/cache"); | ||||||
| const RequestRepository = require('src/plex/requestRepository.js'); | const RequestRepository = require("src/plex/requestRepository.js"); | ||||||
|  |  | ||||||
| const cache = new Cache(); | const cache = new Cache(); | ||||||
| const requestRepository = new RequestRepository(cache); | const requestRepository = new RequestRepository(cache); | ||||||
| const searchHistory = new SearchHistory(); | const searchHistory = new SearchHistory(); | ||||||
|  |  | ||||||
|  |  | ||||||
| function searchRequestController(req, res) { | function searchRequestController(req, res) { | ||||||
|    const user = req.loggedInUser; |  | ||||||
|   const { query, page, type } = req.query; |   const { query, page, type } = req.query; | ||||||
|    const username = user === undefined ? undefined : user.username; |   const username = req.loggedInUser ? req.loggedInUser.username : null; | ||||||
|  |  | ||||||
|   Promise.resolve() |   Promise.resolve() | ||||||
|     .then(() => searchHistory.create(username, query)) |     .then(() => searchHistory.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); | ||||||
|     }) |     }) | ||||||
|     .catch(error => { |     .catch(error => { | ||||||
|   | |||||||
| @@ -1,19 +1,16 @@ | |||||||
| const configuration = require('src/config/configuration').getInstance() | const configuration = require("src/config/configuration").getInstance(); | ||||||
| const RequestRepository = require('src/request/request'); | const RequestRepository = require("src/request/request"); | ||||||
| const Cache = require('src/tmdb/cache') | const TMDB = require("src/tmdb/tmdb"); | ||||||
| const TMDB = require('src/tmdb/tmdb') | const tmdb = new TMDB(configuration.get("tmdb", "apiKey")); | ||||||
|  | const request = new RequestRepository(); | ||||||
|  |  | ||||||
| const cache = new Cache() | const tmdbMovieInfo = id => { | ||||||
| const tmdb = new TMDB(cache, configuration.get('tmdb', 'apiKey')) |   return tmdb.movieInfo(id); | ||||||
| const request = new RequestRepository() | }; | ||||||
|  |  | ||||||
| const tmdbMovieInfo = (id) => { | const tmdbShowInfo = id => { | ||||||
|   return tmdb.movieInfo(id) |   return tmdb.showInfo(id); | ||||||
| } | }; | ||||||
|  |  | ||||||
| const tmdbShowInfo = (id) => { |  | ||||||
|   return tmdb.showInfo(id) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * Controller: POST a media id to be donwloaded |  * Controller: POST a media id to be donwloaded | ||||||
| @@ -24,28 +21,43 @@ const tmdbShowInfo = (id) => { | |||||||
| function submitRequestController(req, res) { | function submitRequestController(req, res) { | ||||||
|   // This is the id that is the param of the url |   // This is the id that is the param of the url | ||||||
|   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 user_agent = req.headers["user-agent"]; | ||||||
|   const user = req.loggedInUser; |   const username = req.loggedInUser ? req.loggedInUser.username : null; | ||||||
|   let mediaFunction = undefined |  | ||||||
|  |  | ||||||
|   if (type === 'movie') { |   let mediaFunction = undefined; | ||||||
|     console.log('movie') |  | ||||||
|     mediaFunction = tmdbMovieInfo |   if (type === "movie") { | ||||||
|   } else if (type === 'show') { |     console.log("movie"); | ||||||
|     console.log('show') |     mediaFunction = tmdbMovieInfo; | ||||||
|     mediaFunction = tmdbShowInfo |   } else if (type === "show") { | ||||||
|  |     console.log("show"); | ||||||
|  |     mediaFunction = tmdbShowInfo; | ||||||
|   } else { |   } else { | ||||||
|     res.status(422).send({ success: false, message: 'Incorrect type. Allowed types: "movie" or "show"'}) |     res | ||||||
|  |       .status(422) | ||||||
|  |       .send({ | ||||||
|  |         success: false, | ||||||
|  |         message: 'Incorrect type. Allowed types: "movie" or "show"' | ||||||
|  |       }); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   if (mediaFunction === undefined) { res.status(200); return } |   if (mediaFunction === undefined) { | ||||||
|  |     res.status(200); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |  | ||||||
|   mediaFunction(id) |   mediaFunction(id) | ||||||
|     .then(tmdbMedia => request.requestFromTmdb(tmdbMedia, ip, user_agent, user)) |     .then(tmdbMedia => | ||||||
|     .then(() => res.send({ success: true, message: 'Media item successfully requested' })) |       request.requestFromTmdb(tmdbMedia, ip, user_agent, username) | ||||||
|     .catch(err => res.status(500).send({ success: false, message: err.message })) |     ) | ||||||
|  |     .then(() => | ||||||
|  |       res.send({ success: true, message: "Media item successfully requested" }) | ||||||
|  |     ) | ||||||
|  |     .catch(err => | ||||||
|  |       res.status(500).send({ success: false, message: err.message }) | ||||||
|  |     ); | ||||||
| } | } | ||||||
|  |  | ||||||
| module.exports = submitRequestController; | module.exports = submitRequestController; | ||||||
|   | |||||||
| @@ -0,0 +1,27 @@ | |||||||
|  | const configuration = require('src/config/configuration').getInstance(); | ||||||
|  | const Plex = require('src/plex/plex'); | ||||||
|  | const plex = new Plex(configuration.get('plex', 'ip')); | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Controller: Search plex for movies, shows and episodes by query | ||||||
|  |  * @param {Request} req http request variable | ||||||
|  |  * @param {Response} res | ||||||
|  |  * @returns {Callback} | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | function watchDirectLink (req, res) { | ||||||
|  |    const { title, year } = req.query; | ||||||
|  |  | ||||||
|  |   plex.getDirectLinkByTitleAndYear(title, year) | ||||||
|  |     .then(plexDirectLink => { | ||||||
|  |       if (plexDirectLink == false) | ||||||
|  |         res.status(404).send({ success: true, link: null }) | ||||||
|  |       else | ||||||
|  |         res.status(200).send({ success: true, link: plexDirectLink }) | ||||||
|  |     }) | ||||||
|  |     .catch(error => { | ||||||
|  |        res.status(500).send({ success: false, message: error.message }); | ||||||
|  |     }); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | module.exports = watchDirectLink; | ||||||
| @@ -1,18 +1,17 @@ | |||||||
| const configuration = require('src/config/configuration').getInstance(); | const configuration = require("src/config/configuration").getInstance(); | ||||||
| const Cache = require('src/tmdb/cache'); | const TMDB = require("src/tmdb/tmdb"); | ||||||
| const TMDB = require('src/tmdb/tmdb'); | const RequestRepository = require("src/request/request"); | ||||||
| const RequestRepository = require('src/request/request'); | const tmdb = new TMDB(configuration.get("tmdb", "apiKey")); | ||||||
| const cache = new Cache(); |  | ||||||
| const tmdb = new TMDB(cache, configuration.get('tmdb', 'apiKey')); |  | ||||||
| const request = new RequestRepository(); | const request = new RequestRepository(); | ||||||
|  | // const { sendSMS } = require("src/notifications/sms"); | ||||||
|  |  | ||||||
| const tmdbMovieInfo = (id) => { | const tmdbMovieInfo = id => { | ||||||
|   return tmdb.movieInfo(id) |   return tmdb.movieInfo(id); | ||||||
| } | }; | ||||||
|  |  | ||||||
| const tmdbShowInfo = (id) => { | const tmdbShowInfo = id => { | ||||||
|   return tmdb.showInfo(id) |   return tmdb.showInfo(id); | ||||||
| } | }; | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * Controller: Request by id with type param |  * Controller: Request by id with type param | ||||||
| @@ -21,33 +20,48 @@ const tmdbShowInfo = (id) => { | |||||||
|  * @returns {Callback} |  * @returns {Callback} | ||||||
|  */ |  */ | ||||||
| function requestTmdbIdController(req, res) { | 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 user_agent = req.headers["user-agent"]; | ||||||
|   const user = req.loggedInUser; |   const username = req.loggedInUser ? req.loggedInUser.username : null; | ||||||
|  |  | ||||||
|   let mediaFunction = undefined |   let mediaFunction = undefined; | ||||||
|  |  | ||||||
|   if (id === undefined || type === undefined) { |   if (id === undefined || type === undefined) { | ||||||
|     res.status(422).send({ success: false, message: "'Missing parameteres: 'id' and/or 'type'"}) |     res.status(422).send({ | ||||||
|  |       success: false, | ||||||
|  |       message: "'Missing parameteres: 'id' and/or 'type'" | ||||||
|  |     }); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   if (type === 'movie') { |   if (type === "movie") { | ||||||
|     mediaFunction = tmdbMovieInfo |     mediaFunction = tmdbMovieInfo; | ||||||
|   } else if (type === 'show') { |   } else if (type === "show") { | ||||||
|     mediaFunction = tmdbShowInfo |     mediaFunction = tmdbShowInfo; | ||||||
|   } else { |   } else { | ||||||
|     res.status(422).send({ success: false, message: 'Incorrect type. Allowed types: "movie" or "show"'}) |     res.status(422).send({ | ||||||
|  |       success: false, | ||||||
|  |       message: 'Incorrect type. Allowed types: "movie" or "show"' | ||||||
|  |     }); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   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 => request.requestFromTmdb(tmdbMedia, ip, user_agent, user)) |     .then(tmdbMedia => { | ||||||
|     .then(() => res.send({success: true, message: 'Request has been submitted.'})) |       request.requestFromTmdb(tmdbMedia, ip, user_agent, username); | ||||||
|  |  | ||||||
|  |       // TODO enable SMS | ||||||
|  |       // const url = `https://request.movie?${tmdbMedia.type}=${tmdbMedia.id}`; | ||||||
|  |       // const message = `${tmdbMedia.title} (${tmdbMedia.year}) requested!\n${url}`; | ||||||
|  |       // sendSMS(message); | ||||||
|  |     }) | ||||||
|  |     .then(() => | ||||||
|  |       res.send({ success: true, message: "Request has been submitted." }) | ||||||
|  |     ) | ||||||
|     .catch(error => { |     .catch(error => { | ||||||
|       res.send({ success: false, message: error.message }); |       res.send({ success: false, message: error.message }); | ||||||
|     }) |     }); | ||||||
| } | } | ||||||
|  |  | ||||||
| module.exports = requestTmdbIdController; | module.exports = requestTmdbIdController; | ||||||
|   | |||||||
| @@ -1,9 +1,7 @@ | |||||||
| const configuration = require('src/config/configuration').getInstance(); | const configuration = require("src/config/configuration").getInstance(); | ||||||
| const Cache = require('src/tmdb/cache'); | const TMDB = require("src/tmdb/tmdb"); | ||||||
| const TMDB = require('src/tmdb/tmdb'); | const SearchHistory = require("src/searchHistory/searchHistory"); | ||||||
| const SearchHistory = require('src/searchHistory/searchHistory'); | const tmdb = new TMDB(configuration.get("tmdb", "apiKey")); | ||||||
| const cache = new Cache(); |  | ||||||
| const tmdb = new TMDB(cache, configuration.get('tmdb', 'apiKey')); |  | ||||||
| const searchHistory = new SearchHistory(); | const searchHistory = new SearchHistory(); | ||||||
|  |  | ||||||
| /** | /** | ||||||
| @@ -13,28 +11,30 @@ const searchHistory = new SearchHistory(); | |||||||
|  * @returns {Callback} |  * @returns {Callback} | ||||||
|  */ |  */ | ||||||
| function movieSearchController(req, res) { | function movieSearchController(req, res) { | ||||||
|   const user = req.loggedInUser; |   const { query, page, adult } = req.query; | ||||||
|   const { query, page } = req.query; |   const username = req.loggedInUser ? req.loggedInUser.username : null; | ||||||
|  |   const includeAdult = adult == "true" ? true : false; | ||||||
|  |  | ||||||
|   if (user) { |   if (username) { | ||||||
|     return searchHistory.create(user, query); |     searchHistory.create(username, query); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   tmdb.movieSearch(query, page) |   return tmdb | ||||||
|  |     .movieSearch(query, page, includeAdult) | ||||||
|     .then(movieSearchResults => res.send(movieSearchResults)) |     .then(movieSearchResults => res.send(movieSearchResults)) | ||||||
|     .catch(error => { |     .catch(error => { | ||||||
|       const { status, message } = error; |       const { status, message } = error; | ||||||
|  |  | ||||||
|       if (status && message) { |       if (status && message) { | ||||||
|         res.status(status).send({ success: false, message }) |         res.status(status).send({ success: false, message }); | ||||||
|       } else { |       } else { | ||||||
|         // TODO log unhandled errors |         // TODO log unhandled errors | ||||||
|         console.log('caugth movie search controller error', error) |         console.log("caugth movie search controller error", error); | ||||||
|         res.status(500).send({ |         res.status(500).send({ | ||||||
|           message: `An unexpected error occured while searching movies with query: ${query}` |           message: `An unexpected error occured while searching movies with query: ${query}` | ||||||
|         }) |         }); | ||||||
|       } |       } | ||||||
|     }) |     }); | ||||||
| } | } | ||||||
|  |  | ||||||
| module.exports = movieSearchController; | module.exports = movieSearchController; | ||||||
|   | |||||||
| @@ -1,16 +1,14 @@ | |||||||
| const configuration = require('src/config/configuration').getInstance(); | const configuration = require("src/config/configuration").getInstance(); | ||||||
| const Cache = require('src/tmdb/cache'); | const TMDB = require("src/tmdb/tmdb"); | ||||||
| const TMDB = require('src/tmdb/tmdb'); | const SearchHistory = require("src/searchHistory/searchHistory"); | ||||||
| const SearchHistory = require('src/searchHistory/searchHistory'); | const tmdb = new TMDB(configuration.get("tmdb", "apiKey")); | ||||||
| const cache = new Cache(); |  | ||||||
| const tmdb = new TMDB(cache, configuration.get('tmdb', 'apiKey')); |  | ||||||
| const searchHistory = new SearchHistory(); | const searchHistory = new SearchHistory(); | ||||||
|  |  | ||||||
| function checkAndCreateJsonResponse(result) { | function checkAndCreateJsonResponse(result) { | ||||||
|   if (typeof result['createJsonResponse'] === 'function') { |   if (typeof result["createJsonResponse"] === "function") { | ||||||
|     return result.createJsonResponse() |     return result.createJsonResponse(); | ||||||
|   } |   } | ||||||
|   return result |   return result; | ||||||
| } | } | ||||||
|  |  | ||||||
| /** | /** | ||||||
| @@ -20,26 +18,31 @@ function checkAndCreateJsonResponse(result) { | |||||||
|  * @returns {Callback} |  * @returns {Callback} | ||||||
|  */ |  */ | ||||||
| function multiSearchController(req, res) { | function multiSearchController(req, res) { | ||||||
|   const user = req.loggedInUser; |   const { query, page, adult } = req.query; | ||||||
|   const { query, page } = req.query; |   const username = req.loggedInUser ? req.loggedInUser.username : null; | ||||||
|  |  | ||||||
|   if (user) { |   if (username) { | ||||||
|     searchHistory.create(user.username, query) |     searchHistory.create(username, query); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   return tmdb.multiSearch(query, page) |   return tmdb | ||||||
|  |     .multiSearch(query, page, adult) | ||||||
|     .then(multiSearchResults => res.send(multiSearchResults)) |     .then(multiSearchResults => res.send(multiSearchResults)) | ||||||
|     .catch(error => { |     .catch(error => { | ||||||
|       const { status, message } = error; |       const { status, message } = error; | ||||||
|  |  | ||||||
|       if (status && message) { |       if (status && message) { | ||||||
|       res.status(status).send({ success: false, message }) |         res.status(status).send({ success: false, message }); | ||||||
|       } else { |       } else { | ||||||
|         // TODO log unhandled errors |         // TODO log unhandled errors | ||||||
|       console.log('caugth multi search controller error', error) |         console.log("caugth multi search controller error", error); | ||||||
|       res.status(500).send({ message: `An unexpected error occured while searching with query: ${query}` }) |         res | ||||||
|  |           .status(500) | ||||||
|  |           .send({ | ||||||
|  |             message: `An unexpected error occured while searching with query: ${query}` | ||||||
|  |           }); | ||||||
|       } |       } | ||||||
|   }) |     }); | ||||||
| } | } | ||||||
|  |  | ||||||
| module.exports = multiSearchController; | module.exports = multiSearchController; | ||||||
|   | |||||||
| @@ -1,9 +1,7 @@ | |||||||
| const configuration = require('src/config/configuration').getInstance(); | const configuration = require("src/config/configuration").getInstance(); | ||||||
| const Cache = require('src/tmdb/cache'); | const TMDB = require("src/tmdb/tmdb"); | ||||||
| const TMDB = require('src/tmdb/tmdb'); | const SearchHistory = require("src/searchHistory/searchHistory"); | ||||||
| const SearchHistory = require('src/searchHistory/searchHistory'); | const tmdb = new TMDB(configuration.get("tmdb", "apiKey")); | ||||||
| const cache = new Cache(); |  | ||||||
| const tmdb = new TMDB(cache, configuration.get('tmdb', 'apiKey')); |  | ||||||
| const searchHistory = new SearchHistory(); | const searchHistory = new SearchHistory(); | ||||||
|  |  | ||||||
| /** | /** | ||||||
| @@ -13,30 +11,30 @@ const searchHistory = new SearchHistory(); | |||||||
|  * @returns {Callback} |  * @returns {Callback} | ||||||
|  */ |  */ | ||||||
| function personSearchController(req, res) { | function personSearchController(req, res) { | ||||||
|   const user = req.loggedInUser; |   const { query, page, adult } = req.query; | ||||||
|   const { query, page } = req.query; |   const username = req.loggedInUser ? req.loggedInUser.username : null; | ||||||
|  |   const includeAdult = adult == "true" ? true : false; | ||||||
|  |  | ||||||
|   if (user) { |   if (username) { | ||||||
|     return searchHistory.create(user, query); |     searchHistory.create(username, query); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   tmdb.personSearch(query, page) |   return tmdb | ||||||
|     .then((person) => { |     .personSearch(query, page, includeAdult) | ||||||
|       res.send(person); |     .then(persons => res.send(persons)) | ||||||
|     }) |  | ||||||
|     .catch(error => { |     .catch(error => { | ||||||
|       const { status, message } = error; |       const { status, message } = error; | ||||||
|  |  | ||||||
|       if (status && message) { |       if (status && message) { | ||||||
|         res.status(status).send({ success: false, message }) |         res.status(status).send({ success: false, message }); | ||||||
|       } else { |       } else { | ||||||
|         // TODO log unhandled errors |         // TODO log unhandled errors | ||||||
|         console.log('caugth person search controller error', error) |         console.log("caugth person search controller error", error); | ||||||
|         res.status(500).send({ |         res.status(500).send({ | ||||||
|           message: `An unexpected error occured while searching people with query: ${query}` |           message: `An unexpected error occured while searching people with query: ${query}` | ||||||
|         }) |         }); | ||||||
|       } |       } | ||||||
|     }) |     }); | ||||||
| } | } | ||||||
|  |  | ||||||
| module.exports = personSearchController; | module.exports = personSearchController; | ||||||
|   | |||||||
| @@ -1,9 +1,7 @@ | |||||||
| const SearchHistory = require('src/searchHistory/searchHistory'); | const SearchHistory = require("src/searchHistory/searchHistory"); | ||||||
| const configuration = require('src/config/configuration').getInstance(); | const configuration = require("src/config/configuration").getInstance(); | ||||||
| const Cache = require('src/tmdb/cache'); | const TMDB = require("src/tmdb/tmdb"); | ||||||
| const TMDB = require('src/tmdb/tmdb'); | const tmdb = new TMDB(configuration.get("tmdb", "apiKey")); | ||||||
| const cache = new Cache(); |  | ||||||
| const tmdb = new TMDB(cache, configuration.get('tmdb', 'apiKey')); |  | ||||||
| const searchHistory = new SearchHistory(); | const searchHistory = new SearchHistory(); | ||||||
|  |  | ||||||
| /** | /** | ||||||
| @@ -13,18 +11,17 @@ const searchHistory = new SearchHistory(); | |||||||
|  * @returns {Callback} |  * @returns {Callback} | ||||||
|  */ |  */ | ||||||
| function showSearchController(req, res) { | function showSearchController(req, res) { | ||||||
|   const user = req.loggedInUser; |   const { query, page, adult } = req.query; | ||||||
|   const { query, page } = req.query; |   const username = req.loggedInUser ? req.loggedInUser.username : null; | ||||||
|  |   const includeAdult = adult == "true" ? true : false; | ||||||
|  |  | ||||||
|   Promise.resolve() |   if (username) { | ||||||
|   .then(() => { |     searchHistory.create(username, query); | ||||||
|     if (user) { |  | ||||||
|       return searchHistory.create(user, query); |  | ||||||
|   } |   } | ||||||
|     return null |  | ||||||
|   }) |   return tmdb | ||||||
|   .then(() => tmdb.showSearch(query, page)) |     .showSearch(query, page, includeAdult) | ||||||
|   .then((shows) => { |     .then(shows => { | ||||||
|       res.send(shows); |       res.send(shows); | ||||||
|     }) |     }) | ||||||
|     .catch(error => { |     .catch(error => { | ||||||
|   | |||||||
| @@ -1,9 +1,6 @@ | |||||||
| const configuration = require('src/config/configuration').getInstance(); | const configuration = require('src/config/configuration').getInstance(); | ||||||
| const Cache = require('src/tmdb/cache'); |  | ||||||
| const TMDB = require('src/tmdb/tmdb'); | const TMDB = require('src/tmdb/tmdb'); | ||||||
|  | const tmdb = new TMDB(configuration.get('tmdb', 'apiKey')); | ||||||
| const cache = new Cache(); |  | ||||||
| const tmdb = new TMDB(cache, configuration.get('tmdb', 'apiKey')); |  | ||||||
|  |  | ||||||
| const showCreditsController = (req, res) => { | const showCreditsController = (req, res) => { | ||||||
|   const showId = req.params.id; |   const showId = req.params.id; | ||||||
|   | |||||||
| @@ -1,9 +1,7 @@ | |||||||
| const configuration = require('src/config/configuration').getInstance(); | const configuration = require('src/config/configuration').getInstance(); | ||||||
| const Cache = require('src/tmdb/cache'); |  | ||||||
| const TMDB = require('src/tmdb/tmdb'); | const TMDB = require('src/tmdb/tmdb'); | ||||||
| const Plex = require('src/plex/plex'); | const Plex = require('src/plex/plex'); | ||||||
| const cache = new Cache(); | const tmdb = new TMDB(configuration.get('tmdb', 'apiKey')); | ||||||
| const tmdb = new TMDB(cache, configuration.get('tmdb', 'apiKey')); |  | ||||||
| const plex = new Plex(configuration.get('plex', 'ip')); | const plex = new Plex(configuration.get('plex', 'ip')); | ||||||
|  |  | ||||||
| function handleError(error, res) { | function handleError(error, res) { | ||||||
|   | |||||||
| @@ -1,24 +1,25 @@ | |||||||
| const UserRepository = require('src/user/userRepository'); | const UserRepository = require("src/user/userRepository"); | ||||||
| const userRepository = new UserRepository(); | const userRepository = new UserRepository(); | ||||||
|  const fetch = require('node-fetch'); | const fetch = require("node-fetch"); | ||||||
|  const FormData = require('form-data'); | const FormData = require("form-data"); | ||||||
|  |  | ||||||
| function handleError(error, res) { | function handleError(error, res) { | ||||||
|   let { status, message, source } = error; |   let { status, 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 { | ||||||
|     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: 'An unexpected error occured while authenticating your account with plex', |       message: | ||||||
|  |         "An unexpected error occured while authenticating your account with plex", | ||||||
|       source |       source | ||||||
|     }) |     }); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -28,33 +29,32 @@ function handleResponse(response) { | |||||||
|       success: false, |       success: false, | ||||||
|       status: response.status, |       status: response.status, | ||||||
|       message: response.statusText |       message: response.statusText | ||||||
|     } |     }; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   return response.json() |   return response.json(); | ||||||
| } | } | ||||||
|  |  | ||||||
| function plexAuthenticate(username, password) { | function plexAuthenticate(username, password) { | ||||||
|   const url = 'https://plex.tv/api/v2/users/signin' |   const url = "https://plex.tv/api/v2/users/signin"; | ||||||
|  |  | ||||||
|   const form = new FormData() |   const form = new FormData(); | ||||||
|   form.append('login', username) |   form.append("login", username); | ||||||
|   form.append('password', password) |   form.append("password", password); | ||||||
|   form.append('rememberMe', 'false') |   form.append("rememberMe", "false"); | ||||||
|  |  | ||||||
|   const headers = { |   const headers = { | ||||||
|     'Accept': 'application/json, text/plain, */*', |     Accept: "application/json, text/plain, */*", | ||||||
|     'Content-Type': form.getHeaders()['content-type'], |     "Content-Type": form.getHeaders()["content-type"], | ||||||
|     'X-Plex-Client-Identifier': 'seasonedRequest' |     "X-Plex-Client-Identifier": "seasonedRequest" | ||||||
|   } |   }; | ||||||
|   const options = { |   const options = { | ||||||
|     method: 'POST', |     method: "POST", | ||||||
|     headers, |     headers, | ||||||
|     body: form |     body: form | ||||||
|   } |   }; | ||||||
|  |  | ||||||
|   return fetch(url, options) |   return fetch(url, options).then(resp => handleResponse(resp)); | ||||||
|     .then(resp => handleResponse(resp)) |  | ||||||
| } | } | ||||||
|  |  | ||||||
| function link(req, res) { | function link(req, res) { | ||||||
| @@ -63,22 +63,28 @@ 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 => res.send({ |     .then(response => | ||||||
|  |       res.send({ | ||||||
|         success: true, |         success: true, | ||||||
|       message: "Successfully authenticated and linked plex account with seasoned request." |         message: | ||||||
|     })) |           "Successfully authenticated and linked plex account with seasoned request." | ||||||
|     .catch(error => handleError(error, res)) |       }) | ||||||
|  |     ) | ||||||
|  |     .catch(error => handleError(error, res)); | ||||||
| } | } | ||||||
|  |  | ||||||
| function unlink(req, res) { | function unlink(req, res) { | ||||||
|   const user = req.loggedInUser; |   const username = req.loggedInUser ? req.loggedInUser.username : null; | ||||||
|  |  | ||||||
|   return userRepository.unlinkPlexUserId(user.username) |   return userRepository | ||||||
|     .then(response => res.send({ |     .unlinkPlexUserId(username) | ||||||
|  |     .then(response => | ||||||
|  |       res.send({ | ||||||
|         success: true, |         success: true, | ||||||
|         message: "Successfully unlinked plex account from seasoned request." |         message: "Successfully unlinked plex account from seasoned request." | ||||||
|     })) |       }) | ||||||
|     .catch(error => handleError(error, res)) |     ) | ||||||
|  |     .catch(error => handleError(error, res)); | ||||||
| } | } | ||||||
|  |  | ||||||
| module.exports = { | module.exports = { | ||||||
|   | |||||||
| @@ -1,36 +1,61 @@ | |||||||
| const User = require('src/user/user'); | const User = require("src/user/user"); | ||||||
| const Token = require('src/user/token'); | const Token = require("src/user/token"); | ||||||
| const UserSecurity = require('src/user/userSecurity'); | const UserSecurity = require("src/user/userSecurity"); | ||||||
| const UserRepository = require('src/user/userRepository'); | const UserRepository = require("src/user/userRepository"); | ||||||
| const configuration = require('src/config/configuration').getInstance(); | const configuration = require("src/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 userRepository = new UserRepository(); | ||||||
|  |  | ||||||
| // TODO look to move some of the token generation out of the reach of the final "catch-all" | // TODO look to move some of the token generation out of the reach of the final "catch-all" | ||||||
| // catch including the, maybe sensitive, error message. | // catch including the, maybe sensitive, error message. | ||||||
|  |  | ||||||
|  | const isProduction = process.env.NODE_ENV === "production"; | ||||||
|  | const cookieOptions = { | ||||||
|  |   httpOnly: false, | ||||||
|  |   secure: isProduction, | ||||||
|  |   maxAge: 90 * 24 * 3600000, // 90 days | ||||||
|  |   sameSite: isProduction ? "Strict" : "Lax" | ||||||
|  | }; | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * Controller: Log in a user provided correct credentials. |  * Controller: Log in a user provided correct credentials. | ||||||
|  * @param {Request} req http request variable |  * @param {Request} req http request variable | ||||||
|  * @param {Response} res |  * @param {Response} res | ||||||
|  * @returns {Callback} |  * @returns {Callback} | ||||||
|  */ |  */ | ||||||
| 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.password; | ||||||
|  |  | ||||||
|    userSecurity.login(user, password) |   try { | ||||||
|       .then(() => userRepository.checkAdmin(user)) |     const [loggedIn, isAdmin, settings] = await Promise.all([ | ||||||
|       .then(checkAdmin => { |       userSecurity.login(user, password), | ||||||
|          const isAdmin = checkAdmin === 1 ? true : false; |       userRepository.checkAdmin(user), | ||||||
|          const token = new Token(user, isAdmin).toString(secret); |       userRepository.getSettings(user.username) | ||||||
|          res.send({ success: true, token }); |     ]); | ||||||
|       }) |  | ||||||
|       .catch(error => { |     if (!loggedIn) { | ||||||
|          res.status(401).send({ success: false, message: error.message }); |       return res.status(503).send({ | ||||||
|  |         success: false, | ||||||
|  |         message: "Unexpected error! Unable to create user." | ||||||
|       }); |       }); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     const token = new Token( | ||||||
|  |       user, | ||||||
|  |       isAdmin === 1 ? true : false, | ||||||
|  |       settings | ||||||
|  |     ).toString(secret); | ||||||
|  |  | ||||||
|  |     return res.cookie("authorization", token, cookieOptions).status(200).send({ | ||||||
|  |       success: true, | ||||||
|  |       message: "Welcome to request.movie!" | ||||||
|  |     }); | ||||||
|  |   } catch (error) { | ||||||
|  |     return res.status(401).send({ success: false, message: error.message }); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
| module.exports = loginController; | module.exports = loginController; | ||||||
|   | |||||||
							
								
								
									
										16
									
								
								seasoned_api/src/webserver/controllers/user/logout.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								seasoned_api/src/webserver/controllers/user/logout.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,16 @@ | |||||||
|  | /** | ||||||
|  |  * Controller: Log out a user (destroy authorization token) | ||||||
|  |  * @param {Request} req http request variable | ||||||
|  |  * @param {Response} res | ||||||
|  |  * @returns {Callback} | ||||||
|  |  */ | ||||||
|  | async function logoutController(req, res) { | ||||||
|  |   res.clearCookie("authorization"); | ||||||
|  |  | ||||||
|  |   return res.status(200).send({ | ||||||
|  |     success: true, | ||||||
|  |     message: "Logged out, see you later!" | ||||||
|  |   }); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | module.exports = logoutController; | ||||||
| @@ -1,13 +1,21 @@ | |||||||
| const User = require('src/user/user'); | const User = require("src/user/user"); | ||||||
| const Token = require('src/user/token'); | const Token = require("src/user/token"); | ||||||
| const UserSecurity = require('src/user/userSecurity'); | const UserSecurity = require("src/user/userSecurity"); | ||||||
| const UserRepository = require('src/user/userRepository'); | const UserRepository = require("src/user/userRepository"); | ||||||
| const configuration = require('src/config/configuration').getInstance(); | const configuration = require("src/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 userRepository = new UserRepository(); | ||||||
|  |  | ||||||
|  | const isProduction = process.env.NODE_ENV === "production"; | ||||||
|  | const cookieOptions = { | ||||||
|  |   httpOnly: false, | ||||||
|  |   secure: isProduction, | ||||||
|  |   maxAge: 90 * 24 * 3600000, // 90 days | ||||||
|  |   sameSite: isProduction ? "Strict" : "Lax" | ||||||
|  | }; | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * Controller: Register a new user |  * Controller: Register a new user | ||||||
|  * @param {Request} req http request variable |  * @param {Request} req http request variable | ||||||
| @@ -18,13 +26,17 @@ 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.password; | ||||||
|  |  | ||||||
|    userSecurity.createNewUser(user, password) |   userSecurity | ||||||
|       .then(() => userRepository.checkAdmin(user)) |     .createNewUser(user, password) | ||||||
|       .then(checkAdmin => { |     .then(() => { | ||||||
|          const isAdmin = checkAdmin === 1 ? true : false; |       const token = new Token(user, false).toString(secret); | ||||||
|          const token = new Token(user, isAdmin).toString(secret); |  | ||||||
|          res.send({ |       return res | ||||||
|             success: true, message: 'Welcome to Seasoned!', token |         .cookie("authorization", token, cookieOptions) | ||||||
|  |         .status(200) | ||||||
|  |         .send({ | ||||||
|  |           success: true, | ||||||
|  |           message: "Welcome to Seasoned!" | ||||||
|         }); |         }); | ||||||
|     }) |     }) | ||||||
|     .catch(error => { |     .catch(error => { | ||||||
|   | |||||||
| @@ -1,4 +1,4 @@ | |||||||
| const RequestRepository = require('src/plex/requestRepository.js'); | const RequestRepository = require("src/plex/requestRepository.js"); | ||||||
|  |  | ||||||
| const requestRepository = new RequestRepository(); | const requestRepository = new RequestRepository(); | ||||||
|  |  | ||||||
| @@ -9,11 +9,16 @@ const requestRepository = new RequestRepository(); | |||||||
|  * @returns {Callback} |  * @returns {Callback} | ||||||
|  */ |  */ | ||||||
| function requestsController(req, res) { | function requestsController(req, res) { | ||||||
|    const user = req.loggedInUser; |   const username = req.loggedInUser ? req.loggedInUser.username : null; | ||||||
|  |  | ||||||
|    requestRepository.userRequests(user) |   requestRepository | ||||||
|  |     .userRequests(username) | ||||||
|     .then(requests => { |     .then(requests => { | ||||||
|          res.send({ success: true, results: requests, total_results: requests.length }); |       res.send({ | ||||||
|  |         success: true, | ||||||
|  |         results: requests, | ||||||
|  |         total_results: requests.length | ||||||
|  |       }); | ||||||
|     }) |     }) | ||||||
|     .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 SearchHistory = require('src/searchHistory/searchHistory'); | const SearchHistory = require("src/searchHistory/searchHistory"); | ||||||
|  |  | ||||||
| const searchHistory = new SearchHistory(); | const searchHistory = new SearchHistory(); | ||||||
|  |  | ||||||
| @@ -9,10 +9,10 @@ const searchHistory = new SearchHistory(); | |||||||
|  * @returns {Callback} |  * @returns {Callback} | ||||||
|  */ |  */ | ||||||
| function historyController(req, res) { | function historyController(req, res) { | ||||||
|    const user = req.loggedInUser; |   const username = req.loggedInUser ? req.loggedInUser.username : null; | ||||||
|    const username = user === undefined ? undefined : user.username; |  | ||||||
|  |  | ||||||
|    searchHistory.read(username) |   searchHistory | ||||||
|  |     .read(username) | ||||||
|     .then(searchQueries => { |     .then(searchQueries => { | ||||||
|       res.send({ success: true, searchQueries }); |       res.send({ success: true, searchQueries }); | ||||||
|     }) |     }) | ||||||
|   | |||||||
| @@ -1,4 +1,4 @@ | |||||||
| const UserRepository = require('src/user/userRepository'); | const UserRepository = require("src/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 | ||||||
| @@ -7,36 +7,35 @@ const userRepository = new UserRepository(); | |||||||
|  * @returns {Callback} |  * @returns {Callback} | ||||||
|  */ |  */ | ||||||
| const getSettingsController = (req, res) => { | const getSettingsController = (req, res) => { | ||||||
|    const user = req.loggedInUser; |   const username = req.loggedInUser ? req.loggedInUser.username : null; | ||||||
|    const username = user === undefined ? undefined : user.username; |  | ||||||
|  |  | ||||||
|    userRepository.getSettings(username) |   userRepository | ||||||
|  |     .getSettings(username) | ||||||
|     .then(settings => { |     .then(settings => { | ||||||
|       res.send({ success: true, settings }); |       res.send({ success: true, settings }); | ||||||
|     }) |     }) | ||||||
|     .catch(error => { |     .catch(error => { | ||||||
|       res.status(404).send({ success: false, message: error.message }); |       res.status(404).send({ success: false, message: error.message }); | ||||||
|     }); |     }); | ||||||
| } | }; | ||||||
|  |  | ||||||
|  |  | ||||||
| const updateSettingsController = (req, res) => { | const updateSettingsController = (req, res) => { | ||||||
|    const user = req.loggedInUser; |   const username = req.loggedInUser ? req.loggedInUser.username : null; | ||||||
|    const username = user === undefined ? undefined : user.username; |  | ||||||
|  |  | ||||||
|    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 { dark_mode, emoji } = req.body; | ||||||
|  |  | ||||||
|    userRepository.updateSettings(username, dark_mode, emoji) |   userRepository | ||||||
|  |     .updateSettings(username, dark_mode, emoji) | ||||||
|     .then(settings => { |     .then(settings => { | ||||||
|       res.send({ success: true, settings }); |       res.send({ success: true, settings }); | ||||||
|     }) |     }) | ||||||
|     .catch(error => { |     .catch(error => { | ||||||
|       res.status(404).send({ success: false, message: error.message }); |       res.status(404).send({ success: false, message: error.message }); | ||||||
|     }); |     }); | ||||||
| } | }; | ||||||
|  |  | ||||||
| module.exports = { | module.exports = { | ||||||
|   getSettingsController, |   getSettingsController, | ||||||
|   updateSettingsController |   updateSettingsController | ||||||
| } | }; | ||||||
|   | |||||||
| @@ -1,47 +1,52 @@ | |||||||
| const configuration = require('src/config/configuration').getInstance(); | const configuration = require("src/config/configuration").getInstance(); | ||||||
| const Tautulli = require('src/tautulli/tautulli'); | const Tautulli = require("src/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"); | ||||||
| const tautulli = new Tautulli(apiKey, ip, port); | const tautulli = new Tautulli(apiKey, ip, port); | ||||||
|  |  | ||||||
| function handleError(error, res) { | function handleError(error, res) { | ||||||
|   const { status, message } = error; |   const { status, message } = error; | ||||||
|  |  | ||||||
|   if (status && message) { |   if (status && message) { | ||||||
|     res.status(status).send({ success: false, message }) |     return res.status(status).send({ success: false, message }); | ||||||
|   } else { |   } else { | ||||||
|     console.log('caught view history controller error', error) |     console.log("caught view history controller error", error); | ||||||
|     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; | ||||||
|  |  | ||||||
|   tautulli.watchTimeStats(user.plex_userid) |   return tautulli | ||||||
|  |     .watchTimeStats(user.plex_userid) | ||||||
|     .then(data => { |     .then(data => { | ||||||
|       console.log('data', data, JSON.stringify(data.response.data)) |  | ||||||
|  |  | ||||||
|       return res.send({ |       return res.send({ | ||||||
|         success: true, |         success: true, | ||||||
|         data: data.response.data, |         data: data.response.data, | ||||||
|         message: 'watch time successfully fetched from tautulli' |         message: "watch time successfully fetched from tautulli" | ||||||
|       }) |       }); | ||||||
|     }) |     }) | ||||||
|  |     .catch(error => handleError(error, 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, y_axis } = req.query; | ||||||
|  |  | ||||||
|   tautulli.getPlaysByDayOfWeek(user.plex_userid, days, y_axis) |   return tautulli | ||||||
|     .then(data => res.send({ |     .getPlaysByDayOfWeek(user.plex_userid, days, y_axis) | ||||||
|  |     .then(data => | ||||||
|  |       res.send({ | ||||||
|         success: true, |         success: true, | ||||||
|         data: data.response.data, |         data: data.response.data, | ||||||
|       message: 'play by day of week successfully fetched from tautulli' |         message: "play by day of week successfully fetched from tautulli" | ||||||
|       }) |       }) | ||||||
|     ) |     ) | ||||||
|  |     .catch(error => handleError(error, res)); | ||||||
| } | } | ||||||
|  |  | ||||||
| function getPlaysByDaysController(req, res) { | function getPlaysByDaysController(req, res) { | ||||||
| @@ -52,47 +57,44 @@ function getPlaysByDaysController(req, res) { | |||||||
|     return res.status(422).send({ |     return res.status(422).send({ | ||||||
|       success: false, |       success: false, | ||||||
|       message: "Missing parameter: days (number)" |       message: "Missing parameter: days (number)" | ||||||
|     }) |     }); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   const allowedYAxisDataType = ['plays', 'duration']; |   const allowedYAxisDataType = ["plays", "duration"]; | ||||||
|   if (!allowedYAxisDataType.includes(y_axis)) { |   if (!allowedYAxisDataType.includes(y_axis)) { | ||||||
|     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}]` | ||||||
|     }) |     }); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   tautulli.getPlaysByDays(user.plex_userid, days, y_axis) |   return tautulli | ||||||
|     .then(data => res.send({ |     .getPlaysByDays(user.plex_userid, days, y_axis) | ||||||
|  |     .then(data => | ||||||
|  |       res.send({ | ||||||
|         success: true, |         success: true, | ||||||
|         data: data.response.data |         data: data.response.data | ||||||
|       })) |       }) | ||||||
|  |     ) | ||||||
|  |     .catch(error => handleError(error, res)); | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
| function userViewHistoryController(req, res) { | function userViewHistoryController(req, res) { | ||||||
|   const user = req.loggedInUser; |   const user = req.loggedInUser; | ||||||
|  |  | ||||||
|    console.log('user', user) |  | ||||||
|  |  | ||||||
|  |  | ||||||
|   // TODO here we should check if we can init tau |   // TODO here we should check if we can init tau | ||||||
|   // and then return 501 Not implemented |   // and then return 501 Not implemented | ||||||
|  |  | ||||||
|    tautulli.viewHistory(user.plex_userid) |   return tautulli | ||||||
|  |     .viewHistory(user.plex_userid) | ||||||
|     .then(data => { |     .then(data => { | ||||||
|        console.log('data', data, JSON.stringify(data.response.data.data)) |  | ||||||
|  |  | ||||||
|  |  | ||||||
|       return res.send({ |       return res.send({ | ||||||
|         success: true, |         success: true, | ||||||
|         data: data.response.data.data, |         data: data.response.data.data, | ||||||
|          message: 'view history successfully fetched from tautulli' |         message: "view history successfully fetched from tautulli" | ||||||
|  |       }); | ||||||
|     }) |     }) | ||||||
|      }) |     .catch(error => handleError(error, res)); | ||||||
|      .catch(error => handleError(error)) |  | ||||||
|  |  | ||||||
|  |  | ||||||
|   // const username = user.username; |   // const username = user.username; | ||||||
| } | } | ||||||
|   | |||||||
| @@ -2,7 +2,7 @@ const mustBeAuthenticated = (req, res, next) => { | |||||||
|   if (req.loggedInUser === undefined) { |   if (req.loggedInUser === undefined) { | ||||||
|     return res.status(401).send({ |     return res.status(401).send({ | ||||||
|       success: false, |       success: false, | ||||||
|          message: 'You must be logged in.', |       message: "You must be logged in." | ||||||
|     }); |     }); | ||||||
|   } |   } | ||||||
|   return next(); |   return next(); | ||||||
|   | |||||||
							
								
								
									
										32
									
								
								seasoned_api/src/webserver/middleware/reqTokenToUser.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								seasoned_api/src/webserver/middleware/reqTokenToUser.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,32 @@ | |||||||
|  | /* eslint-disable no-param-reassign */ | ||||||
|  | const configuration = require("src/config/configuration").getInstance(); | ||||||
|  | const Token = require("src/user/token"); | ||||||
|  |  | ||||||
|  | const secret = configuration.get("authentication", "secret"); | ||||||
|  |  | ||||||
|  | // Token example: | ||||||
|  | // curl -i -H "Authorization:[token]" localhost:31459/api/v1/user/history | ||||||
|  |  | ||||||
|  | const reqTokenToUser = (req, res, next) => { | ||||||
|  |   const cookieAuthToken = req.cookies.authorization; | ||||||
|  |   const headerAuthToken = req.headers.authorization; | ||||||
|  |  | ||||||
|  |   if (cookieAuthToken || headerAuthToken) { | ||||||
|  |     try { | ||||||
|  |       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(); | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | module.exports = reqTokenToUser; | ||||||
| @@ -1,23 +0,0 @@ | |||||||
| /* eslint-disable no-param-reassign */ |  | ||||||
| const configuration = require('src/config/configuration').getInstance(); |  | ||||||
|  |  | ||||||
| const secret = configuration.get('authentication', 'secret'); |  | ||||||
| const Token = require('src/user/token'); |  | ||||||
|  |  | ||||||
| // Token example: |  | ||||||
| // curl -i -H "Authorization:[token]" localhost:31459/api/v1/user/history |  | ||||||
|  |  | ||||||
| const tokenToUser = (req, res, next) => { |  | ||||||
|   const rawToken = req.headers.authorization; |  | ||||||
|   if (rawToken) { |  | ||||||
|     try { |  | ||||||
|       const token = Token.fromString(rawToken, secret); |  | ||||||
|       req.loggedInUser = token.user; |  | ||||||
|     } catch (error) { |  | ||||||
|       req.loggedInUser = undefined; |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
|   next(); |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| module.exports = tokenToUser; |  | ||||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
		Reference in New Issue
	
	Block a user