Compare commits
	
		
			85 Commits
		
	
	
		
			1.1.0
			...
			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 | |||
| 318d1e331b | |||
| 5923cbf051 | |||
| 291bdf089c | |||
| 8eacde9ccc | |||
| f8847c62f2 | |||
| ddb7e7379d | |||
| 720fb69648 | |||
| fedacf498e | |||
| 9022853502 | |||
| c1b96e17ca | |||
| a5248f0631 | |||
| e2d85c6242 | |||
| 510c014549 | |||
| 2650497986 | |||
| 639f0ec17a | |||
| 977d05c6f2 | |||
| 601fc1d0de | |||
| acc26a2f09 | |||
| 5d3a5dc8a4 | |||
| 3bb9bd84d9 | |||
| 002e663be1 | |||
| 495a3b4838 | |||
| 9e2a0101c9 | |||
| 05b001de2e | |||
| 1f9dc067e6 | 
							
								
								
									
										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 | ||||
| before_install: | ||||
|    - cd seasoned_api | ||||
|    - cp conf/development.json.example conf/development.json | ||||
| before_script:  | ||||
|    - yarn | ||||
|    - curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter | ||||
|   | ||||
| @@ -1,20 +1,26 @@ | ||||
| { | ||||
| 	"database": { | ||||
| 		"host": "../shows.db" | ||||
| 	}, | ||||
| 	"webserver": { | ||||
| 		"port": 31459 | ||||
| 	}, | ||||
| 	"tmdb": { | ||||
| 		"apiKey": "" | ||||
| 	}, | ||||
| 	"plex": { | ||||
| 		"ip": "" | ||||
| 	}, | ||||
| 	"raven": { | ||||
| 		"DSN": "" | ||||
| 	}, | ||||
| 	"authentication": { | ||||
|   "database": { | ||||
|     "host": "../shows.db" | ||||
|   }, | ||||
|   "webserver": { | ||||
|     "port": 31459, | ||||
|     "origins": [] | ||||
|   }, | ||||
|   "tmdb": { | ||||
|     "apiKey": "" | ||||
|   }, | ||||
|   "plex": { | ||||
|     "ip": "" | ||||
|   }, | ||||
|   "tautulli": { | ||||
|     "apiKey": "", | ||||
|     "ip": "", | ||||
|     "port": "" | ||||
|   }, | ||||
|   "raven": { | ||||
|     "DSN": "" | ||||
|   }, | ||||
|   "authentication": { | ||||
|     "secret": "secret" | ||||
|  	} | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -7,30 +7,33 @@ | ||||
|   }, | ||||
|   "main": "webserver/server.js", | ||||
|   "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", | ||||
|     "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/", | ||||
|     "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", | ||||
|     "apiDocs": "", | ||||
|     "classDocs": "./script/generate-class-docs.sh" | ||||
|   }, | ||||
|   "dependencies": { | ||||
|     "axios": "^0.18.0", | ||||
|     "bcrypt": "^3.0.6", | ||||
|     "bcrypt": "^5.0.1", | ||||
|     "body-parser": "~1.18.2", | ||||
|     "cookie-parser": "^1.4.6", | ||||
|     "cross-env": "~5.1.4", | ||||
|     "express": "~4.16.0", | ||||
|     "jsonwebtoken": "^8.2.0", | ||||
|     "form-data": "^2.5.1", | ||||
|     "jsonwebtoken": "^8.5.1", | ||||
|     "km-moviedb": "^0.2.12", | ||||
|     "node-cache": "^4.1.1", | ||||
|     "node-fetch": "^2.6.0", | ||||
|     "python-shell": "^0.5.0", | ||||
|     "raven": "^2.4.2", | ||||
|     "redis": "^3.0.2", | ||||
|     "request": "^2.87.0", | ||||
|     "request-promise": "^4.2", | ||||
|     "sqlite3": "^4.0.0" | ||||
|     "sqlite3": "^5.0.1" | ||||
|   }, | ||||
|   "devDependencies": { | ||||
|     "@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) { | ||||
|       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]); | ||||
|  | ||||
|       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; } | ||||
|       } | ||||
|  | ||||
|   | ||||
| @@ -1,11 +1,19 @@ | ||||
| CREATE TABLE IF NOT EXISTS user ( | ||||
|     user_name varchar(127) UNIQUE, | ||||
|     password varchar(127), | ||||
|     email varchar(127) UNIQUE, | ||||
|     admin boolean DEFAULT 0, | ||||
|     email varchar(127) UNIQUE, | ||||
|     primary key (user_name) | ||||
| ); | ||||
|  | ||||
| CREATE TABLE IF NOT EXISTS settings ( | ||||
|     user_name varchar(127) UNIQUE, | ||||
|     dark_mode boolean DEFAULT 0, | ||||
|     plex_userid varchar(127) DEFAULT NULL, | ||||
|     emoji varchar(16) DEFAULT NULL, | ||||
|     foreign key(user_name) REFERENCES user(user_name) ON DELETE CASCADE | ||||
| ); | ||||
|  | ||||
| CREATE TABLE IF NOT EXISTS cache ( | ||||
|     key varchar(255), | ||||
|     value blob, | ||||
| @@ -23,17 +31,18 @@ CREATE TABLE IF NOT EXISTS search_history ( | ||||
| ); | ||||
|  | ||||
| CREATE TABLE IF NOT EXISTS requests( | ||||
|     id TEXT, | ||||
|     id NUMBER, | ||||
|     title TEXT, | ||||
|     year NUMBER, | ||||
|     poster_path TEXT DEFAULT NULL, | ||||
|     background_path TEXT DEFAULT NULL, | ||||
|     requested_by TEXT, | ||||
|     requested_by varchar(127) DEFAULT NULL, | ||||
|     ip TEXT, | ||||
|     date DATE DEFAULT CURRENT_TIMESTAMP, | ||||
|     status CHAR(25) DEFAULT 'requested' NOT NULL, | ||||
|     user_agent CHAR(255) DEFAULT NULL, | ||||
|     type CHAR(50) DEFAULT 'movie' | ||||
|     type CHAR(50) DEFAULT 'movie', | ||||
|     foreign key(requested_by) REFERENCES user(user_name) ON DELETE SET NULL | ||||
| ); | ||||
|  | ||||
| CREATE TABLE IF NOT EXISTS request( | ||||
|   | ||||
| @@ -1,4 +1,5 @@ | ||||
| DROP TABLE IF EXISTS user; | ||||
| DROP TABLE IF EXISTS settings; | ||||
| DROP TABLE IF EXISTS search_history; | ||||
| DROP TABLE IF EXISTS requests; | ||||
| DROP TABLE IF EXISTS request; | ||||
|   | ||||
| @@ -6,6 +6,7 @@ class SqliteDatabase { | ||||
|    constructor(host) { | ||||
|       this.host = host; | ||||
|       this.connection = new sqlite3.Database(this.host); | ||||
|       this.execute('pragma foreign_keys = on;'); | ||||
|       this.schemaDirectory = path.join(__dirname, 'schemas'); | ||||
|    } | ||||
|  | ||||
|   | ||||
							
								
								
									
										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,84 +1,104 @@ | ||||
| const assert = require('assert'); | ||||
| const http = require('http'); | ||||
| const { URL } = require('url'); | ||||
| const PythonShell = require('python-shell'); | ||||
| const assert = require("assert"); | ||||
| const http = require("http"); | ||||
| const { URL } = require("url"); | ||||
| 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) { | ||||
|    return new Promise((resolve, reject) => { | ||||
|       const options = new URL(url); | ||||
|       if (options.protocol.includes('magnet')) | ||||
|          resolve(url) | ||||
|   return new Promise((resolve, reject) => { | ||||
|     const options = new URL(url); | ||||
|     if (options.protocol.includes("magnet")) resolve(url); | ||||
|  | ||||
|       http.get(options, (res) => { | ||||
|          if (res.statusCode == 301 || res.statusCode == 302) { | ||||
|             resolve(res.headers.location) | ||||
|          } | ||||
|       }); | ||||
|    }); | ||||
|     http.get(options, res => { | ||||
|       if (res.statusCode == 301 || res.statusCode == 302) { | ||||
|         resolve(res.headers.location); | ||||
|       } | ||||
|     }); | ||||
|   }); | ||||
| } | ||||
|  | ||||
| async function find(searchterm, callback) { | ||||
|    const options = { | ||||
|       pythonPath: '../torrent_search/env/bin/python3', | ||||
|       scriptPath: '../torrent_search', | ||||
|       args: [searchterm, '-s', 'jackett', '-f', '--print'] | ||||
|    } | ||||
|   const options = { | ||||
|     pythonPath: "../torrent_search/env/bin/python3", | ||||
|     scriptPath: "../torrent_search", | ||||
|     args: [searchterm, "-s", "jackett", "--print"] | ||||
|   }; | ||||
|  | ||||
|    PythonShell.run('torrentSearch/search.py', options, callback); | ||||
|    // PythonShell does not support return | ||||
|   PythonShell.run("torrentSearch/search.py", options, callback); | ||||
|   // PythonShell does not support return | ||||
| } | ||||
|  | ||||
|  | ||||
| async function callPythonAddMagnet(url, callback) { | ||||
|    getMagnetFromURL(url) | ||||
|    .then((magnet) => { | ||||
|   getMagnetFromURL(url) | ||||
|     .then(magnet => { | ||||
|       const options = { | ||||
|         pythonPath: '../delugeClient/env/bin/python3', | ||||
|         scriptPath: '../delugeClient', | ||||
|         args: ['add', magnet] | ||||
|         pythonPath: "../delugeClient/env/bin/python3", | ||||
|         scriptPath: "../delugeClient", | ||||
|         args: ["add", magnet] | ||||
|       }; | ||||
|  | ||||
|       PythonShell.run('deluge_cli.py', options, callback); | ||||
|    }) | ||||
|    .catch((err) => { | ||||
|       PythonShell.run("deluge_cli.py", options, callback); | ||||
|     }) | ||||
|     .catch(err => { | ||||
|       console.log(err); | ||||
|       throw new Error(err); | ||||
|    }) | ||||
|     }); | ||||
| } | ||||
|  | ||||
| async function SearchPiratebay(query) { | ||||
|    return await new Promise((resolve, reject) => find(query, (err, results) => { | ||||
|       if (err) { | ||||
|          console.log('THERE WAS A FUCKING ERROR!\n', err); | ||||
|          reject(Error('There was a error when searching for torrents')); | ||||
|       } | ||||
|       if (results) { | ||||
|          resolve(JSON.parse(results, null, '\t')); | ||||
|       } | ||||
|    })); | ||||
|   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) { | ||||
|             console.log("THERE WAS A FUCKING ERROR!\n", err); | ||||
|             reject(Error("There was a error when searching for torrents")); | ||||
|           } | ||||
|  | ||||
|           if (results) { | ||||
|             const jsonData = JSON.parse(results[1], null, "\t"); | ||||
|             cache.set(cacheKey, jsonData); | ||||
|             resolve(jsonData); | ||||
|           } | ||||
|         }) | ||||
|       ) | ||||
|   ); | ||||
| } | ||||
|  | ||||
| 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) { | ||||
|          /* eslint-disable no-console */ | ||||
|          console.log(err); | ||||
|          reject(Error('Enable to add torrent', err))  | ||||
|         /* eslint-disable no-console */ | ||||
|         console.log(err); | ||||
|         reject(Error("Enable to add torrent", err)); | ||||
|       } | ||||
|       /* eslint-disable no-console */ | ||||
|       console.log('result/error:', err, results); | ||||
|       console.log("result/error:", err, results); | ||||
|  | ||||
|       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 (?,?,?)"; | ||||
|  | ||||
|       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 }); | ||||
|    })); | ||||
|     }) | ||||
|   ); | ||||
| } | ||||
|  | ||||
| module.exports = { SearchPiratebay, AddMagnet }; | ||||
|   | ||||
| @@ -1,89 +1,240 @@ | ||||
| const fetch = require('node-fetch') | ||||
| const convertPlexToMovie = require('src/plex/convertPlexToMovie') | ||||
| const convertPlexToShow = require('src/plex/convertPlexToShow') | ||||
| const convertPlexToEpisode = require('src/plex/convertPlexToEpisode') | ||||
| const fetch = require("node-fetch"); | ||||
| const convertPlexToMovie = require("src/plex/convertPlexToMovie"); | ||||
| const convertPlexToShow = require("src/plex/convertPlexToShow"); | ||||
| 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, }  | ||||
| // TODO? import class definitions to compare types ? | ||||
| // what would typescript do? | ||||
| const sanitize = string => string.toLowerCase().replace(/[^\w]/gi, ""); | ||||
|  | ||||
| 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 { | ||||
|   constructor(ip, port=32400) { | ||||
|     this.plexIP = ip | ||||
|     this.plexPort = port | ||||
|   constructor(ip, port = 32400, cache = null) { | ||||
|     this.plexIP = ip; | ||||
|     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) { | ||||
|     if (plex === undefined || tmdb === undefined) | ||||
|       return false | ||||
|     let match; | ||||
|  | ||||
|     const sanitize = (string) => string.toLowerCase() | ||||
|     if (plex == null || tmdb == null) return false; | ||||
|  | ||||
|     const matchTitle = sanitize(plex.title) === sanitize(tmdb.title) | ||||
|     const matchYear = plex.year === tmdb.year | ||||
|  | ||||
|     return matchTitle && matchYear | ||||
|   } | ||||
|  | ||||
|   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() | ||||
|     if (plex instanceof Array) { | ||||
|       let possibleMatches = plex.map(plexItem => | ||||
|         matchingTitleAndYear(plexItem, tmdb) | ||||
|       ); | ||||
|       match = possibleMatches.includes(true); | ||||
|     } 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) { | ||||
|     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 = { | ||||
|       timeout: 2000, | ||||
|       headers: { 'Accept': 'application/json' } | ||||
|     } | ||||
|       timeout: 20000, | ||||
|       headers: { Accept: "application/json" } | ||||
|     }; | ||||
|  | ||||
|     return fetch(url, options) | ||||
|       .then(this.successfullResponse) | ||||
|       .then(this.mapResults) | ||||
|       .catch(error => { | ||||
|         if (error.type === 'request-timeout') { | ||||
|           throw { message: 'Plex did not respond', status: 408, success: false } | ||||
|         } | ||||
|     return new Promise((resolve, reject) => | ||||
|       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(resolve) | ||||
|         .catch(error => { | ||||
|           if (error != undefined && error.type === "request-timeout") { | ||||
|             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) { | ||||
|     if (response === undefined || response.MediaContainer === undefined) { | ||||
|       console.log('response was not valid to map', response) | ||||
|       return [] | ||||
|     if ( | ||||
|       response == null || | ||||
|       response.MediaContainer == null || | ||||
|       response.MediaContainer.Hub == null | ||||
|     ) { | ||||
|       return []; | ||||
|     } | ||||
|  | ||||
|     return response.MediaContainer.Hub | ||||
|       .filter(category => category.size > 0) | ||||
|     return response.MediaContainer.Hub.filter(category => category.size > 0) | ||||
|       .map(category => { | ||||
|         if (category.type === 'movie') { | ||||
|           return category.Metadata.map(movie => { | ||||
|             const ovie = Movie.convertFromPlexResponse(movie) | ||||
|             return ovie.createJsonResponse() | ||||
|           }) | ||||
|         } else if (category.type === 'show') { | ||||
|           return category.Metadata.map(convertPlexToShow) | ||||
|         } else if (category.type === 'episode') { | ||||
|           return category.Metadata.map(convertPlexToEpisode) | ||||
|         if (category.type === "movie") { | ||||
|           return category.Metadata; | ||||
|         } else if (category.type === "show") { | ||||
|           return category.Metadata.map(convertPlexToShow); | ||||
|         } else if (category.type === "episode") { | ||||
|           return category.Metadata.map(convertPlexToEpisode); | ||||
|         } | ||||
|       }) | ||||
|       .filter(result => result !== undefined) | ||||
|       .flat() | ||||
|       .filter(result => result !== undefined); | ||||
|   } | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -1,101 +1,130 @@ | ||||
| const PlexRepository = require('src/plex/plexRepository'); | ||||
| const Cache = require('src/tmdb/cache'); | ||||
| const configuration = require('src/config/configuration').getInstance(); | ||||
| const TMDB = require('src/tmdb/tmdb'); | ||||
| const establishedDatabase = require('src/database/database'); | ||||
| const PlexRepository = require("src/plex/plexRepository"); | ||||
| const configuration = require("src/config/configuration").getInstance(); | ||||
| const TMDB = require("src/tmdb/tmdb"); | ||||
| const establishedDatabase = require("src/database/database"); | ||||
|  | ||||
| const plexRepository = new PlexRepository(configuration.get('plex', 'ip')); | ||||
| const cache = new Cache(); | ||||
| const tmdb = new TMDB(cache, configuration.get('tmdb', 'apiKey')); | ||||
| const plexRepository = new PlexRepository(configuration.get("plex", "ip")); | ||||
| const tmdb = new TMDB(configuration.get("tmdb", "apiKey")); | ||||
|  | ||||
| class RequestRepository { | ||||
|    constructor(database) { | ||||
|       this.database = database || establishedDatabase; | ||||
|       this.queries = { | ||||
|          insertRequest: `INSERT INTO requests(id,title,year,poster_path,background_path,requested_by,ip,user_agent,type) | ||||
|   constructor(database) { | ||||
|     this.database = database || establishedDatabase; | ||||
|     this.queries = { | ||||
|       insertRequest: `INSERT INTO requests(id,title,year,poster_path,background_path,requested_by,ip,user_agent,type) | ||||
|           VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, | ||||
|          fetchRequestedItems: 'SELECT * FROM requests ORDER BY date DESC LIMIT 25 OFFSET ?*25-25', | ||||
|          fetchRequestedItemsByStatus: 'SELECT * FROM requests WHERE status IS ? AND type LIKE ? ORDER BY date DESC LIMIT 25 OFFSET ?*25-25', | ||||
|          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 = { | ||||
|          search: 'se', | ||||
|          lookup: 'i', | ||||
|       }; | ||||
|    } | ||||
|       fetchRequestedItems: | ||||
|         "SELECT * FROM requests ORDER BY date DESC LIMIT 25 OFFSET ?*25-25", | ||||
|       fetchRequestedItemsByStatus: | ||||
|         "SELECT * FROM requests WHERE status IS ? AND type LIKE ? ORDER BY date DESC LIMIT 25 OFFSET ?*25-25", | ||||
|       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 = { | ||||
|       search: "se", | ||||
|       lookup: "i" | ||||
|     }; | ||||
|   } | ||||
|  | ||||
|    search(query, type, page) { | ||||
|       return Promise.resolve() | ||||
|          .then(() => tmdb.search(query, type, page)) | ||||
|          .catch(error => Error(`error in the house${error}`)); | ||||
|    } | ||||
|   search(query, type, page) { | ||||
|     return Promise.resolve() | ||||
|       .then(() => tmdb.search(query, type, page)) | ||||
|       .catch(error => Error(`error in the house${error}`)); | ||||
|   } | ||||
|  | ||||
|    lookup(identifier, type = 'movie') { | ||||
|       return Promise.resolve() | ||||
|          .then(() => tmdb.lookup(identifier, type)) | ||||
|          .then(tmdbMovie => this.checkID(tmdbMovie)) | ||||
|          .then(tmdbMovie => plexRepository.inPlex(tmdbMovie)) | ||||
|          .catch((error) => { | ||||
|             throw new Error(error); | ||||
|          }); | ||||
|    } | ||||
|   lookup(identifier, type = "movie") { | ||||
|     return Promise.resolve() | ||||
|       .then(() => tmdb.lookup(identifier, type)) | ||||
|       .then(tmdbMovie => this.checkID(tmdbMovie)) | ||||
|       .then(tmdbMovie => plexRepository.inPlex(tmdbMovie)) | ||||
|       .catch(error => { | ||||
|         throw new Error(error); | ||||
|       }); | ||||
|   } | ||||
|  | ||||
|    checkID(tmdbMovie) { | ||||
|       return Promise.resolve() | ||||
|          .then(() => this.database.get(this.queries.checkIfIdRequested, [tmdbMovie.id, tmdbMovie.type])) | ||||
|          .then((result, error) => { | ||||
|             if (error) { throw new Error(error); } | ||||
|             tmdbMovie.requested = result ? true : false; | ||||
|             return tmdbMovie; | ||||
|          }); | ||||
|    } | ||||
|   checkID(tmdbMovie) { | ||||
|     return Promise.resolve() | ||||
|       .then(() => | ||||
|         this.database.get(this.queries.checkIfIdRequested, [ | ||||
|           tmdbMovie.id, | ||||
|           tmdbMovie.type | ||||
|         ]) | ||||
|       ) | ||||
|       .then((result, error) => { | ||||
|         if (error) { | ||||
|           throw new Error(error); | ||||
|         } | ||||
|         tmdbMovie.requested = result ? true : false; | ||||
|         return tmdbMovie; | ||||
|       }); | ||||
|   } | ||||
|  | ||||
|    /** | ||||
|   /** | ||||
|    * Send request for given media id. | ||||
|    * @param {identifier, type} the id of the media object and type of media must be defined | ||||
|    * @returns {Promise} If nothing has gone wrong. | ||||
|    */ | ||||
|    sendRequest(identifier, type, ip, user_agent, user) { | ||||
|    	return Promise.resolve() | ||||
|    	.then(() => tmdb.lookup(identifier, type)) | ||||
|       .then((movie) => { | ||||
|          const username = user === undefined ? undefined : user.username; | ||||
|          // Add request to database | ||||
|          return this.database.run(this.queries.insertRequest, [movie.id, movie.title, movie.year, movie.poster_path, movie.background_path, username, ip, user_agent, movie.type]); | ||||
|   sendRequest(identifier, type, ip, user_agent, user) { | ||||
|     return Promise.resolve() | ||||
|       .then(() => tmdb.lookup(identifier, type)) | ||||
|       .then(movie => { | ||||
|         const username = user === undefined ? undefined : user.username; | ||||
|         // Add request to database | ||||
|         return this.database.run(this.queries.insertRequest, [ | ||||
|           movie.id, | ||||
|           movie.title, | ||||
|           movie.year, | ||||
|           movie.poster_path, | ||||
|           movie.background_path, | ||||
|           username, | ||||
|           ip, | ||||
|           user_agent, | ||||
|           movie.type | ||||
|         ]); | ||||
|       }); | ||||
|    } | ||||
|   } | ||||
|  | ||||
|    fetchRequested(status, page = '1', type = '%') { | ||||
|    	return Promise.resolve() | ||||
|    	.then(() => { | ||||
| 	      if (status === 'requested' || status === 'downloading' || status === 'downloaded') | ||||
| 	         return this.database.all(this.queries.fetchRequestedItemsByStatus, [status, type, page]); | ||||
| 	      else | ||||
| 	         return this.database.all(this.queries.fetchRequestedItems, page); | ||||
|    	}) | ||||
|    } | ||||
|   fetchRequested(status, page = "1", type = "%") { | ||||
|     return Promise.resolve().then(() => { | ||||
|       if ( | ||||
|         status === "requested" || | ||||
|         status === "downloading" || | ||||
|         status === "downloaded" | ||||
|       ) | ||||
|         return this.database.all(this.queries.fetchRequestedItemsByStatus, [ | ||||
|           status, | ||||
|           type, | ||||
|           page | ||||
|         ]); | ||||
|       else return this.database.all(this.queries.fetchRequestedItems, page); | ||||
|     }); | ||||
|   } | ||||
|  | ||||
|    userRequests(user) { | ||||
|       return Promise.resolve() | ||||
|          .then(() => this.database.all(this.queries.userRequests, user.username)) | ||||
|          .catch((error) => { | ||||
|             if (String(error).includes('no such column')) { | ||||
|                throw new Error('Username not found'); | ||||
|             } | ||||
|             throw new Error('Unable to fetch your requests'); | ||||
|          }) | ||||
|          .then((result) => { | ||||
|             // TODO do a correct mapping before sending, not just a dump of the database | ||||
|             result.map(item => item.poster = item.poster_path) | ||||
|             return result | ||||
|          }); | ||||
|    } | ||||
|   userRequests(username) { | ||||
|     return Promise.resolve() | ||||
|       .then(() => this.database.all(this.queries.userRequests, username)) | ||||
|       .catch(error => { | ||||
|         if (String(error).includes("no such column")) { | ||||
|           throw new Error("Username not found"); | ||||
|         } | ||||
|         throw new Error("Unable to fetch your requests"); | ||||
|       }) | ||||
|       .then(result => { | ||||
|         // TODO do a correct mapping before sending, not just a dump of the database | ||||
|         result.map(item => (item.poster = item.poster_path)); | ||||
|         return result; | ||||
|       }); | ||||
|   } | ||||
|  | ||||
|    updateRequestedById(id, type, status) { | ||||
|       return this.database.run(this.queries.updateRequestedById, [status, id, type]); | ||||
|    } | ||||
|   updateRequestedById(id, type, status) { | ||||
|     return this.database.run(this.queries.updateRequestedById, [ | ||||
|       status, | ||||
|       id, | ||||
|       type | ||||
|     ]); | ||||
|   } | ||||
| } | ||||
|  | ||||
| module.exports = RequestRepository; | ||||
|   | ||||
| @@ -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 configuration = require('src/config/configuration').getInstance(); | ||||
| const Cache = require('src/tmdb/cache'); | ||||
| const TMDB = require('src/tmdb/tmdb'); | ||||
| const cache = new Cache(); | ||||
| const tmdb = new TMDB(cache, configuration.get('tmdb', 'apiKey')); | ||||
| const tmdb = new TMDB(configuration.get('tmdb', 'apiKey')); | ||||
| const establishedDatabase = require('src/database/database'); | ||||
| const utils = require('./utils'); | ||||
|  | ||||
| @@ -86,18 +84,18 @@ class RequestRepository { | ||||
|    * @param {tmdb} tmdb class of movie|show to add | ||||
|    * @returns {Promise} | ||||
|    */ | ||||
|   requestFromTmdb(tmdb, ip, user_agent, user) { | ||||
|   requestFromTmdb(tmdb, ip, user_agent, username) { | ||||
|     return Promise.resolve() | ||||
|     .then(() => this.database.get(this.queries.read, [tmdb.id, tmdb.type])) | ||||
|     .then(row => assert.equal(row, undefined, 'Id has already been requested')) | ||||
|     .then(() => this.database.run(this.queries.add, [tmdb.id, tmdb.title, tmdb.year, tmdb.poster, tmdb.backdrop, user, ip, user_agent, tmdb.type])) | ||||
|     .catch((error) => { | ||||
|       if (error.name === 'AssertionError' || error.message.endsWith('been requested')) { | ||||
|         throw new Error('This id is already requested', error.message); | ||||
|       } | ||||
|       console.log('Error @ request.addTmdb:', error); | ||||
|       throw new Error('Could not add request'); | ||||
|     }); | ||||
|       .then(() => this.database.get(this.queries.read, [tmdb.id, tmdb.type])) | ||||
|       .then(row => assert.equal(row, undefined, 'Id has already been requested')) | ||||
|       .then(() => this.database.run(this.queries.add, [tmdb.id, tmdb.title, tmdb.year, tmdb.poster, tmdb.backdrop, username, ip, user_agent, tmdb.type])) | ||||
|       .catch((error) => { | ||||
|         if (error.name === 'AssertionError' || error.message.endsWith('been requested')) { | ||||
|           throw new Error('This id is already requested', error.message); | ||||
|         } | ||||
|         console.log('Error @ request.addTmdb:', error); | ||||
|         throw new Error('Could not add request'); | ||||
|       }); | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|   | ||||
| @@ -28,17 +28,23 @@ class SearchHistory { | ||||
|  | ||||
|    /** | ||||
|    * Creates a new search entry in the database. | ||||
|    * @param {User} user a new user | ||||
|    * @param {String} username logged in user doing the search | ||||
|    * @param {String} searchQuery the query the user searched for | ||||
|    * @returns {Promise} | ||||
|    */ | ||||
|    create(user, searchQuery) { | ||||
|       return Promise.resolve() | ||||
|          .then(() => this.database.run(this.queries.create, [searchQuery, user])) | ||||
|          .catch((error) => { | ||||
|    create(username, searchQuery) { | ||||
|       return this.database.run(this.queries.create, [searchQuery, username]) | ||||
|          .catch(error => { | ||||
|             if (error.message.includes('FOREIGN')) { | ||||
|                throw new Error('Could not create search history.'); | ||||
|             } | ||||
|  | ||||
|             throw { | ||||
|                success: false, | ||||
|                status: 500, | ||||
|                message: 'An unexpected error occured', | ||||
|                source: 'database' | ||||
|             } | ||||
|          }); | ||||
|    } | ||||
| } | ||||
|   | ||||
							
								
								
									
										74
									
								
								seasoned_api/src/tautulli/tautulli.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										74
									
								
								seasoned_api/src/tautulli/tautulli.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,74 @@ | ||||
| const fetch = require("node-fetch"); | ||||
|  | ||||
| class Tautulli { | ||||
|   constructor(apiKey, ip, port) { | ||||
|     this.apiKey = apiKey; | ||||
|     this.ip = ip; | ||||
|     this.port = port; | ||||
|   } | ||||
|  | ||||
|   buildUrlWithCmdAndUserid(cmd, user_id) { | ||||
|     const url = new URL("api/v2", `http://${this.ip}:${this.port}`); | ||||
|     url.searchParams.append("apikey", this.apiKey); | ||||
|     url.searchParams.append("cmd", cmd); | ||||
|     url.searchParams.append("user_id", user_id); | ||||
|  | ||||
|     return url; | ||||
|   } | ||||
|  | ||||
|   logTautulliError(error) { | ||||
|     console.error("error fetching from tautulli"); | ||||
|  | ||||
|     throw error; | ||||
|   } | ||||
|  | ||||
|   getPlaysByDayOfWeek(plex_userid, days, y_axis) { | ||||
|     const url = this.buildUrlWithCmdAndUserid( | ||||
|       "get_plays_by_dayofweek", | ||||
|       plex_userid | ||||
|     ); | ||||
|     url.searchParams.append("time_range", days); | ||||
|     url.searchParams.append("y_axis", y_axis); | ||||
|  | ||||
|     return fetch(url.href) | ||||
|       .then(resp => resp.json()) | ||||
|       .catch(error => this.logTautulliError(error)); | ||||
|   } | ||||
|  | ||||
|   getPlaysByDays(plex_userid, days, y_axis) { | ||||
|     const url = this.buildUrlWithCmdAndUserid("get_plays_by_date", plex_userid); | ||||
|     url.searchParams.append("time_range", days); | ||||
|     url.searchParams.append("y_axis", y_axis); | ||||
|  | ||||
|     return fetch(url.href) | ||||
|       .then(resp => resp.json()) | ||||
|       .catch(error => this.logTautulliError(error)); | ||||
|   } | ||||
|  | ||||
|   watchTimeStats(plex_userid) { | ||||
|     const url = this.buildUrlWithCmdAndUserid( | ||||
|       "get_user_watch_time_stats", | ||||
|       plex_userid | ||||
|     ); | ||||
|     url.searchParams.append("grouping", 0); | ||||
|  | ||||
|     return fetch(url.href) | ||||
|       .then(resp => resp.json()) | ||||
|       .catch(error => this.logTautulliError(error)); | ||||
|   } | ||||
|  | ||||
|   viewHistory(plex_userid) { | ||||
|     const url = this.buildUrlWithCmdAndUserid("get_history", plex_userid); | ||||
|  | ||||
|     url.searchParams.append("start", 0); | ||||
|     url.searchParams.append("length", 50); | ||||
|  | ||||
|     console.log("fetching url", url.href); | ||||
|  | ||||
|     return fetch(url.href) | ||||
|       .then(resp => resp.json()) | ||||
|       .catch(error => this.logTautulliError(error)); | ||||
|   } | ||||
| } | ||||
|  | ||||
| module.exports = Tautulli; | ||||
| @@ -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 { tmdbInfo } = require('src/tmdb/types') | ||||
| const { | ||||
|   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 { | ||||
|   constructor(cache, apiKey, tmdbLibrary) { | ||||
|     this.cache = cache; | ||||
|   constructor(apiKey, cache, tmdbLibrary) { | ||||
|     this.tmdbLibrary = tmdbLibrary || moviedb(apiKey); | ||||
|  | ||||
|     this.cache = cache || redisCache; | ||||
|     this.cacheTags = { | ||||
|       multiSearch: 'mus',  | ||||
|       movieSearch: 'mos',  | ||||
|       showSearch: 'ss', | ||||
|       personSearch: 'ps', | ||||
|       movieInfo: 'mi', | ||||
|       movieCredits: 'mc', | ||||
|       movieReleaseDates: 'mrd', | ||||
|       showInfo: 'si', | ||||
|       showCredits: 'sc', | ||||
|       personInfo: 'pi', | ||||
|       miscNowPlayingMovies: 'npm', | ||||
|       miscPopularMovies: 'pm', | ||||
|       miscTopRatedMovies: 'tpm', | ||||
|       miscUpcomingMovies: 'um', | ||||
|       tvOnTheAir: 'toa', | ||||
|       miscPopularTvs: 'pt', | ||||
|       miscTopRatedTvs: 'trt', | ||||
|       multiSearch: "mus", | ||||
|       movieSearch: "mos", | ||||
|       showSearch: "ss", | ||||
|       personSearch: "ps", | ||||
|       movieInfo: "mi", | ||||
|       movieCredits: "mc", | ||||
|       movieReleaseDates: "mrd", | ||||
|       movieImages: "mimg", | ||||
|       showInfo: "si", | ||||
|       showCredits: "sc", | ||||
|       personInfo: "pi", | ||||
|       personCredits: "pc", | ||||
|       miscNowPlayingMovies: "npm", | ||||
|       miscPopularMovies: "pm", | ||||
|       miscTopRatedMovies: "tpm", | ||||
|       miscUpcomingMovies: "um", | ||||
|       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) { | ||||
|     const query = { id: identifier }; | ||||
|     const cacheKey = `${this.cacheTags.movieInfo}:${identifier}`; | ||||
|     const cacheKey = `tmdb/${this.cacheTags.movieInfo}:${identifier}`; | ||||
|  | ||||
|     return this.cache.get(cacheKey) | ||||
|       .catch(() => this.tmdb('movieInfo', query)) | ||||
|       .catch(tmdbError => tmdbErrorResponse(tmdbError, 'movie info')) | ||||
|       .then(movie => this.cache.set(cacheKey, movie, 1)) | ||||
|       .then(movie => Movie.convertFromTmdbResponse(movie)) | ||||
|     return this.getFromCacheOrFetchFromTmdb(cacheKey, "movieInfo", query) | ||||
|       .then(movie => this.cache.set(cacheKey, movie, this.defaultTTL)) | ||||
|       .then(movie => Movie.convertFromTmdbResponse(movie)); | ||||
|   } | ||||
|  | ||||
|   /** | ||||
| @@ -52,14 +93,12 @@ class TMDB { | ||||
|    * @returns {Promise} movie cast object | ||||
|    */ | ||||
|   movieCredits(identifier) { | ||||
|     const query = { id: identifier } | ||||
|     const cacheKey = `${this.cacheTags.movieCredits}:${identifier}` | ||||
|     const query = { id: identifier }; | ||||
|     const cacheKey = `tmdb/${this.cacheTags.movieCredits}:${identifier}`; | ||||
|  | ||||
|     return this.cache.get(cacheKey) | ||||
|       .catch(() => this.tmdb('movieCredits', query)) | ||||
|       .catch(tmdbError => tmdbErrorResponse(tmdbError, 'movie credits')) | ||||
|       .then(credits => this.cache.set(cacheKey, credits, 1)) | ||||
|       .then(credits => Credits.convertFromTmdbResponse(credits)) | ||||
|     return this.getFromCacheOrFetchFromTmdb(cacheKey, "movieCredits", query) | ||||
|       .then(credits => this.cache.set(cacheKey, credits, this.defaultTTL)) | ||||
|       .then(credits => Credits.convertFromTmdbResponse(credits)); | ||||
|   } | ||||
|  | ||||
|   /** | ||||
| @@ -69,12 +108,10 @@ class TMDB { | ||||
|    */ | ||||
|   movieReleaseDates(identifier) { | ||||
|     const query = { id: identifier } | ||||
|     const cacheKey = `${this.cacheTags.movieReleaseDates}:${identifier}` | ||||
|     const cacheKey = `tmdb/${this.cacheTags.movieReleaseDates}:${identifier}` | ||||
|  | ||||
|     return this.cache.get(cacheKey) | ||||
|       .catch(() => this.tmdb('movieReleaseDates', query)) | ||||
|       .catch(tmdbError => tmdbErrorResponse(tmdbError, 'movie release dates')) | ||||
|       .then(releaseDates => this.cache.set(cacheKey, releaseDates, 1)) | ||||
|     return this.getFromCacheOrFetchFromTmdb(cacheKey, 'movieReleaseDates', query) | ||||
|       .then(releaseDates => this.cache.set(cacheKey, releaseDates, this.defaultTTL)) | ||||
|       .then(releaseDates => ReleaseDates.convertFromTmdbResponse(releaseDates)) | ||||
|   } | ||||
|   | ||||
| @@ -86,24 +123,20 @@ class TMDB { | ||||
|    */ | ||||
|   showInfo(identifier) { | ||||
|     const query = { id: identifier }; | ||||
|     const cacheKey = `${this.cacheTags.showInfo}:${identifier}`; | ||||
|     const cacheKey = `tmdb/${this.cacheTags.showInfo}:${identifier}`; | ||||
|  | ||||
|     return this.cache.get(cacheKey) | ||||
|       .catch(() => this.tmdb('tvInfo', query)) | ||||
|       .catch(tmdbError => tmdbErrorResponse(tmdbError, 'tv info')) | ||||
|       .then(show => this.cache.set(cacheKey, show, 1)) | ||||
|       .then(show => Show.convertFromTmdbResponse(show)) | ||||
|     return this.getFromCacheOrFetchFromTmdb(cacheKey, "tvInfo", query) | ||||
|       .then(show => this.cache.set(cacheKey, show, this.defaultTTL)) | ||||
|       .then(show => Show.convertFromTmdbResponse(show)); | ||||
|   } | ||||
|  | ||||
|   showCredits(identifier) { | ||||
|     const query = { id: identifier } | ||||
|     const cacheKey = `${this.cacheTags.showCredits}:${identifier}` | ||||
|     const query = { id: identifier }; | ||||
|     const cacheKey = `tmdb/${this.cacheTags.showCredits}:${identifier}`; | ||||
|  | ||||
|     return this.cache.get(cacheKey) | ||||
|       .catch(() => this.tmdb('tvCredits', query)) | ||||
|       .catch(tmdbError => tmdbErrorResponse(tmdbError, 'show credits')) | ||||
|       .then(credits => this.cache.set(cacheKey, credits, 1)) | ||||
|       .then(credits => Credits.convertFromTmdbResponse(credits)) | ||||
|     return this.getFromCacheOrFetchFromTmdb(cacheKey, "tvCredits", query) | ||||
|       .then(credits => this.cache.set(cacheKey, credits, this.defaultTTL)) | ||||
|       .then(credits => Credits.convertFromTmdbResponse(credits)); | ||||
|   } | ||||
|  | ||||
|   /** | ||||
| @@ -114,22 +147,32 @@ class TMDB { | ||||
|    */ | ||||
|   personInfo(identifier) { | ||||
|     const query = { id: identifier }; | ||||
|     const cacheKey = `${this.cacheTags.personInfo}:${identifier}`; | ||||
|     const cacheKey = `tmdb/${this.cacheTags.personInfo}:${identifier}`; | ||||
|  | ||||
|     return this.cache.get(cacheKey) | ||||
|       .catch(() => this.tmdb('personInfo', query)) | ||||
|       .catch(tmdbError => tmdbErrorResponse(tmdbError, 'person info')) | ||||
|       .then(person => this.cache.set(cacheKey, person, 1)) | ||||
|       .then(person => Person.convertFromTmdbResponse(person)) | ||||
|     return this.getFromCacheOrFetchFromTmdb(cacheKey, "personInfo", query) | ||||
|       .then(person => this.cache.set(cacheKey, person, this.defaultTTL)) | ||||
|       .then(person => Person.convertFromTmdbResponse(person)); | ||||
|   } | ||||
|  | ||||
|   multiSearch(search_query, page=1) { | ||||
|     const query = { query: search_query, page: page }; | ||||
|     const cacheKey = `${this.cacheTags.multiSearch}:${page}:${search_query}`; | ||||
|     return this.cache.get(cacheKey) | ||||
|       .catch(() => this.tmdb('searchMulti', query)) | ||||
|       .catch(tmdbError => tmdbErrorResponse(tmdbError, 'search results')) | ||||
|       .then(response => this.cache.set(cacheKey, response, 1)) | ||||
|   personCredits(identifier) { | ||||
|     const query = { id: identifier }; | ||||
|     const cacheKey = `tmdb/${this.cacheTags.personCredits}:${identifier}`; | ||||
|  | ||||
|     return this.getFromCacheOrFetchFromTmdb( | ||||
|       cacheKey, | ||||
|       "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)); | ||||
|   } | ||||
|  | ||||
| @@ -139,15 +182,13 @@ class TMDB { | ||||
|    * @param {Number} page representing pagination of results | ||||
|    * @returns {Promise} dict with query results, current page and total_pages | ||||
|    */ | ||||
|   movieSearch(query, page=1) { | ||||
|     const tmdbquery = { query: query, page: page }; | ||||
|     const cacheKey = `${this.cacheTags.movieSearch}:${page}:${query}`; | ||||
|   movieSearch(search_query, page = 1, include_adult = true) { | ||||
|     const tmdbquery = { query: search_query, page, include_adult }; | ||||
|     const cacheKey = `tmdb/${this.cacheTags.movieSearch}:${page}:${search_query}:${include_adult}`; | ||||
|  | ||||
|     return this.cache.get(cacheKey) | ||||
|       .catch(() => this.tmdb('searchMovie', tmdbquery)) | ||||
|       .catch(tmdbError => tmdbErrorResponse(tmdbError, 'movie search results')) | ||||
|       .then(response => this.cache.set(cacheKey, response, 1)) | ||||
|       .then(response => this.mapResults(response, 'movie')) | ||||
|     return this.getFromCacheOrFetchFromTmdb(cacheKey, "searchMovie", tmdbquery) | ||||
|       .then(response => this.cache.set(cacheKey, response, this.defaultTTL)) | ||||
|       .then(response => this.mapResults(response, "movie")); | ||||
|   } | ||||
|  | ||||
|   /** | ||||
| @@ -156,15 +197,13 @@ class TMDB { | ||||
|    * @param {Number} page representing pagination of results | ||||
|    * @returns {Promise} dict with query results, current page and total_pages | ||||
|    */ | ||||
|   showSearch(query, page=1) { | ||||
|     const tmdbquery = { query: query, page: page }; | ||||
|     const cacheKey = `${this.cacheTags.showSearch}:${page}:${query}`; | ||||
|   showSearch(search_query, page = 1, include_adult = true) { | ||||
|     const tmdbquery = { query: search_query, page, include_adult }; | ||||
|     const cacheKey = `tmdb/${this.cacheTags.showSearch}:${page}:${search_query}:${include_adult}`; | ||||
|  | ||||
|     return this.cache.get(cacheKey) | ||||
|       .catch(() => this.tmdb('searchTv', tmdbquery)) | ||||
|       .catch(tmdbError => tmdbErrorResponse(tmdbError, 'tv search results')) | ||||
|       .then(response => this.cache.set(cacheKey, response, 1)) | ||||
|       .then(response => this.mapResults(response, 'show')) | ||||
|     return this.getFromCacheOrFetchFromTmdb(cacheKey, "searchTv", tmdbquery) | ||||
|       .then(response => this.cache.set(cacheKey, response, this.defaultTTL)) | ||||
|       .then(response => this.mapResults(response, "show")); | ||||
|   } | ||||
|  | ||||
|   /** | ||||
| @@ -173,37 +212,31 @@ class TMDB { | ||||
|    * @param {Number} page representing pagination of results | ||||
|    * @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 }; | ||||
|     const cacheKey = `${this.cacheTags.personSearch}:${page}:${query}`; | ||||
|  | ||||
|     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')) | ||||
|     return this.getFromCacheOrFetchFromTmdb(cacheKey, "searchPerson", tmdbquery) | ||||
|       .then(response => this.cache.set(cacheKey, response, this.defaultTTL)) | ||||
|       .then(response => this.mapResults(response, "person")); | ||||
|   } | ||||
|  | ||||
|   movieList(listname, page = 1) { | ||||
|     const query = { page: page }; | ||||
|     const cacheKey = `${this.cacheTags[listname]}:${page}`; | ||||
|     return this.cache.get(cacheKey) | ||||
|       .catch(() => this.tmdb(listname, query)) | ||||
|       .catch(tmdbError => this.tmdbErrorResponse(tmdbError, 'movie list ' + listname)) | ||||
|       .then(response => this.cache.set(cacheKey, response, 1)) | ||||
|       .then(response => this.mapResults(response, 'movie')) | ||||
|     const cacheKey = `tmdb/${this.cacheTags[listname]}:${page}`; | ||||
|  | ||||
|     return this.getFromCacheOrFetchFromTmdb(cacheKey, listname, query) | ||||
|       .then(response => this.cache.set(cacheKey, response, this.defaultTTL)) | ||||
|       .then(response => this.mapResults(response, "movie")); | ||||
|   } | ||||
|  | ||||
|   showList(listname, page = 1) { | ||||
|     const query = { page: page }; | ||||
|     const cacheKey = `${this.cacheTags[listname]}:${page}`; | ||||
|     const cacheKey = `tmdb/${this.cacheTags[listname]}:${page}`; | ||||
|  | ||||
|     return this.cache.get(cacheKey) | ||||
|       .catch(() => this.tmdb(listname, query)) | ||||
|       .catch(tmdbError => this.tmdbErrorResponse(tmdbError, 'show list ' + listname)) | ||||
|       .then(response => this.cache.set(cacheKey, response, 1)) | ||||
|       .then(response => this.mapResults(response, 'show')) | ||||
|     return this.getFromCacheOrFetchFromTmdb(cacheKey, listName, query) | ||||
|       .then(response => this.cache.set(cacheKey, response, this.defaultTTL)) | ||||
|       .then(response => this.mapResults(response, "show")); | ||||
|   } | ||||
|  | ||||
|   /** | ||||
| @@ -212,29 +245,26 @@ class TMDB { | ||||
|    * @param {String} The type declared in listSearch. | ||||
|    * @returns {Promise} dict with tmdb results, mapped as movie/show objects. | ||||
|    */ | ||||
|   mapResults(response, type=undefined) { | ||||
|     // console.log(response.results) | ||||
|     // response.results.map(te => console.table(te)) | ||||
|  | ||||
|   mapResults(response, type = undefined) { | ||||
|     let results = response.results.map(result => { | ||||
|       if (type === 'movie' || result.media_type === 'movie') { | ||||
|         const movie = Movie.convertFromTmdbResponse(result) | ||||
|         return movie.createJsonResponse() | ||||
|       } else if (type === 'show' || result.media_type === 'tv') { | ||||
|         const show = Show.convertFromTmdbResponse(result) | ||||
|         return show.createJsonResponse() | ||||
|       } else if (type === 'person' || result.media_type === 'person') { | ||||
|         const person = Person.convertFromTmdbResponse(result) | ||||
|         return person.createJsonResponse() | ||||
|       if (type === "movie" || result.media_type === "movie") { | ||||
|         const movie = Movie.convertFromTmdbResponse(result); | ||||
|         return movie.createJsonResponse(); | ||||
|       } else if (type === "show" || result.media_type === "tv") { | ||||
|         const show = Show.convertFromTmdbResponse(result); | ||||
|         return show.createJsonResponse(); | ||||
|       } else if (type === "person" || result.media_type === "person") { | ||||
|         const person = Person.convertFromTmdbResponse(result); | ||||
|         return person.createJsonResponse(); | ||||
|       } | ||||
|     }) | ||||
|     }); | ||||
|  | ||||
|     return { | ||||
|       results: results, | ||||
|       page: response.page, | ||||
|       total_results: response.total_results, | ||||
|       total_pages: response.total_pages | ||||
|     } | ||||
|     }; | ||||
|   } | ||||
|  | ||||
|   /** | ||||
| @@ -243,43 +273,21 @@ class TMDB { | ||||
|    * @param {Object} argument argument to function being called | ||||
|    * @returns {Promise} succeeds if callback succeeds | ||||
|    */ | ||||
|    tmdb(method, argument) { | ||||
|       return new Promise((resolve, reject) => { | ||||
|          const callback = (error, reponse) => { | ||||
|             if (error) { | ||||
|                return reject(error); | ||||
|             } | ||||
|             resolve(reponse); | ||||
|          }; | ||||
|   tmdb(method, argument) { | ||||
|     return new Promise((resolve, reject) => { | ||||
|       const callback = (error, reponse) => { | ||||
|         if (error) { | ||||
|           return reject(error); | ||||
|         } | ||||
|         resolve(reponse); | ||||
|       }; | ||||
|  | ||||
|          if (!argument) { | ||||
|             this.tmdbLibrary[method](callback); | ||||
|          } else { | ||||
|             this.tmdbLibrary[method](argument, callback); | ||||
|          } | ||||
|       }); | ||||
|    } | ||||
|  | ||||
| } | ||||
|  | ||||
| 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` | ||||
|       if (!argument) { | ||||
|         this.tmdbLibrary[method](callback); | ||||
|       } else { | ||||
|         this.tmdbLibrary[method](argument, callback); | ||||
|       } | ||||
|     }); | ||||
|   } | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| import Movie from './types/movie.js' | ||||
| import Show from './types/show.js' | ||||
| import Person from './types/person.js' | ||||
| import Credits from './types/credits.js' | ||||
| import ReleaseDates from './types/releaseDates.js' | ||||
| const Movie = require('./types/movie.js') | ||||
| const Show = require('./types/show.js') | ||||
| const Person = require('./types/person.js') | ||||
| const Credits = require('./types/credits.js') | ||||
| const ReleaseDates = require('./types/releaseDates.js') | ||||
|  | ||||
| module.exports = { Movie, Show, Person, Credits, ReleaseDates } | ||||
|   | ||||
| @@ -1,20 +1,55 @@ | ||||
| class Credits {  | ||||
|   constructor(id, cast=[], crew=[]) { | ||||
| import Movie from "./movie"; | ||||
| import Show from "./show"; | ||||
|  | ||||
| class Credits { | ||||
|   constructor(id, cast = [], crew = []) { | ||||
|     this.id = id; | ||||
|     this.cast = cast; | ||||
|     this.crew = crew; | ||||
|     this.type = 'credits'; | ||||
|     this.type = "credits"; | ||||
|   } | ||||
|  | ||||
|   static convertFromTmdbResponse(response) { | ||||
|     const { id, cast, crew } = response; | ||||
|  | ||||
|     const allCast = cast.map(cast =>  | ||||
|       new CastMember(cast.character, cast.gender, cast.id, cast.name, cast.profile_path)) | ||||
|     const allCrew = crew.map(crew => | ||||
|       new CrewMember(crew.department, crew.gender, crew.id, crew.job, crew.name, crew.profile_path)) | ||||
|     const allCast = cast.map(cast => { | ||||
|       if (cast["media_type"]) { | ||||
|         if (cast.media_type === "movie") { | ||||
|           return CreditedMovie.convertFromTmdbResponse(cast); | ||||
|         } else if (cast.media_type === "tv") { | ||||
|           return CreditedShow.convertFromTmdbResponse(cast); | ||||
|         } | ||||
|       } | ||||
|  | ||||
|     return new 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() { | ||||
| @@ -22,7 +57,7 @@ class Credits { | ||||
|       id: this.id, | ||||
|       cast: this.cast.map(cast => cast.createJsonResponse()), | ||||
|       crew: this.crew.map(crew => crew.createJsonResponse()) | ||||
|     } | ||||
|     }; | ||||
|   } | ||||
| } | ||||
|  | ||||
| @@ -33,7 +68,7 @@ class CastMember { | ||||
|     this.id = id; | ||||
|     this.name = name; | ||||
|     this.profile_path = profile_path; | ||||
|     this.type = 'cast member'; | ||||
|     this.type = "person"; | ||||
|   } | ||||
|  | ||||
|   createJsonResponse() { | ||||
| @@ -44,7 +79,7 @@ class CastMember { | ||||
|       name: this.name, | ||||
|       profile_path: this.profile_path, | ||||
|       type: this.type | ||||
|     } | ||||
|     }; | ||||
|   } | ||||
| } | ||||
|  | ||||
| @@ -56,7 +91,7 @@ class CrewMember { | ||||
|     this.job = job; | ||||
|     this.name = name; | ||||
|     this.profile_path = profile_path; | ||||
|     this.type = 'crew member'; | ||||
|     this.type = "person"; | ||||
|   } | ||||
|  | ||||
|   createJsonResponse() { | ||||
| @@ -68,8 +103,11 @@ class CrewMember { | ||||
|       name: this.name, | ||||
|       profile_path: this.profile_path, | ||||
|       type: this.type | ||||
|     } | ||||
|     }; | ||||
|   } | ||||
| } | ||||
|  | ||||
| class CreditedMovie extends Movie {} | ||||
| class CreditedShow extends Show {} | ||||
|  | ||||
| module.exports = Credits; | ||||
|   | ||||
| @@ -1,23 +1,54 @@ | ||||
| class Person {  | ||||
|   constructor(id, name, poster=undefined, birthday=undefined, deathday=undefined, | ||||
|               adult=undefined, knownForDepartment=undefined) { | ||||
| class Person { | ||||
|   constructor( | ||||
|     id, | ||||
|     name, | ||||
|     poster = undefined, | ||||
|     birthday = undefined, | ||||
|     deathday = undefined, | ||||
|     adult = undefined, | ||||
|     placeOfBirth = undefined, | ||||
|     biography = undefined, | ||||
|     knownForDepartment = undefined | ||||
|   ) { | ||||
|     this.id = id; | ||||
|     this.name = name; | ||||
|     this.poster = poster; | ||||
|     this.birthday = birthday; | ||||
|     this.deathday = deathday; | ||||
|     this.adult = adult; | ||||
|     this.placeOfBirth = placeOfBirth; | ||||
|     this.biography = biography; | ||||
|     this.knownForDepartment = knownForDepartment; | ||||
|     this.type = 'person'; | ||||
|     this.type = "person"; | ||||
|   } | ||||
|  | ||||
|   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 deathDay = deathday ? new Date(deathday) : null | ||||
|     const birthDay = new Date(birthday); | ||||
|     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() { | ||||
| @@ -27,10 +58,12 @@ class Person { | ||||
|       poster: this.poster, | ||||
|       birthday: this.birthday, | ||||
|       deathday: this.deathday, | ||||
|       place_of_birth: this.placeOfBirth, | ||||
|       biography: this.biography, | ||||
|       known_for_department: this.knownForDepartment, | ||||
|       adult: this.adult, | ||||
|       type: this.type | ||||
|     } | ||||
|     }; | ||||
|   } | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -1,26 +1,25 @@ | ||||
| const User = require('src/user/user'); | ||||
| const jwt = require('jsonwebtoken'); | ||||
| const User = require("src/user/user"); | ||||
| const jwt = require("jsonwebtoken"); | ||||
|  | ||||
| class Token { | ||||
|   constructor(user, admin=false) { | ||||
|   constructor(user, admin = false, settings = null) { | ||||
|     this.user = user; | ||||
|     this.admin = admin; | ||||
|     this.settings = settings; | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|     * Generate a new token. | ||||
|     * @param {String} secret a cipher of the token | ||||
|     * @returns {String} | ||||
|     */ | ||||
|    * Generate a new token. | ||||
|    * @param {String} secret a cipher of the token | ||||
|    * @returns {String} | ||||
|    */ | ||||
|   toString(secret) { | ||||
|     const username = this.user.username; | ||||
|     const admin = this.admin; | ||||
|     let data = { username } | ||||
|     const { user, admin, settings } = this; | ||||
|  | ||||
|     if (admin) | ||||
|       data = { ...data, admin } | ||||
|     let data = { username: user.username, settings }; | ||||
|     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} | ||||
|    */ | ||||
|   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 }) | ||||
|     if (token.username === undefined || token.username === null) | ||||
|       throw new Error('Malformed token') | ||||
|  | ||||
|     username = token.username | ||||
|     const user = new User(username) | ||||
|     return new Token(user) | ||||
|     const { username, admin, settings } = token; | ||||
|     const user = new User(username); | ||||
|     return new Token(user, admin, settings); | ||||
|   } | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -1,65 +1,256 @@ | ||||
| const assert = require('assert'); | ||||
| const establishedDatabase = require('src/database/database'); | ||||
| const assert = require("assert"); | ||||
| const establishedDatabase = require("src/database/database"); | ||||
|  | ||||
| class UserRepository { | ||||
|    constructor(database) { | ||||
|       this.database = database || establishedDatabase; | ||||
|       this.queries = { | ||||
|          read: 'select * from user where lower(user_name) = lower(?)', | ||||
|          create: 'insert into user (user_name) values (?)', | ||||
|          change: 'update user set password = ? where user_name = ?', | ||||
|          retrieveHash: 'select * from user where user_name = ?', | ||||
|          getAdminStateByUser: 'select admin from user where user_name = ?' | ||||
|       }; | ||||
|    } | ||||
|   constructor(database) { | ||||
|     this.database = database || establishedDatabase; | ||||
|     this.queries = { | ||||
|       read: "select * from user where lower(user_name) = lower(?)", | ||||
|       create: "insert into user (user_name) values (?)", | ||||
|       change: "update user set password = ? where user_name = ?", | ||||
|       retrieveHash: "select * from user where user_name = ?", | ||||
|       getAdminStateByUser: "select admin from user where user_name = ?", | ||||
|       link: "update settings set plex_userid = ? where user_name = ?", | ||||
|       unlink: "update settings set plex_userid = null where user_name = ?", | ||||
|       createSettings: "insert into settings (user_name) values (?)", | ||||
|       updateSettings: | ||||
|         "update settings set user_name = ?, dark_mode = ?, emoji = ?", | ||||
|       getSettings: "select * from settings where user_name = ?" | ||||
|     }; | ||||
|   } | ||||
|  | ||||
|    /** | ||||
|   /** | ||||
|    * Create a user in a database. | ||||
|    * @param {User} user the user you want to create | ||||
|    * @returns {Promise} | ||||
|    */ | ||||
|    create(user) { | ||||
|       return Promise.resolve() | ||||
|          .then(() => this.database.get(this.queries.read, user.username)) | ||||
|          .then(() => this.database.run(this.queries.create, user.username)) | ||||
|          .catch((error) => { | ||||
|             if (error.name === 'AssertionError' || error.message.endsWith('user_name')) { | ||||
|                throw new Error('That username is already registered'); | ||||
|             } | ||||
|             throw Error(error) | ||||
|          }); | ||||
|    } | ||||
|   create(user) { | ||||
|     return this.database | ||||
|       .get(this.queries.read, user.username) | ||||
|       .then(() => this.database.run(this.queries.create, user.username)) | ||||
|       .catch(error => { | ||||
|         if ( | ||||
|           error.name === "AssertionError" || | ||||
|           error.message.endsWith("user_name") | ||||
|         ) { | ||||
|           throw new Error("That username is already registered"); | ||||
|         } | ||||
|         throw Error(error); | ||||
|       }); | ||||
|   } | ||||
|  | ||||
|    /** | ||||
|   /** | ||||
|    * Retrieve a password from a database. | ||||
|    * @param {User} user the user you want to retrieve the password | ||||
|    * @returns {Promise} | ||||
|    */ | ||||
|    retrieveHash(user) { | ||||
|       return Promise.resolve() | ||||
|          .then(() => this.database.get(this.queries.retrieveHash, user.username)) | ||||
|          .then((row) => { | ||||
|             assert(row, 'The user does not exist.'); | ||||
|             return row.password; | ||||
|          }) | ||||
|          .catch((err) => { console.log(error); throw new Error('Unable to find your user.'); }); | ||||
|    } | ||||
|   retrieveHash(user) { | ||||
|     return this.database | ||||
|       .get(this.queries.retrieveHash, user.username) | ||||
|       .then(row => { | ||||
|         assert(row, "The user does not exist."); | ||||
|         return row.password; | ||||
|       }) | ||||
|       .catch(err => { | ||||
|         console.log(error); | ||||
|         throw new Error("Unable to find your user."); | ||||
|       }); | ||||
|   } | ||||
|  | ||||
|    /** | ||||
|   /** | ||||
|    * Change a user's password in a database. | ||||
|    * @param {User} user the user you want to create | ||||
|    * @param {String} password the new password you want to change | ||||
|    * @returns {Promise} | ||||
|    */ | ||||
|    changePassword(user, password) { | ||||
|       return Promise.resolve(this.database.run(this.queries.change, [password, user.username])); | ||||
|    } | ||||
|   changePassword(user, password) { | ||||
|     return this.database.run(this.queries.change, [password, user.username]); | ||||
|   } | ||||
|  | ||||
|    checkAdmin(user) { | ||||
|       return this.database.get(this.queries.getAdminStateByUser, user.username).then((row) => { | ||||
|          return row.admin; | ||||
|       }); | ||||
|    } | ||||
|   /** | ||||
|    * Link plex userid with seasoned user | ||||
|    * @param {String} username the user you want to lunk plex userid with | ||||
|    * @param {Number} plexUserID plex unique id | ||||
|    * @returns {Promsie} | ||||
|    */ | ||||
|   linkPlexUserId(username, plexUserID) { | ||||
|     return new Promise((resolve, reject) => { | ||||
|       this.database | ||||
|         .run(this.queries.link, [plexUserID, username]) | ||||
|         .then(row => resolve(row)) | ||||
|         .catch(error => { | ||||
|           // TODO log this unknown db error | ||||
|           console.error("db error", error); | ||||
|  | ||||
|           reject({ | ||||
|             status: 500, | ||||
|             message: | ||||
|               "An unexpected error occured while linking plex and seasoned accounts", | ||||
|             source: "seasoned database" | ||||
|           }); | ||||
|         }); | ||||
|     }); | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Unlink plex userid with seasoned user | ||||
|    * @param {User} user the user you want to lunk plex userid with | ||||
|    * @returns {Promsie} | ||||
|    */ | ||||
|   unlinkPlexUserId(username) { | ||||
|     return new Promise((resolve, reject) => { | ||||
|       this.database | ||||
|         .run(this.queries.unlink, username) | ||||
|         .then(row => resolve(row)) | ||||
|         .catch(error => { | ||||
|           // TODO log this unknown db error | ||||
|           console.log("db error", error); | ||||
|  | ||||
|           reject({ | ||||
|             status: 500, | ||||
|             message: | ||||
|               "An unexpected error occured while unlinking plex and seasoned accounts", | ||||
|             source: "seasoned database" | ||||
|           }); | ||||
|         }); | ||||
|     }); | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Check if the user has boolean flag set for admin in database | ||||
|    * @param {User} user object | ||||
|    * @returns {Promsie} | ||||
|    */ | ||||
|   checkAdmin(user) { | ||||
|     return this.database | ||||
|       .get(this.queries.getAdminStateByUser, user.username) | ||||
|       .then(row => row.admin); | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Get settings for user matching string username | ||||
|    * @param {String} username | ||||
|    * @returns {Promsie} | ||||
|    */ | ||||
|   getSettings(username) { | ||||
|     return new Promise((resolve, reject) => { | ||||
|       this.database | ||||
|         .get(this.queries.getSettings, username) | ||||
|         .then(async row => { | ||||
|           if (row == null) { | ||||
|             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 | ||||
|             ); | ||||
|             if (userExistsWithUsername !== undefined) { | ||||
|               try { | ||||
|                 resolve(this.dbCreateSettings(username)); | ||||
|               } catch (error) { | ||||
|                 reject(error); | ||||
|               } | ||||
|             } else { | ||||
|               reject({ | ||||
|                 status: 404, | ||||
|                 message: "User not found, no settings to get" | ||||
|               }); | ||||
|             } | ||||
|           } | ||||
|  | ||||
|           resolve(row); | ||||
|         }) | ||||
|         .catch(error => { | ||||
|           console.error( | ||||
|             "Unexpected error occured while fetching settings for your account. Error:", | ||||
|             error | ||||
|           ); | ||||
|           reject({ | ||||
|             status: 500, | ||||
|             message: | ||||
|               "An unexpected error occured while fetching settings for your account", | ||||
|             source: "seasoned database" | ||||
|           }); | ||||
|         }); | ||||
|     }); | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Update settings values for user matching string username | ||||
|    * @param {String} username | ||||
|    * @param {String} dark_mode | ||||
|    * @param {String} emoji | ||||
|    * @returns {Promsie} | ||||
|    */ | ||||
|   updateSettings(username, dark_mode = undefined, emoji = undefined) { | ||||
|     const settings = this.getSettings(username); | ||||
|     dark_mode = dark_mode !== undefined ? dark_mode : settings.dark_mode; | ||||
|     emoji = emoji !== undefined ? emoji : settings.emoji; | ||||
|  | ||||
|     return this.dbUpdateSettings(username, dark_mode, emoji).catch(error => { | ||||
|       if (error.status && error.message) { | ||||
|         return error; | ||||
|       } | ||||
|  | ||||
|       return { | ||||
|         status: 500, | ||||
|         message: | ||||
|           "An unexpected error occured while updating settings for your account" | ||||
|       }; | ||||
|     }); | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Helper function for creating settings in the database | ||||
|    * @param {String} username | ||||
|    * @returns {Promsie} | ||||
|    */ | ||||
|   dbCreateSettings(username) { | ||||
|     return this.database | ||||
|       .run(this.queries.createSettings, username) | ||||
|       .then(() => this.database.get(this.queries.getSettings, username)) | ||||
|       .catch(error => | ||||
|         rejectUnexpectedDatabaseError( | ||||
|           "Unexpected error occured while creating settings", | ||||
|           503, | ||||
|           error | ||||
|         ) | ||||
|       ); | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Helper function for updating settings in the database | ||||
|    * @param {String} username | ||||
|    * @returns {Promsie} | ||||
|    */ | ||||
|   dbUpdateSettings(username, dark_mode, emoji) { | ||||
|     return new Promise((resolve, reject) => | ||||
|       this.database | ||||
|         .run(this.queries.updateSettings, [username, dark_mode, emoji]) | ||||
|         .then(row => resolve(row)) | ||||
|     ); | ||||
|   } | ||||
| } | ||||
|  | ||||
| const rejectUnexpectedDatabaseError = ( | ||||
|   message, | ||||
|   status, | ||||
|   error, | ||||
|   reject = null | ||||
| ) => { | ||||
|   console.error(error); | ||||
|   const body = { | ||||
|     status, | ||||
|     message, | ||||
|     source: "seasoned database" | ||||
|   }; | ||||
|  | ||||
|   if (reject == null) { | ||||
|     return new Promise((resolve, reject) => reject(body)); | ||||
|   } | ||||
|   reject(body); | ||||
| }; | ||||
|  | ||||
| module.exports = UserRepository; | ||||
|   | ||||
| @@ -1,10 +1,10 @@ | ||||
| const bcrypt = require('bcrypt'); | ||||
| const UserRepository = require('src/user/userRepository'); | ||||
| const bcrypt = require("bcrypt"); | ||||
| const UserRepository = require("src/user/userRepository"); | ||||
|  | ||||
| class UserSecurity { | ||||
|   constructor(database) { | ||||
|   this.userRepository = new UserRepository(database); | ||||
| } | ||||
|     this.userRepository = new UserRepository(database); | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Create a new user in PlanFlix. | ||||
| @@ -13,15 +13,15 @@ class UserSecurity { | ||||
|    * @returns {Promise} | ||||
|    */ | ||||
|   createNewUser(user, clearPassword) { | ||||
|     if (user.username.trim() === '') { | ||||
|       throw new Error('The username is empty.'); | ||||
|     } else if (clearPassword.trim() === '') { | ||||
|       throw new Error('The password is empty.'); | ||||
|     if (user.username.trim() === "") { | ||||
|       throw new Error("The username is empty."); | ||||
|     } else if (clearPassword.trim() === "") { | ||||
|       throw new Error("The password is empty."); | ||||
|     } else { | ||||
|       return Promise.resolve() | ||||
|         .then(() => this.userRepository.create(user)) | ||||
|       return this.userRepository | ||||
|         .create(user) | ||||
|         .then(() => UserSecurity.hashPassword(clearPassword)) | ||||
|         .then(hash => this.userRepository.changePassword(user, hash)) | ||||
|         .then(hash => this.userRepository.changePassword(user, hash)); | ||||
|     } | ||||
|   } | ||||
|  | ||||
| @@ -32,24 +32,25 @@ class UserSecurity { | ||||
|    * @returns {Promise} | ||||
|    */ | ||||
|   login(user, clearPassword) { | ||||
|     return Promise.resolve() | ||||
|       .then(() => this.userRepository.retrieveHash(user)) | ||||
|     return this.userRepository | ||||
|       .retrieveHash(user) | ||||
|       .then(hash => UserSecurity.compareHashes(hash, clearPassword)) | ||||
|       .catch(() => { throw new Error('Incorrect username or password.'); }); | ||||
|       .catch(() => { | ||||
|         throw new Error("Incorrect username or password."); | ||||
|       }); | ||||
|   } | ||||
|  | ||||
|    /** | ||||
|     * Compare between a password and a hash password from database. | ||||
|     * @param {String} hash the hash password from database | ||||
|     * @param {String} clearPassword the user's password | ||||
|     * @returns {Promise} | ||||
|     */ | ||||
|   /** | ||||
|    * Compare between a password and a hash password from database. | ||||
|    * @param {String} hash the hash password from database | ||||
|    * @param {String} clearPassword the user's password | ||||
|    * @returns {Promise} | ||||
|    */ | ||||
|   static compareHashes(hash, clearPassword) { | ||||
|     return new Promise((resolve, reject) => { | ||||
|       bcrypt.compare(clearPassword, hash, (error, match) => { | ||||
|         if (match) | ||||
|           resolve() | ||||
|         reject() | ||||
|         if (match) resolve(true); | ||||
|         reject(false); | ||||
|       }); | ||||
|     }); | ||||
|   } | ||||
| @@ -60,9 +61,11 @@ class UserSecurity { | ||||
|    * @returns {Promise} | ||||
|    */ | ||||
|   static hashPassword(clearPassword) { | ||||
|     return new Promise((resolve) => { | ||||
|     return new Promise(resolve => { | ||||
|       const saltRounds = 10; | ||||
|       bcrypt.hash(clearPassword, saltRounds, (error, hash) => { | ||||
|         if (error) reject(error); | ||||
|  | ||||
|         resolve(hash); | ||||
|       }); | ||||
|     }); | ||||
|   | ||||
| @@ -1,133 +1,239 @@ | ||||
| const express = require('express'); | ||||
| const Raven = require('raven'); | ||||
| const bodyParser = require('body-parser'); | ||||
| const tokenToUser = require('./middleware/tokenToUser'); | ||||
| const mustBeAuthenticated = require('./middleware/mustBeAuthenticated'); | ||||
| const mustBeAdmin = require('./middleware/mustBeAdmin'); | ||||
| const configuration = require('src/config/configuration').getInstance(); | ||||
| const express = require("express"); | ||||
| const Raven = require("raven"); | ||||
| const cookieParser = require("cookie-parser"); | ||||
| const bodyParser = require("body-parser"); | ||||
|  | ||||
| const listController = require('./controllers/list/listController'); | ||||
| const configuration = require("src/config/configuration").getInstance(); | ||||
|  | ||||
| const reqTokenToUser = require("./middleware/reqTokenToUser"); | ||||
| 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. | ||||
| Raven.config(configuration.get('raven', 'DSN')).install(); | ||||
| Raven.config(configuration.get("raven", "DSN")).install(); | ||||
|  | ||||
| const app = express(); // define our app using express | ||||
| app.use(Raven.requestHandler()); | ||||
| app.use(bodyParser.json()); | ||||
| app.use(cookieParser()); | ||||
|  | ||||
| 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 | ||||
| // router.use(bodyParser.json()); | ||||
| app.use(bodyParser.urlencoded({ extended: true })); | ||||
|  | ||||
| /* Decode the Authorization header if provided */ | ||||
| router.use(tokenToUser); | ||||
| /* Check header and cookie for authentication and set req.loggedInUser */ | ||||
| router.use(reqTokenToUser); | ||||
|  | ||||
| // TODO: Should have a separate middleware/router for handling headers. | ||||
| router.use((req, res, next) => { | ||||
|    // TODO add logging of all incoming | ||||
|   const origin = req.headers.origin; | ||||
|    if (allowedOrigins.indexOf(origin) > -1) { | ||||
|        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'); | ||||
|   // TODO add logging of all incoming | ||||
|   // const origin = req.headers.origin; | ||||
|   // if (allowedOrigins.indexOf(origin) > -1) { | ||||
|   //   res.setHeader("Access-Control-Allow-Origin", origin); | ||||
|   // } | ||||
|  | ||||
|    next(); | ||||
|   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(); | ||||
| }); | ||||
|  | ||||
| router.get('/', function mainHandler(req, res) { | ||||
|    throw new Error('Broke!'); | ||||
| router.get("/", (req, res) => { | ||||
|   res.send("welcome to seasoned api"); | ||||
| }); | ||||
|  | ||||
| app.use(Raven.errorHandler()); | ||||
| app.use(function onError(err, req, res, next) { | ||||
|    res.statusCode = 500; | ||||
|    res.end(res.sentry + '\n'); | ||||
| app.use((err, req, res, next) => { | ||||
|   res.statusCode = 500; | ||||
|   res.end(res.sentry + "\n"); | ||||
| }); | ||||
|  | ||||
| /** | ||||
|  * User | ||||
|  */ | ||||
| router.post('/v1/user', require('./controllers/user/register.js')); | ||||
| router.post('/v1/user/login', require('./controllers/user/login.js')); | ||||
| router.get('/v1/user/history', mustBeAuthenticated, require('./controllers/user/history.js')); | ||||
| router.get('/v1/user/requests', mustBeAuthenticated, require('./controllers/user/requests.js')); | ||||
| router.post("/v1/user", require("./controllers/user/register.js")); | ||||
| router.post("/v1/user/login", require("./controllers/user/login.js")); | ||||
| router.post("/v1/user/logout", require("./controllers/user/logout.js")); | ||||
| router.get( | ||||
|   "/v1/user/settings", | ||||
|   mustBeAuthenticated, | ||||
|   SettingsController.getSettingsController | ||||
| ); | ||||
| 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( | ||||
|   "/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 | ||||
|  */ | ||||
| router.get('/v1/seasoned/all', require('./controllers/seasoned/readStrays.js')); | ||||
| router.get('/v1/seasoned/:strayId', require('./controllers/seasoned/strayById.js')); | ||||
| router.post('/v1/seasoned/verify/:strayId', require('./controllers/seasoned/verifyStray.js')); | ||||
| router.get("/v1/seasoned/all", require("./controllers/seasoned/readStrays.js")); | ||||
| router.get( | ||||
|   "/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/movie', require('./controllers/search/movieSearch.js')); | ||||
| router.get('/v2/search/show', require('./controllers/search/showSearch.js')); | ||||
| router.get('/v2/search/person', require('./controllers/search/personSearch.js')); | ||||
| router.get("/v2/search/", require("./controllers/search/multiSearch.js")); | ||||
| router.get("/v2/search/movie", require("./controllers/search/movieSearch.js")); | ||||
| router.get("/v2/search/show", require("./controllers/search/showSearch.js")); | ||||
| router.get( | ||||
|   "/v2/search/person", | ||||
|   require("./controllers/search/personSearch.js") | ||||
| ); | ||||
|  | ||||
| router.get('/v2/movie/now_playing', listController.nowPlayingMovies); | ||||
| router.get('/v2/movie/popular', listController.popularMovies); | ||||
| router.get('/v2/movie/top_rated', listController.topRatedMovies); | ||||
| router.get('/v2/movie/upcoming', listController.upcomingMovies); | ||||
| router.get("/v2/movie/now_playing", listController.nowPlayingMovies); | ||||
| router.get("/v2/movie/popular", listController.popularMovies); | ||||
| router.get("/v2/movie/top_rated", listController.topRatedMovies); | ||||
| router.get("/v2/movie/upcoming", listController.upcomingMovies); | ||||
| router.get("/v2/movie/:id/credits", require("./controllers/movie/credits.js")); | ||||
| router.get( | ||||
|   "/v2/movie/:id/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/popular', listController.popularShows); | ||||
| router.get('/v2/show/top_rated', listController.topRatedShows); | ||||
| router.get("/v2/show/now_playing", listController.nowPlayingShows); | ||||
| router.get("/v2/show/popular", listController.popularShows); | ||||
| router.get("/v2/show/top_rated", listController.topRatedShows); | ||||
| router.get("/v2/show/:id/credits", require("./controllers/show/credits.js")); | ||||
| router.get("/v2/show/:id", require("./controllers/show/info.js")); | ||||
|  | ||||
| router.get('/v2/movie/:id/credits', require('./controllers/movie/credits.js')); | ||||
| router.get('/v2/movie/:id/release_dates', require('./controllers/movie/releaseDates.js')); | ||||
| router.get('/v2/show/:id/credits', require('./controllers/show/credits.js')); | ||||
|  | ||||
| router.get('/v2/movie/:id', require('./controllers/movie/info.js')); | ||||
| router.get('/v2/show/:id', require('./controllers/show/info.js')); | ||||
| router.get('/v2/person/:id', require('./controllers/person/info.js')); | ||||
| router.get( | ||||
|   "/v2/person/:id/credits", | ||||
|   require("./controllers/person/credits.js") | ||||
| ); | ||||
| router.get("/v2/person/:id", require("./controllers/person/info.js")); | ||||
|  | ||||
| /** | ||||
|  * Plex | ||||
|  */ | ||||
| router.get('/v2/plex/search', require('./controllers/plex/search')); | ||||
| router.get("/v2/plex/search", require("./controllers/plex/search")); | ||||
|  | ||||
| /** | ||||
|  * List | ||||
|  */ | ||||
| router.get('/v1/plex/search', require('./controllers/plex/searchMedia.js')); | ||||
| router.get('/v1/plex/playing', require('./controllers/plex/plexPlaying.js')); | ||||
| router.get('/v1/plex/request', require('./controllers/plex/searchRequest.js')); | ||||
| router.get('/v1/plex/request/:mediaId', 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/search", require("./controllers/plex/searchMedia.js")); | ||||
| router.get("/v1/plex/playing", require("./controllers/plex/plexPlaying.js")); | ||||
| router.get("/v1/plex/request", require("./controllers/plex/searchRequest.js")); | ||||
| router.get( | ||||
|   "/v1/plex/request/:mediaId", | ||||
|   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 | ||||
|  */ | ||||
|  | ||||
| router.get('/v2/request', require('./controllers/request/fetchAllRequests.js')); | ||||
| router.get('/v2/request/:id', require('./controllers/request/getRequest.js')); | ||||
| router.post('/v2/request', require('./controllers/request/requestTmdbId.js')); | ||||
| router.get('/v1/plex/requests/all', require('./controllers/plex/fetchRequested.js')); | ||||
| router.put('/v1/plex/request/:requestId', mustBeAuthenticated, require('./controllers/plex/updateRequested.js')); | ||||
| router.get("/v2/request", require("./controllers/request/fetchAllRequests.js")); | ||||
| router.get("/v2/request/:id", require("./controllers/request/getRequest.js")); | ||||
| router.post("/v2/request", require("./controllers/request/requestTmdbId.js")); | ||||
| router.get( | ||||
|   "/v1/plex/requests/all", | ||||
|   require("./controllers/plex/fetchRequested.js") | ||||
| ); | ||||
| router.put( | ||||
|   "/v1/plex/request/:requestId", | ||||
|   mustBeAuthenticated, | ||||
|   require("./controllers/plex/updateRequested.js") | ||||
| ); | ||||
|  | ||||
| /** | ||||
|  * Pirate | ||||
|  */ | ||||
| router.get('/v1/pirate/search', mustBeAuthenticated, require('./controllers/pirate/searchTheBay.js')); | ||||
| router.post('/v1/pirate/add', mustBeAuthenticated, require('./controllers/pirate/addMagnet.js')); | ||||
| router.get( | ||||
|   "/v1/pirate/search", | ||||
|   mustBeAuthenticated, | ||||
|   require("./controllers/pirate/searchTheBay.js") | ||||
| ); | ||||
| router.post( | ||||
|   "/v1/pirate/add", | ||||
|   mustBeAuthenticated, | ||||
|   require("./controllers/pirate/addMagnet.js") | ||||
| ); | ||||
|  | ||||
| /** | ||||
|  * git | ||||
|  */ | ||||
| router.post('/v1/git/dump', require('./controllers/git/dumpHook.js')); | ||||
| router.post("/v1/git/dump", require("./controllers/git/dumpHook.js")); | ||||
|  | ||||
| /** | ||||
|  * misc | ||||
|  */ | ||||
|  router.get('/v1/emoji', require('./controllers/misc/emoji.js')); | ||||
| router.get("/v1/emoji", require("./controllers/misc/emoji.js")); | ||||
|  | ||||
| // REGISTER OUR ROUTES ------------------------------- | ||||
| // all of our routes will be prefixed with /api | ||||
| app.use('/api', router); | ||||
| app.use("/api", router); | ||||
|  | ||||
| module.exports = app; | ||||
|   | ||||
| @@ -1,8 +1,6 @@ | ||||
| const configuration = require('src/config/configuration').getInstance(); | ||||
| const Cache = require('src/tmdb/cache'); | ||||
| const TMDB = require('src/tmdb/tmdb'); | ||||
| const cache = new Cache(); | ||||
| const tmdb = new TMDB(cache, configuration.get('tmdb', 'apiKey')); | ||||
| const tmdb = new TMDB(configuration.get('tmdb', 'apiKey')); | ||||
|  | ||||
| // there should be a translate function from query params to  | ||||
| // 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 Cache = require('src/tmdb/cache'); | ||||
| const TMDB = require('src/tmdb/tmdb'); | ||||
|  | ||||
| const cache = new Cache(); | ||||
| const tmdb = new TMDB(cache, configuration.get('tmdb', 'apiKey')); | ||||
| const tmdb = new TMDB(configuration.get('tmdb', 'apiKey')); | ||||
|  | ||||
| const movieCreditsController = (req, res) => { | ||||
|   const movieId = req.params.id; | ||||
| @@ -23,4 +21,4 @@ const movieCreditsController = (req, res) => { | ||||
|     }) | ||||
| } | ||||
|  | ||||
| module.exports = movieCreditsController; | ||||
| module.exports = movieCreditsController; | ||||
|   | ||||
| @@ -1,19 +1,19 @@ | ||||
| const configuration = require('src/config/configuration').getInstance(); | ||||
| const Cache = require('src/tmdb/cache'); | ||||
| const TMDB = require('src/tmdb/tmdb'); | ||||
| const Plex = require('src/plex/plex'); | ||||
| const cache = new Cache(); | ||||
| const tmdb = new TMDB(cache, configuration.get('tmdb', 'apiKey')); | ||||
| const plex = new Plex(configuration.get('plex', 'ip')); | ||||
| const configuration = require("src/config/configuration").getInstance(); | ||||
| const TMDB = require("src/tmdb/tmdb"); | ||||
| const Plex = require("src/plex/plex"); | ||||
| const tmdb = new TMDB(configuration.get("tmdb", "apiKey")); | ||||
| const plex = new Plex(configuration.get("plex", "ip")); | ||||
|  | ||||
| function handleError(error, res) { | ||||
|   const { status, message } = error; | ||||
|  | ||||
|   if (status && message) { | ||||
|     res.status(status).send({ success: false, message }) | ||||
|     res.status(status).send({ success: false, message }); | ||||
|   } else { | ||||
|     console.log('caught movieinfo controller error', error) | ||||
|     res.status(500).send({ message: 'An unexpected error occured while requesting movie info'}) | ||||
|     console.log("caught movieinfo controller error", error); | ||||
|     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; | ||||
|   let { credits, release_dates, check_existance } = req.query; | ||||
|  | ||||
|   credits && credits.toLowerCase() === 'true' ? credits = true : credits = false | ||||
|   release_dates && release_dates.toLowerCase() === 'true' ? release_dates = true : release_dates = false | ||||
|   check_existance && check_existance.toLowerCase() === 'true' ? check_existance = true : check_existance = false | ||||
|   credits && credits.toLowerCase() === "true" | ||||
|     ? (credits = true) | ||||
|     : (credits = false); | ||||
|   release_dates && release_dates.toLowerCase() === "true" | ||||
|     ? (release_dates = true) | ||||
|     : (release_dates = false); | ||||
|   check_existance && check_existance.toLowerCase() === "true" | ||||
|     ? (check_existance = true) | ||||
|     : (check_existance = false); | ||||
|  | ||||
|   let tmdbQueue = [tmdb.movieInfo(movieId)] | ||||
|   if (credits) | ||||
|     tmdbQueue.push(tmdb.movieCredits(movieId)) | ||||
|   if (release_dates) | ||||
|     tmdbQueue.push(tmdb.movieReleaseDates(movieId)) | ||||
|   let tmdbQueue = [tmdb.movieInfo(movieId)]; | ||||
|   if (credits) tmdbQueue.push(tmdb.movieCredits(movieId)); | ||||
|   if (release_dates) tmdbQueue.push(tmdb.movieReleaseDates(movieId)); | ||||
|  | ||||
|   try { | ||||
|     const [ Movie, Credits, ReleaseDates ] = await Promise.all(tmdbQueue) | ||||
|     const [Movie, Credits, ReleaseDates] = await Promise.all(tmdbQueue); | ||||
|  | ||||
|     const movie = Movie.createJsonResponse() | ||||
|     if (Credits) | ||||
|       movie.credits = Credits.createJsonResponse() | ||||
|     const movie = Movie.createJsonResponse(); | ||||
|     if (Credits) movie.credits = Credits.createJsonResponse(); | ||||
|     if (ReleaseDates) | ||||
|       movie.release_dates = ReleaseDates.createJsonResponse().results | ||||
|       movie.release_dates = ReleaseDates.createJsonResponse().results; | ||||
|  | ||||
|     if (check_existance) | ||||
|       movie.exists_in_plex = await plex.existsInPlex(movie) | ||||
|    | ||||
|     res.send(movie) | ||||
|   } catch(error) { | ||||
|     handleError(error, res) | ||||
|     if (check_existance) { | ||||
|       try { | ||||
|         movie.exists_in_plex = await plex.existsInPlex(movie); | ||||
|       } catch (error) { | ||||
|         if (error.status === 401) { | ||||
|           console.log("Unathorized request, check plex server LAN settings"); | ||||
|         } else { | ||||
|           console.log("Unkown error from plex!"); | ||||
|         } | ||||
|         console.log(error); | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     res.send(movie); | ||||
|   } catch (error) { | ||||
|     handleError(error, res); | ||||
|   } | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -1,9 +1,7 @@ | ||||
| const configuration = require('src/config/configuration').getInstance(); | ||||
| const Cache = require('src/tmdb/cache'); | ||||
| const TMDB = require('src/tmdb/tmdb'); | ||||
|  | ||||
| const cache = new Cache(); | ||||
| const tmdb = new TMDB(cache, configuration.get('tmdb', 'apiKey')); | ||||
| const tmdb = new TMDB(configuration.get('tmdb', 'apiKey')); | ||||
|  | ||||
| const movieReleaseDatesController = (req, res) => { | ||||
|   const movieId = req.params.id; | ||||
| @@ -23,4 +21,4 @@ const movieReleaseDatesController = (req, res) => { | ||||
|     }) | ||||
| } | ||||
|  | ||||
| module.exports = movieReleaseDatesController; | ||||
| module.exports = movieReleaseDatesController; | ||||
|   | ||||
							
								
								
									
										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,25 +1,49 @@ | ||||
| const configuration = require('src/config/configuration').getInstance(); | ||||
| const Cache = require('src/tmdb/cache'); | ||||
| const TMDB = require('src/tmdb/tmdb'); | ||||
| const cache = new Cache(); | ||||
| const tmdb = new TMDB(cache, configuration.get('tmdb', 'apiKey')); | ||||
| const configuration = require("src/config/configuration").getInstance(); | ||||
| const TMDB = require("src/tmdb/tmdb"); | ||||
| const tmdb = new TMDB(configuration.get("tmdb", "apiKey")); | ||||
|  | ||||
| function handleError(error, res) { | ||||
|   const { status, message } = error; | ||||
|  | ||||
|   if (status && message) { | ||||
|     res.status(status).send({ success: false, message }); | ||||
|   } else { | ||||
|     console.log("caught personinfo controller error", error); | ||||
|     res.status(500).send({ | ||||
|       message: "An unexpected error occured while requesting person info." | ||||
|     }); | ||||
|   } | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Controller: Retrieve information for a person  | ||||
|  * Controller: Retrieve information for a person | ||||
|  * @param {Request} req http request variable | ||||
|  * @param {Response} res | ||||
|  * @returns {Callback} | ||||
|  */ | ||||
|  | ||||
| function personInfoController(req, res) { | ||||
| async function personInfoController(req, res) { | ||||
|   const personId = req.params.id; | ||||
|   let { credits } = req.query; | ||||
|   arguments; | ||||
|  | ||||
|   credits && credits.toLowerCase() === "true" | ||||
|     ? (credits = true) | ||||
|     : (credits = false); | ||||
|  | ||||
|   tmdb.personInfo(personId) | ||||
|   .then(person => res.send(person.createJsonResponse())) | ||||
|   .catch(error => { | ||||
|     res.status(404).send({ success: false, message: error.message }); | ||||
|   }); | ||||
|   let tmdbQueue = [tmdb.personInfo(personId)]; | ||||
|   if (credits) tmdbQueue.push(tmdb.personCredits(personId)); | ||||
|  | ||||
|   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; | ||||
|   | ||||
| @@ -1,11 +1,11 @@ | ||||
| /* | ||||
| * @Author: KevinMidboe | ||||
| * @Date:   2017-10-21 09:54:31 | ||||
| * @Last Modified by:   KevinMidboe | ||||
| * @Last Modified time: 2018-02-26 19:56:32 | ||||
| */ | ||||
|  * @Author: KevinMidboe | ||||
|  * @Date:   2017-10-21 09:54:31 | ||||
|  * @Last Modified by:   KevinMidboe | ||||
|  * @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(); | ||||
|  | ||||
| /** | ||||
| @@ -15,15 +15,15 @@ const PirateRepository = require('src/pirate/pirateRepository'); | ||||
|  * @returns {Callback} | ||||
|  */ | ||||
| function updateRequested(req, res) { | ||||
|    const { query, page, type } = req.query; | ||||
|   const { query, page, type } = req.query; | ||||
|  | ||||
|    PirateRepository.SearchPiratebay(query, page, type) | ||||
|       .then((result) => { | ||||
|          res.send({ success: true, results: result }); | ||||
|       }) | ||||
|       .catch(error => { | ||||
|          res.status(401).send({ success: false, message: error.message }); | ||||
|       }); | ||||
|   PirateRepository.SearchPiratebay(query, page, type) | ||||
|     .then(result => { | ||||
|       res.send({ success: true, results: result }); | ||||
|     }) | ||||
|     .catch(error => { | ||||
|       res.status(401).send({ success: false, message: error.message }); | ||||
|     }); | ||||
| } | ||||
|  | ||||
| module.exports = updateRequested; | ||||
|   | ||||
| @@ -1,26 +1,24 @@ | ||||
| const SearchHistory = require('src/searchHistory/searchHistory'); | ||||
| const Cache = require('src/tmdb/cache'); | ||||
| const RequestRepository = require('src/plex/requestRepository.js'); | ||||
| const SearchHistory = require("src/searchHistory/searchHistory"); | ||||
| const Cache = require("src/tmdb/cache"); | ||||
| const RequestRepository = require("src/plex/requestRepository.js"); | ||||
|  | ||||
| const cache = new Cache(); | ||||
| const requestRepository = new RequestRepository(cache); | ||||
| const searchHistory = new SearchHistory(); | ||||
|  | ||||
|  | ||||
| function searchRequestController(req, res) { | ||||
|    const user = req.loggedInUser; | ||||
|    const { query, page, type } = req.query; | ||||
|    const username = user === undefined ? undefined : user.username; | ||||
|   const { query, page, type } = req.query; | ||||
|   const username = req.loggedInUser ? req.loggedInUser.username : null; | ||||
|  | ||||
|    Promise.resolve() | ||||
|       .then(() => searchHistory.create(username, query)) | ||||
|       .then(() => requestRepository.search(query, page, type)) | ||||
|       .then((searchResult) => { | ||||
|          res.send(searchResult); | ||||
|       }) | ||||
|       .catch(error => { | ||||
|          res.status(500).send({ success: false, message: error.message }); | ||||
|       }); | ||||
|   Promise.resolve() | ||||
|     .then(() => searchHistory.create(username, query)) | ||||
|     .then(() => requestRepository.search(query, page, type)) | ||||
|     .then(searchResult => { | ||||
|       res.send(searchResult); | ||||
|     }) | ||||
|     .catch(error => { | ||||
|       res.status(500).send({ success: false, message: error.message }); | ||||
|     }); | ||||
| } | ||||
|  | ||||
| module.exports = searchRequestController; | ||||
|   | ||||
| @@ -1,19 +1,16 @@ | ||||
| const configuration = require('src/config/configuration').getInstance() | ||||
| const RequestRepository = require('src/request/request'); | ||||
| const Cache = require('src/tmdb/cache') | ||||
| const TMDB = require('src/tmdb/tmdb') | ||||
| const configuration = require("src/config/configuration").getInstance(); | ||||
| const RequestRepository = require("src/request/request"); | ||||
| const TMDB = require("src/tmdb/tmdb"); | ||||
| const tmdb = new TMDB(configuration.get("tmdb", "apiKey")); | ||||
| const request = new RequestRepository(); | ||||
|  | ||||
| const cache = new Cache() | ||||
| const tmdb = new TMDB(cache, configuration.get('tmdb', 'apiKey')) | ||||
| const request = new RequestRepository() | ||||
| const tmdbMovieInfo = id => { | ||||
|   return tmdb.movieInfo(id); | ||||
| }; | ||||
|  | ||||
| const tmdbMovieInfo = (id) => { | ||||
|   return tmdb.movieInfo(id) | ||||
| } | ||||
|  | ||||
| const tmdbShowInfo = (id) => { | ||||
|   return tmdb.showInfo(id) | ||||
| } | ||||
| const tmdbShowInfo = id => { | ||||
|   return tmdb.showInfo(id); | ||||
| }; | ||||
|  | ||||
| /** | ||||
|  * Controller: POST a media id to be donwloaded | ||||
| @@ -24,28 +21,43 @@ const tmdbShowInfo = (id) => { | ||||
| function submitRequestController(req, res) { | ||||
|   // This is the id that is the param of the url | ||||
|   const id = req.params.mediaId; | ||||
|   const type = req.query.type ? req.query.type.toLowerCase() : undefined | ||||
|   const ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress; | ||||
|   const user_agent = req.headers['user-agent']; | ||||
|   const user = req.loggedInUser; | ||||
|   let mediaFunction = undefined | ||||
|   const type = req.query.type ? req.query.type.toLowerCase() : undefined; | ||||
|   const ip = req.headers["x-forwarded-for"] || req.connection.remoteAddress; | ||||
|   const user_agent = req.headers["user-agent"]; | ||||
|   const username = req.loggedInUser ? req.loggedInUser.username : null; | ||||
|  | ||||
|   if (type === 'movie') { | ||||
|     console.log('movie') | ||||
|     mediaFunction = tmdbMovieInfo | ||||
|   } else if (type === 'show') { | ||||
|     console.log('show') | ||||
|     mediaFunction = tmdbShowInfo | ||||
|   let mediaFunction = undefined; | ||||
|  | ||||
|   if (type === "movie") { | ||||
|     console.log("movie"); | ||||
|     mediaFunction = tmdbMovieInfo; | ||||
|   } else if (type === "show") { | ||||
|     console.log("show"); | ||||
|     mediaFunction = tmdbShowInfo; | ||||
|   } 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) | ||||
|     .then(tmdbMedia => request.requestFromTmdb(tmdbMedia, ip, user_agent, user)) | ||||
|     .then(() => res.send({ success: true, message: 'Media item successfully requested' })) | ||||
|     .catch(err => res.status(500).send({ success: false, message: err.message })) | ||||
|     .then(tmdbMedia => | ||||
|       request.requestFromTmdb(tmdbMedia, ip, user_agent, username) | ||||
|     ) | ||||
|     .then(() => | ||||
|       res.send({ success: true, message: "Media item successfully requested" }) | ||||
|     ) | ||||
|     .catch(err => | ||||
|       res.status(500).send({ success: false, message: err.message }) | ||||
|     ); | ||||
| } | ||||
|  | ||||
| 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 Cache = require('src/tmdb/cache'); | ||||
| const TMDB = require('src/tmdb/tmdb'); | ||||
| const RequestRepository = require('src/request/request'); | ||||
| const cache = new Cache(); | ||||
| const tmdb = new TMDB(cache, configuration.get('tmdb', 'apiKey')); | ||||
| const configuration = require("src/config/configuration").getInstance(); | ||||
| const TMDB = require("src/tmdb/tmdb"); | ||||
| const RequestRepository = require("src/request/request"); | ||||
| const tmdb = new TMDB(configuration.get("tmdb", "apiKey")); | ||||
| const request = new RequestRepository(); | ||||
| // const { sendSMS } = require("src/notifications/sms"); | ||||
|  | ||||
| const tmdbMovieInfo = (id) => { | ||||
|   return tmdb.movieInfo(id) | ||||
| } | ||||
| const tmdbMovieInfo = id => { | ||||
|   return tmdb.movieInfo(id); | ||||
| }; | ||||
|  | ||||
| const tmdbShowInfo = (id) => { | ||||
|   return tmdb.showInfo(id) | ||||
| } | ||||
| const tmdbShowInfo = id => { | ||||
|   return tmdb.showInfo(id); | ||||
| }; | ||||
|  | ||||
| /** | ||||
|  * Controller: Request by id with type param | ||||
| @@ -21,33 +20,48 @@ const tmdbShowInfo = (id) => { | ||||
|  * @returns {Callback} | ||||
|  */ | ||||
| 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 user_agent = req.headers['user-agent']; | ||||
|   const user = req.loggedInUser; | ||||
|   const ip = req.headers["x-forwarded-for"] || req.connection.remoteAddress; | ||||
|   const user_agent = req.headers["user-agent"]; | ||||
|   const username = req.loggedInUser ? req.loggedInUser.username : null; | ||||
|  | ||||
|   let mediaFunction = undefined | ||||
|   let mediaFunction = 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') { | ||||
|     mediaFunction = tmdbMovieInfo | ||||
|   } else if (type === 'show') { | ||||
|     mediaFunction = tmdbShowInfo | ||||
|   if (type === "movie") { | ||||
|     mediaFunction = tmdbMovieInfo; | ||||
|   } else if (type === "show") { | ||||
|     mediaFunction = tmdbShowInfo; | ||||
|   } 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) | ||||
|     // .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(() => res.send({success: true, message: 'Request has been submitted.'})) | ||||
|     .then(tmdbMedia => { | ||||
|       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 => { | ||||
|       res.send({ success: false, message: error.message }); | ||||
|     }) | ||||
|     }); | ||||
| } | ||||
|  | ||||
| module.exports = requestTmdbIdController; | ||||
|   | ||||
| @@ -1,9 +1,7 @@ | ||||
| const configuration = require('src/config/configuration').getInstance(); | ||||
| const Cache = require('src/tmdb/cache'); | ||||
| const TMDB = require('src/tmdb/tmdb'); | ||||
| const SearchHistory = require('src/searchHistory/searchHistory'); | ||||
| const cache = new Cache(); | ||||
| const tmdb = new TMDB(cache, configuration.get('tmdb', 'apiKey')); | ||||
| const configuration = require("src/config/configuration").getInstance(); | ||||
| const TMDB = require("src/tmdb/tmdb"); | ||||
| const SearchHistory = require("src/searchHistory/searchHistory"); | ||||
| const tmdb = new TMDB(configuration.get("tmdb", "apiKey")); | ||||
| const searchHistory = new SearchHistory(); | ||||
|  | ||||
| /** | ||||
| @@ -13,28 +11,30 @@ const searchHistory = new SearchHistory(); | ||||
|  * @returns {Callback} | ||||
|  */ | ||||
| function movieSearchController(req, res) { | ||||
|   const user = req.loggedInUser; | ||||
|   const { query, page } = req.query; | ||||
|   const { query, page, adult } = req.query; | ||||
|   const username = req.loggedInUser ? req.loggedInUser.username : null; | ||||
|   const includeAdult = adult == "true" ? true : false; | ||||
|  | ||||
|   if (user) { | ||||
|     return searchHistory.create(user, query); | ||||
|   if (username) { | ||||
|     searchHistory.create(username, query); | ||||
|   } | ||||
|  | ||||
|   tmdb.movieSearch(query, page) | ||||
|   return tmdb | ||||
|     .movieSearch(query, page, includeAdult) | ||||
|     .then(movieSearchResults => res.send(movieSearchResults)) | ||||
|     .catch(error => { | ||||
|       const { status, message } = error; | ||||
|  | ||||
|       if (status && message) { | ||||
|         res.status(status).send({ success: false, message }) | ||||
|         res.status(status).send({ success: false, message }); | ||||
|       } else { | ||||
|         // TODO log unhandled errors | ||||
|         console.log('caugth movie search controller error', error) | ||||
|         console.log("caugth movie search controller error", error); | ||||
|         res.status(500).send({ | ||||
|           message: `An unexpected error occured while searching movies with query: ${query}` | ||||
|         }) | ||||
|         }); | ||||
|       } | ||||
|     }) | ||||
|     }); | ||||
| } | ||||
|  | ||||
| module.exports = movieSearchController; | ||||
|   | ||||
| @@ -1,16 +1,14 @@ | ||||
| const configuration = require('src/config/configuration').getInstance(); | ||||
| const Cache = require('src/tmdb/cache'); | ||||
| const TMDB = require('src/tmdb/tmdb'); | ||||
| const SearchHistory = require('src/searchHistory/searchHistory'); | ||||
| const cache = new Cache(); | ||||
| const tmdb = new TMDB(cache, configuration.get('tmdb', 'apiKey')); | ||||
| const configuration = require("src/config/configuration").getInstance(); | ||||
| const TMDB = require("src/tmdb/tmdb"); | ||||
| const SearchHistory = require("src/searchHistory/searchHistory"); | ||||
| const tmdb = new TMDB(configuration.get("tmdb", "apiKey")); | ||||
| const searchHistory = new SearchHistory(); | ||||
|  | ||||
| function checkAndCreateJsonResponse(result) { | ||||
|   if (typeof result['createJsonResponse'] === 'function') { | ||||
|     return result.createJsonResponse() | ||||
|   if (typeof result["createJsonResponse"] === "function") { | ||||
|     return result.createJsonResponse(); | ||||
|   } | ||||
|   return result | ||||
|   return result; | ||||
| } | ||||
|  | ||||
| /** | ||||
| @@ -20,26 +18,31 @@ function checkAndCreateJsonResponse(result) { | ||||
|  * @returns {Callback} | ||||
|  */ | ||||
| function multiSearchController(req, res) { | ||||
|   const user = req.loggedInUser; | ||||
|   const { query, page } = req.query; | ||||
|   const { query, page, adult } = req.query; | ||||
|   const username = req.loggedInUser ? req.loggedInUser.username : null; | ||||
|  | ||||
|   if (user) { | ||||
|     searchHistory.create(user, query) | ||||
|   if (username) { | ||||
|     searchHistory.create(username, query); | ||||
|   } | ||||
|  | ||||
|   return tmdb.multiSearch(query, page) | ||||
|   .then(multiSearchResults => res.send(multiSearchResults)) | ||||
|   .catch(error => { | ||||
|     const { status, message } = error; | ||||
|   return tmdb | ||||
|     .multiSearch(query, page, adult) | ||||
|     .then(multiSearchResults => res.send(multiSearchResults)) | ||||
|     .catch(error => { | ||||
|       const { status, message } = error; | ||||
|  | ||||
|     if (status && message) { | ||||
|       res.status(status).send({ success: false, message }) | ||||
|     } else { | ||||
|       // TODO log unhandled errors | ||||
|       console.log('caugth multi search controller error', error) | ||||
|       res.status(500).send({ message: `An unexpected error occured while searching with query: ${query}` }) | ||||
|     } | ||||
|   }) | ||||
|       if (status && message) { | ||||
|         res.status(status).send({ success: false, message }); | ||||
|       } else { | ||||
|         // TODO log unhandled errors | ||||
|         console.log("caugth multi search controller error", error); | ||||
|         res | ||||
|           .status(500) | ||||
|           .send({ | ||||
|             message: `An unexpected error occured while searching with query: ${query}` | ||||
|           }); | ||||
|       } | ||||
|     }); | ||||
| } | ||||
|  | ||||
| module.exports = multiSearchController; | ||||
|   | ||||
| @@ -1,9 +1,7 @@ | ||||
| const configuration = require('src/config/configuration').getInstance(); | ||||
| const Cache = require('src/tmdb/cache'); | ||||
| const TMDB = require('src/tmdb/tmdb'); | ||||
| const SearchHistory = require('src/searchHistory/searchHistory'); | ||||
| const cache = new Cache(); | ||||
| const tmdb = new TMDB(cache, configuration.get('tmdb', 'apiKey')); | ||||
| const configuration = require("src/config/configuration").getInstance(); | ||||
| const TMDB = require("src/tmdb/tmdb"); | ||||
| const SearchHistory = require("src/searchHistory/searchHistory"); | ||||
| const tmdb = new TMDB(configuration.get("tmdb", "apiKey")); | ||||
| const searchHistory = new SearchHistory(); | ||||
|  | ||||
| /** | ||||
| @@ -13,30 +11,30 @@ const searchHistory = new SearchHistory(); | ||||
|  * @returns {Callback} | ||||
|  */ | ||||
| function personSearchController(req, res) { | ||||
|   const user = req.loggedInUser; | ||||
|   const { query, page } = req.query; | ||||
|   const { query, page, adult } = req.query; | ||||
|   const username = req.loggedInUser ? req.loggedInUser.username : null; | ||||
|   const includeAdult = adult == "true" ? true : false; | ||||
|  | ||||
|   if (user) { | ||||
|     return searchHistory.create(user, query); | ||||
|   if (username) { | ||||
|     searchHistory.create(username, query); | ||||
|   } | ||||
|    | ||||
|   tmdb.personSearch(query, page) | ||||
|     .then((person) => { | ||||
|       res.send(person); | ||||
|     }) | ||||
|  | ||||
|   return tmdb | ||||
|     .personSearch(query, page, includeAdult) | ||||
|     .then(persons => res.send(persons)) | ||||
|     .catch(error => { | ||||
|       const { status, message } = error; | ||||
|  | ||||
|       if (status && message) { | ||||
|         res.status(status).send({ success: false, message }) | ||||
|         res.status(status).send({ success: false, message }); | ||||
|       } else { | ||||
|         // TODO log unhandled errors | ||||
|         console.log('caugth person search controller error', error) | ||||
|         console.log("caugth person search controller error", error); | ||||
|         res.status(500).send({ | ||||
|           message: `An unexpected error occured while searching people with query: ${query}` | ||||
|         }) | ||||
|         }); | ||||
|       } | ||||
|     }) | ||||
|     }); | ||||
| } | ||||
|  | ||||
| module.exports = personSearchController; | ||||
|   | ||||
| @@ -1,9 +1,7 @@ | ||||
| const SearchHistory = require('src/searchHistory/searchHistory'); | ||||
| const configuration = require('src/config/configuration').getInstance(); | ||||
| const Cache = require('src/tmdb/cache'); | ||||
| const TMDB = require('src/tmdb/tmdb'); | ||||
| const cache = new Cache(); | ||||
| const tmdb = new TMDB(cache, configuration.get('tmdb', 'apiKey')); | ||||
| const SearchHistory = require("src/searchHistory/searchHistory"); | ||||
| const configuration = require("src/config/configuration").getInstance(); | ||||
| const TMDB = require("src/tmdb/tmdb"); | ||||
| const tmdb = new TMDB(configuration.get("tmdb", "apiKey")); | ||||
| const searchHistory = new SearchHistory(); | ||||
|  | ||||
| /** | ||||
| @@ -13,23 +11,22 @@ const searchHistory = new SearchHistory(); | ||||
|  * @returns {Callback} | ||||
|  */ | ||||
| function showSearchController(req, res) { | ||||
|   const user = req.loggedInUser; | ||||
|   const { query, page } = req.query; | ||||
|   const { query, page, adult } = req.query; | ||||
|   const username = req.loggedInUser ? req.loggedInUser.username : null; | ||||
|   const includeAdult = adult == "true" ? true : false; | ||||
|  | ||||
|   Promise.resolve() | ||||
|   .then(() => { | ||||
|     if (user) { | ||||
|       return searchHistory.create(user, query); | ||||
|     } | ||||
|     return null | ||||
|   }) | ||||
|   .then(() => tmdb.showSearch(query, page)) | ||||
|   .then((shows) => { | ||||
|     res.send(shows); | ||||
|   }) | ||||
|   .catch(error => { | ||||
|     res.status(500).send({ success: false, message: error.message }); | ||||
|   }); | ||||
|   if (username) { | ||||
|     searchHistory.create(username, query); | ||||
|   } | ||||
|  | ||||
|   return tmdb | ||||
|     .showSearch(query, page, includeAdult) | ||||
|     .then(shows => { | ||||
|       res.send(shows); | ||||
|     }) | ||||
|     .catch(error => { | ||||
|       res.status(500).send({ success: false, message: error.message }); | ||||
|     }); | ||||
| } | ||||
|  | ||||
| module.exports = showSearchController; | ||||
|   | ||||
| @@ -1,9 +1,6 @@ | ||||
| const configuration = require('src/config/configuration').getInstance(); | ||||
| const Cache = require('src/tmdb/cache'); | ||||
| const TMDB = require('src/tmdb/tmdb'); | ||||
|  | ||||
| const cache = new Cache(); | ||||
| const tmdb = new TMDB(cache, configuration.get('tmdb', 'apiKey')); | ||||
| const tmdb = new TMDB(configuration.get('tmdb', 'apiKey')); | ||||
|  | ||||
| const showCreditsController = (req, res) => { | ||||
|   const showId = req.params.id; | ||||
| @@ -23,4 +20,4 @@ const showCreditsController = (req, res) => { | ||||
|     }) | ||||
| } | ||||
|  | ||||
| module.exports = showCreditsController; | ||||
| module.exports = showCreditsController; | ||||
|   | ||||
| @@ -1,9 +1,7 @@ | ||||
| const configuration = require('src/config/configuration').getInstance(); | ||||
| const Cache = require('src/tmdb/cache'); | ||||
| const TMDB = require('src/tmdb/tmdb'); | ||||
| const Plex = require('src/plex/plex'); | ||||
| const cache = new Cache(); | ||||
| const tmdb = new TMDB(cache, configuration.get('tmdb', 'apiKey')); | ||||
| const tmdb = new TMDB(configuration.get('tmdb', 'apiKey')); | ||||
| const plex = new Plex(configuration.get('plex', 'ip')); | ||||
|  | ||||
| function handleError(error, res) { | ||||
|   | ||||
| @@ -0,0 +1,93 @@ | ||||
| const UserRepository = require("src/user/userRepository"); | ||||
| const userRepository = new UserRepository(); | ||||
| const fetch = require("node-fetch"); | ||||
| const FormData = require("form-data"); | ||||
|  | ||||
| function handleError(error, res) { | ||||
|   let { status, message, source } = error; | ||||
|  | ||||
|   if (status && message) { | ||||
|     if (status === 401) { | ||||
|       (message = "Unauthorized. Please check plex credentials."), | ||||
|         (source = "plex"); | ||||
|     } | ||||
|  | ||||
|     res.status(status).send({ success: false, message, source }); | ||||
|   } else { | ||||
|     console.log("caught authenticate plex account controller error", error); | ||||
|     res.status(500).send({ | ||||
|       message: | ||||
|         "An unexpected error occured while authenticating your account with plex", | ||||
|       source | ||||
|     }); | ||||
|   } | ||||
| } | ||||
|  | ||||
| function handleResponse(response) { | ||||
|   if (!response.ok) { | ||||
|     throw { | ||||
|       success: false, | ||||
|       status: response.status, | ||||
|       message: response.statusText | ||||
|     }; | ||||
|   } | ||||
|  | ||||
|   return response.json(); | ||||
| } | ||||
|  | ||||
| function plexAuthenticate(username, password) { | ||||
|   const url = "https://plex.tv/api/v2/users/signin"; | ||||
|  | ||||
|   const form = new FormData(); | ||||
|   form.append("login", username); | ||||
|   form.append("password", password); | ||||
|   form.append("rememberMe", "false"); | ||||
|  | ||||
|   const headers = { | ||||
|     Accept: "application/json, text/plain, */*", | ||||
|     "Content-Type": form.getHeaders()["content-type"], | ||||
|     "X-Plex-Client-Identifier": "seasonedRequest" | ||||
|   }; | ||||
|   const options = { | ||||
|     method: "POST", | ||||
|     headers, | ||||
|     body: form | ||||
|   }; | ||||
|  | ||||
|   return fetch(url, options).then(resp => handleResponse(resp)); | ||||
| } | ||||
|  | ||||
| function link(req, res) { | ||||
|   const user = req.loggedInUser; | ||||
|   const { username, password } = req.body; | ||||
|  | ||||
|   return plexAuthenticate(username, password) | ||||
|     .then(plexUser => userRepository.linkPlexUserId(user.username, plexUser.id)) | ||||
|     .then(response => | ||||
|       res.send({ | ||||
|         success: true, | ||||
|         message: | ||||
|           "Successfully authenticated and linked plex account with seasoned request." | ||||
|       }) | ||||
|     ) | ||||
|     .catch(error => handleError(error, res)); | ||||
| } | ||||
|  | ||||
| function unlink(req, res) { | ||||
|   const username = req.loggedInUser ? req.loggedInUser.username : null; | ||||
|  | ||||
|   return userRepository | ||||
|     .unlinkPlexUserId(username) | ||||
|     .then(response => | ||||
|       res.send({ | ||||
|         success: true, | ||||
|         message: "Successfully unlinked plex account from seasoned request." | ||||
|       }) | ||||
|     ) | ||||
|     .catch(error => handleError(error, res)); | ||||
| } | ||||
|  | ||||
| module.exports = { | ||||
|   link, | ||||
|   unlink | ||||
| }; | ||||
| @@ -1,24 +0,0 @@ | ||||
| const SearchHistory = require('src/searchHistory/searchHistory'); | ||||
|  | ||||
| const searchHistory = new SearchHistory(); | ||||
|  | ||||
| /** | ||||
|  * Controller: Retrieves search history of a logged in user | ||||
|  * @param {Request} req http request variable | ||||
|  * @param {Response} res | ||||
|  * @returns {Callback} | ||||
|  */ | ||||
| function historyController(req, res) { | ||||
|    const user = req.loggedInUser; | ||||
|    const username = user === undefined ? undefined : user.username; | ||||
|  | ||||
|    searchHistory.read(username) | ||||
|       .then(searchQueries => { | ||||
|          res.send({ success: true, searchQueries }); | ||||
|       }) | ||||
|       .catch(error => { | ||||
|          res.status(404).send({ success: false, message: error.message }); | ||||
|       }); | ||||
| } | ||||
|  | ||||
| module.exports = historyController; | ||||
| @@ -1,33 +1,61 @@ | ||||
| const User = require('src/user/user'); | ||||
| const Token = require('src/user/token'); | ||||
| const UserSecurity = require('src/user/userSecurity'); | ||||
| const UserRepository = require('src/user/userRepository'); | ||||
| const configuration = require('src/config/configuration').getInstance(); | ||||
| const User = require("src/user/user"); | ||||
| const Token = require("src/user/token"); | ||||
| const UserSecurity = require("src/user/userSecurity"); | ||||
| const UserRepository = require("src/user/userRepository"); | ||||
| const configuration = require("src/config/configuration").getInstance(); | ||||
|  | ||||
| const secret = configuration.get('authentication', 'secret'); | ||||
| const secret = configuration.get("authentication", "secret"); | ||||
| const userSecurity = new UserSecurity(); | ||||
| const userRepository = new UserRepository(); | ||||
|  | ||||
| // 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. | ||||
|  | ||||
| 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. | ||||
|  * @param {Request} req http request variable | ||||
|  * @param {Response} res | ||||
|  * @returns {Callback} | ||||
|  */ | ||||
| function loginController(req, res) { | ||||
|    const user = new User(req.body.username); | ||||
|    const password = req.body.password; | ||||
| async function loginController(req, res) { | ||||
|   const user = new User(req.body.username); | ||||
|   const password = req.body.password; | ||||
|  | ||||
|    userSecurity.login(user, password) | ||||
|       .then(() => userRepository.checkAdmin(user)) | ||||
|       .then(checkAdmin => { | ||||
|          const isAdmin = checkAdmin === 1 ? true : false; | ||||
|          const token = new Token(user, isAdmin).toString(secret); | ||||
|          res.send({ success: true, token }); | ||||
|       }) | ||||
|       .catch(error => { | ||||
|          res.status(401).send({ success: false, message: error.message }); | ||||
|   try { | ||||
|     const [loggedIn, isAdmin, settings] = await Promise.all([ | ||||
|       userSecurity.login(user, password), | ||||
|       userRepository.checkAdmin(user), | ||||
|       userRepository.getSettings(user.username) | ||||
|     ]); | ||||
|  | ||||
|     if (!loggedIn) { | ||||
|       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; | ||||
|   | ||||
							
								
								
									
										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 Token = require('src/user/token'); | ||||
| const UserSecurity = require('src/user/userSecurity'); | ||||
| const UserRepository = require('src/user/userRepository'); | ||||
| const configuration = require('src/config/configuration').getInstance(); | ||||
| const User = require("src/user/user"); | ||||
| const Token = require("src/user/token"); | ||||
| const UserSecurity = require("src/user/userSecurity"); | ||||
| const UserRepository = require("src/user/userRepository"); | ||||
| const configuration = require("src/config/configuration").getInstance(); | ||||
|  | ||||
| const secret = configuration.get('authentication', 'secret'); | ||||
| const secret = configuration.get("authentication", "secret"); | ||||
| const userSecurity = new UserSecurity(); | ||||
| 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 | ||||
|  * @param {Request} req http request variable | ||||
| @@ -15,21 +23,25 @@ const userRepository = new UserRepository(); | ||||
|  * @returns {Callback} | ||||
|  */ | ||||
| function registerController(req, res) { | ||||
|    const user = new User(req.body.username, req.body.email); | ||||
|    const password = req.body.password; | ||||
|   const user = new User(req.body.username, req.body.email); | ||||
|   const password = req.body.password; | ||||
|  | ||||
|    userSecurity.createNewUser(user, password) | ||||
|       .then(() => userRepository.checkAdmin(user)) | ||||
|       .then(checkAdmin => { | ||||
|          const isAdmin = checkAdmin === 1 ? true : false; | ||||
|          const token = new Token(user, isAdmin).toString(secret); | ||||
|          res.send({ | ||||
|             success: true, message: 'Welcome to Seasoned!', token | ||||
|          }); | ||||
|       }) | ||||
|       .catch(error => { | ||||
|          res.status(401).send({ success: false, message: error.message }); | ||||
|       }); | ||||
|   userSecurity | ||||
|     .createNewUser(user, password) | ||||
|     .then(() => { | ||||
|       const token = new Token(user, false).toString(secret); | ||||
|  | ||||
|       return res | ||||
|         .cookie("authorization", token, cookieOptions) | ||||
|         .status(200) | ||||
|         .send({ | ||||
|           success: true, | ||||
|           message: "Welcome to Seasoned!" | ||||
|         }); | ||||
|     }) | ||||
|     .catch(error => { | ||||
|       res.status(401).send({ success: false, message: error.message }); | ||||
|     }); | ||||
| } | ||||
|  | ||||
| module.exports = registerController; | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| const RequestRepository = require('src/plex/requestRepository.js'); | ||||
| const RequestRepository = require("src/plex/requestRepository.js"); | ||||
|  | ||||
| const requestRepository = new RequestRepository(); | ||||
|  | ||||
| @@ -9,15 +9,20 @@ const requestRepository = new RequestRepository(); | ||||
|  * @returns {Callback} | ||||
|  */ | ||||
| function requestsController(req, res) { | ||||
|    const user = req.loggedInUser; | ||||
|   const username = req.loggedInUser ? req.loggedInUser.username : null; | ||||
|  | ||||
|    requestRepository.userRequests(user) | ||||
|       .then(requests => { | ||||
|          res.send({ success: true, results: requests, total_results: requests.length }); | ||||
|       }) | ||||
|       .catch(error => { | ||||
|          res.status(500).send({ success: false, message: error.message }); | ||||
|   requestRepository | ||||
|     .userRequests(username) | ||||
|     .then(requests => { | ||||
|       res.send({ | ||||
|         success: true, | ||||
|         results: requests, | ||||
|         total_results: requests.length | ||||
|       }); | ||||
|     }) | ||||
|     .catch(error => { | ||||
|       res.status(500).send({ success: false, message: error.message }); | ||||
|     }); | ||||
| } | ||||
|  | ||||
| module.exports = requestsController; | ||||
|   | ||||
							
								
								
									
										24
									
								
								seasoned_api/src/webserver/controllers/user/searchHistory.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								seasoned_api/src/webserver/controllers/user/searchHistory.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,24 @@ | ||||
| const SearchHistory = require("src/searchHistory/searchHistory"); | ||||
|  | ||||
| const searchHistory = new SearchHistory(); | ||||
|  | ||||
| /** | ||||
|  * Controller: Retrieves search history of a logged in user | ||||
|  * @param {Request} req http request variable | ||||
|  * @param {Response} res | ||||
|  * @returns {Callback} | ||||
|  */ | ||||
| function historyController(req, res) { | ||||
|   const username = req.loggedInUser ? req.loggedInUser.username : null; | ||||
|  | ||||
|   searchHistory | ||||
|     .read(username) | ||||
|     .then(searchQueries => { | ||||
|       res.send({ success: true, searchQueries }); | ||||
|     }) | ||||
|     .catch(error => { | ||||
|       res.status(404).send({ success: false, message: error.message }); | ||||
|     }); | ||||
| } | ||||
|  | ||||
| module.exports = historyController; | ||||
							
								
								
									
										41
									
								
								seasoned_api/src/webserver/controllers/user/settings.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								seasoned_api/src/webserver/controllers/user/settings.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,41 @@ | ||||
| const UserRepository = require("src/user/userRepository"); | ||||
| const userRepository = new UserRepository(); | ||||
| /** | ||||
|  * Controller: Retrieves settings of a logged in user | ||||
|  * @param {Request} req http request variable | ||||
|  * @param {Response} res | ||||
|  * @returns {Callback} | ||||
|  */ | ||||
| const getSettingsController = (req, res) => { | ||||
|   const username = req.loggedInUser ? req.loggedInUser.username : null; | ||||
|  | ||||
|   userRepository | ||||
|     .getSettings(username) | ||||
|     .then(settings => { | ||||
|       res.send({ success: true, settings }); | ||||
|     }) | ||||
|     .catch(error => { | ||||
|       res.status(404).send({ success: false, message: error.message }); | ||||
|     }); | ||||
| }; | ||||
|  | ||||
| const updateSettingsController = (req, res) => { | ||||
|   const username = req.loggedInUser ? req.loggedInUser.username : null; | ||||
|  | ||||
|   const idempotencyKey = req.headers("Idempotency-Key"); // TODO implement better transactions | ||||
|   const { dark_mode, emoji } = req.body; | ||||
|  | ||||
|   userRepository | ||||
|     .updateSettings(username, dark_mode, emoji) | ||||
|     .then(settings => { | ||||
|       res.send({ success: true, settings }); | ||||
|     }) | ||||
|     .catch(error => { | ||||
|       res.status(404).send({ success: false, message: error.message }); | ||||
|     }); | ||||
| }; | ||||
|  | ||||
| module.exports = { | ||||
|   getSettingsController, | ||||
|   updateSettingsController | ||||
| }; | ||||
							
								
								
									
										107
									
								
								seasoned_api/src/webserver/controllers/user/viewHistory.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										107
									
								
								seasoned_api/src/webserver/controllers/user/viewHistory.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,107 @@ | ||||
| const configuration = require("src/config/configuration").getInstance(); | ||||
| const Tautulli = require("src/tautulli/tautulli"); | ||||
| const apiKey = configuration.get("tautulli", "apiKey"); | ||||
| const ip = configuration.get("tautulli", "ip"); | ||||
| const port = configuration.get("tautulli", "port"); | ||||
| const tautulli = new Tautulli(apiKey, ip, port); | ||||
|  | ||||
| function handleError(error, res) { | ||||
|   const { status, message } = error; | ||||
|  | ||||
|   if (status && message) { | ||||
|     return res.status(status).send({ success: false, message }); | ||||
|   } else { | ||||
|     console.log("caught view history controller error", error); | ||||
|     return res.status(500).send({ | ||||
|       message: "An unexpected error occured while fetching view history" | ||||
|     }); | ||||
|   } | ||||
| } | ||||
|  | ||||
| function watchTimeStatsController(req, res) { | ||||
|   const user = req.loggedInUser; | ||||
|  | ||||
|   return tautulli | ||||
|     .watchTimeStats(user.plex_userid) | ||||
|     .then(data => { | ||||
|       return res.send({ | ||||
|         success: true, | ||||
|         data: data.response.data, | ||||
|         message: "watch time successfully fetched from tautulli" | ||||
|       }); | ||||
|     }) | ||||
|     .catch(error => handleError(error, res)); | ||||
| } | ||||
|  | ||||
| function getPlaysByDayOfWeekController(req, res) { | ||||
|   const user = req.loggedInUser; | ||||
|   const { days, y_axis } = req.query; | ||||
|  | ||||
|   return tautulli | ||||
|     .getPlaysByDayOfWeek(user.plex_userid, days, y_axis) | ||||
|     .then(data => | ||||
|       res.send({ | ||||
|         success: true, | ||||
|         data: data.response.data, | ||||
|         message: "play by day of week successfully fetched from tautulli" | ||||
|       }) | ||||
|     ) | ||||
|     .catch(error => handleError(error, res)); | ||||
| } | ||||
|  | ||||
| function getPlaysByDaysController(req, res) { | ||||
|   const user = req.loggedInUser; | ||||
|   const { days, y_axis } = req.query; | ||||
|  | ||||
|   if (days === undefined) { | ||||
|     return res.status(422).send({ | ||||
|       success: false, | ||||
|       message: "Missing parameter: days (number)" | ||||
|     }); | ||||
|   } | ||||
|  | ||||
|   const allowedYAxisDataType = ["plays", "duration"]; | ||||
|   if (!allowedYAxisDataType.includes(y_axis)) { | ||||
|     return res.status(422).send({ | ||||
|       success: false, | ||||
|       message: `Y axis parameter must be one of values: [${allowedYAxisDataType}]` | ||||
|     }); | ||||
|   } | ||||
|  | ||||
|   return tautulli | ||||
|     .getPlaysByDays(user.plex_userid, days, y_axis) | ||||
|     .then(data => | ||||
|       res.send({ | ||||
|         success: true, | ||||
|         data: data.response.data | ||||
|       }) | ||||
|     ) | ||||
|     .catch(error => handleError(error, res)); | ||||
| } | ||||
|  | ||||
| function userViewHistoryController(req, res) { | ||||
|   const user = req.loggedInUser; | ||||
|  | ||||
|   // TODO here we should check if we can init tau | ||||
|   // and then return 501 Not implemented | ||||
|  | ||||
|   return tautulli | ||||
|     .viewHistory(user.plex_userid) | ||||
|     .then(data => { | ||||
|       return res.send({ | ||||
|         success: true, | ||||
|         data: data.response.data.data, | ||||
|         message: "view history successfully fetched from tautulli" | ||||
|       }); | ||||
|     }) | ||||
|     .catch(error => handleError(error, res)); | ||||
|  | ||||
|   // const username = user.username; | ||||
| } | ||||
|  | ||||
| module.exports = { | ||||
|   watchTimeStatsController, | ||||
|   getPlaysByDaysController, | ||||
|   getPlaysByDayOfWeekController, | ||||
|   userViewHistoryController | ||||
| }; | ||||
| @@ -6,7 +6,7 @@ const mustBeAdmin = (req, res, next) => { | ||||
|    if (req.loggedInUser === undefined) { | ||||
|       return res.status(401).send({ | ||||
|          success: false, | ||||
|          error: 'You must be logged in.', | ||||
|          message: 'You must be logged in.', | ||||
|       }); | ||||
|    } else { | ||||
|       database.get(`SELECT admin FROM user WHERE user_name IS ?`, req.loggedInUser.username) | ||||
| @@ -15,7 +15,7 @@ const mustBeAdmin = (req, res, next) => { | ||||
|          if (isAdmin.admin == 0) { | ||||
|             return res.status(401).send({ | ||||
|                success: false, | ||||
|                error: 'You must be logged in as a admin.' | ||||
|                message: 'You must be logged in as a admin.' | ||||
|             }) | ||||
|          } | ||||
|       }) | ||||
|   | ||||
| @@ -1,11 +1,11 @@ | ||||
| const mustBeAuthenticated = (req, res, next) => { | ||||
|    if (req.loggedInUser === undefined) { | ||||
|       return res.status(401).send({ | ||||
|          success: false, | ||||
|          error: 'You must be logged in.', | ||||
|       }); | ||||
|    } | ||||
|    return next(); | ||||
|   if (req.loggedInUser === undefined) { | ||||
|     return res.status(401).send({ | ||||
|       success: false, | ||||
|       message: "You must be logged in." | ||||
|     }); | ||||
|   } | ||||
|   return next(); | ||||
| }; | ||||
|  | ||||
| module.exports = mustBeAuthenticated; | ||||
|   | ||||
| @@ -0,0 +1,31 @@ | ||||
| const establishedDatabase = require('src/database/database'); | ||||
|  | ||||
| const mustHaveAccountLinkedToPlex = (req, res, next) => { | ||||
|    let database = establishedDatabase; | ||||
|    const loggedInUser = req.loggedInUser; | ||||
|  | ||||
|    if (loggedInUser === undefined) { | ||||
|       return res.status(401).send({ | ||||
|          success: false, | ||||
|          message: 'You must have your account linked to a plex account.', | ||||
|       }); | ||||
|    } else { | ||||
|       database.get(`SELECT plex_userid FROM settings WHERE user_name IS ?`, loggedInUser.username) | ||||
|       .then(row => { | ||||
|          const plex_userid = row.plex_userid; | ||||
|  | ||||
|          if (plex_userid === null || plex_userid === undefined) { | ||||
|             return res.status(403).send({ | ||||
|                success: false, | ||||
|                message: 'No plex account user id found for your user. Please authenticate your plex account at /user/authenticate.' | ||||
|             }) | ||||
|          } else { | ||||
|             req.loggedInUser.plex_userid = plex_userid; | ||||
|             return next(); | ||||
|          } | ||||
|       }) | ||||
|    } | ||||
|  | ||||
| }; | ||||
|  | ||||
| module.exports = mustHaveAccountLinkedToPlex; | ||||
							
								
								
									
										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