Compare commits
	
		
			2 Commits
		
	
	
		
			feat/cooki
			...
			research/g
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 488da889d8 | |||
| 8da7c159b1 | 
							
								
								
									
										10
									
								
								.prettierrc
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								.prettierrc
									
									
									
									
									
								
							| @@ -1,10 +0,0 @@ | |||||||
| { |  | ||||||
|   "tabWidth": 2, |  | ||||||
|   "useTabs": false, |  | ||||||
|   "semi": true, |  | ||||||
|   "singleQuote": false, |  | ||||||
|   "bracketSpacing": true, |  | ||||||
|   "arrowParens": "avoid", |  | ||||||
|   "vueIndentScriptAndStyle": false, |  | ||||||
|   "trailingComma": "none" |  | ||||||
| } |  | ||||||
| @@ -7,7 +7,6 @@ script: | |||||||
|    - yarn coverage |    - yarn coverage | ||||||
| before_install: | before_install: | ||||||
|    - cd seasoned_api |    - cd seasoned_api | ||||||
|    - cp conf/development.json.example conf/development.json |  | ||||||
| before_script:  | before_script:  | ||||||
|    - yarn |    - yarn | ||||||
|    - curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter |    - curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter | ||||||
|   | |||||||
| @@ -1,26 +1,20 @@ | |||||||
| { | { | ||||||
|   "database": { | 	"database": { | ||||||
|     "host": "../shows.db" | 		"host": "../shows.db" | ||||||
|   }, | 	}, | ||||||
|   "webserver": { | 	"webserver": { | ||||||
|     "port": 31459, | 		"port": 31459 | ||||||
|     "origins": [] | 	}, | ||||||
|   }, | 	"tmdb": { | ||||||
|   "tmdb": { | 		"apiKey": "" | ||||||
|     "apiKey": "" | 	}, | ||||||
|   }, | 	"plex": { | ||||||
|   "plex": { | 		"ip": "" | ||||||
|     "ip": "" | 	}, | ||||||
|   }, | 	"raven": { | ||||||
|   "tautulli": { | 		"DSN": "" | ||||||
|     "apiKey": "", | 	}, | ||||||
|     "ip": "", | 	"authentication": { | ||||||
|     "port": "" |  | ||||||
|   }, |  | ||||||
|   "raven": { |  | ||||||
|     "DSN": "" |  | ||||||
|   }, |  | ||||||
|   "authentication": { |  | ||||||
|     "secret": "secret" |     "secret": "secret" | ||||||
|   } |  	} | ||||||
| } | } | ||||||
|   | |||||||
| @@ -7,33 +7,33 @@ | |||||||
|   }, |   }, | ||||||
|   "main": "webserver/server.js", |   "main": "webserver/server.js", | ||||||
|   "scripts": { |   "scripts": { | ||||||
|     "start": "cross-env SEASONED_CONFIG=conf/development.json NODE_ENV=production NODE_PATH=. babel-node src/webserver/server.js", |     "start": "cross-env SEASONED_CONFIG=conf/development.json PROD=true NODE_PATH=. babel-node src/webserver/server.js", | ||||||
|     "test": "cross-env SEASONED_CONFIG=conf/test.json NODE_PATH=. mocha --require @babel/register --recursive test/unit test/system", |     "test": "cross-env SEASONED_CONFIG=conf/test.json NODE_PATH=. mocha --require @babel/register --recursive test/unit test/system", | ||||||
|     "coverage": "cross-env SEASONED_CONFIG=conf/test.json NODE_PATH=. nyc mocha --require @babel/register --recursive test && nyc report --reporter=text-lcov | coveralls", |     "coverage": "cross-env SEASONED_CONFIG=conf/test.json NODE_PATH=. nyc mocha --require @babel/register --recursive test && nyc report --reporter=text-lcov | coveralls", | ||||||
|     "lint": "./node_modules/.bin/eslint src/", |     "lint": "./node_modules/.bin/eslint src/", | ||||||
|     "update": "cross-env SEASONED_CONFIG=conf/development.json NODE_PATH=. node scripts/updateRequestsInPlex.js", |     "update": "cross-env SEASONED_CONFIG=conf/development.json NODE_PATH=. node src/plex/updateRequestsInPlex.js", | ||||||
|     "docs": "yarn apiDocs; yarn classDocs", |     "docs": "yarn apiDocs; yarn classDocs", | ||||||
|     "apiDocs": "", |     "apiDocs": "", | ||||||
|     "classDocs": "./script/generate-class-docs.sh" |     "classDocs": "./script/generate-class-docs.sh" | ||||||
|   }, |   }, | ||||||
|   "dependencies": { |   "dependencies": { | ||||||
|     "axios": "^0.18.0", |     "axios": "^0.18.0", | ||||||
|     "bcrypt": "^5.0.1", |     "bcrypt": "^3.0.6", | ||||||
|     "body-parser": "~1.18.2", |     "body-parser": "~1.18.2", | ||||||
|     "cookie-parser": "^1.4.6", |  | ||||||
|     "cross-env": "~5.1.4", |     "cross-env": "~5.1.4", | ||||||
|     "express": "~4.16.0", |     "express": "~4.16.0", | ||||||
|     "form-data": "^2.5.1", |     "express-graphql": "^0.9.0", | ||||||
|     "jsonwebtoken": "^8.5.1", |     "express-reload": "^1.2.0", | ||||||
|  |     "graphql": "^14.5.8", | ||||||
|  |     "jsonwebtoken": "^8.2.0", | ||||||
|     "km-moviedb": "^0.2.12", |     "km-moviedb": "^0.2.12", | ||||||
|     "node-cache": "^4.1.1", |     "node-cache": "^4.1.1", | ||||||
|     "node-fetch": "^2.6.0", |     "node-fetch": "^2.6.0", | ||||||
|     "python-shell": "^0.5.0", |     "python-shell": "^0.5.0", | ||||||
|     "raven": "^2.4.2", |     "raven": "^2.4.2", | ||||||
|     "redis": "^3.0.2", |  | ||||||
|     "request": "^2.87.0", |     "request": "^2.87.0", | ||||||
|     "request-promise": "^4.2", |     "request-promise": "^4.2", | ||||||
|     "sqlite3": "^5.0.1" |     "sqlite3": "^4.0.0" | ||||||
|   }, |   }, | ||||||
|   "devDependencies": { |   "devDependencies": { | ||||||
|     "@babel/core": "^7.5.5", |     "@babel/core": "^7.5.5", | ||||||
|   | |||||||
| @@ -1,44 +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"); |  | ||||||
|  |  | ||||||
| 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
									
									
								
							
							
						
						
									
										52
									
								
								seasoned_api/src/cache/redis.js
									
									
									
									
										vendored
									
									
								
							| @@ -1,52 +0,0 @@ | |||||||
| const redis = require("redis") |  | ||||||
| const client = redis.createClient() |  | ||||||
|  |  | ||||||
| class Cache { |  | ||||||
|   /** |  | ||||||
|    * Retrieve an unexpired cache entry by key. |  | ||||||
|    * @param {String} key of the cache entry |  | ||||||
|    * @returns {Promise} |  | ||||||
|    */ |  | ||||||
|   get(key) { |  | ||||||
|     return new Promise((resolve, reject) => { |  | ||||||
|       client.get(key, (error, reply) => { |  | ||||||
|         if (reply == null) { |  | ||||||
|           return reject(); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         resolve(JSON.parse(reply)); |  | ||||||
|       }); |  | ||||||
|     }); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   /** |  | ||||||
|    * Insert cache entry with key and value. |  | ||||||
|    * @param {String} key of the cache entry |  | ||||||
|    * @param {String} value of the cache entry |  | ||||||
|    * @param {Number} timeToLive the number of seconds before entry expires |  | ||||||
|    * @returns {Object} |  | ||||||
|    */ |  | ||||||
|   set(key, value, timeToLive = 10800) { |  | ||||||
|     if (value == null || key == null) return null; |  | ||||||
|  |  | ||||||
|     const json = JSON.stringify(value); |  | ||||||
|     client.set(key, json, (error, reply) => { |  | ||||||
|       if (reply == "OK") { |  | ||||||
|         // successfully set value with key, now set TTL for key |  | ||||||
|         client.expire(key, timeToLive, e => { |  | ||||||
|           if (e) |  | ||||||
|             console.error( |  | ||||||
|               "Unexpected error while setting expiration for key:", |  | ||||||
|               key, |  | ||||||
|               ". Error:", |  | ||||||
|               error |  | ||||||
|             ); |  | ||||||
|         }); |  | ||||||
|       } |  | ||||||
|     }); |  | ||||||
|  |  | ||||||
|     return value; |  | ||||||
|   } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| module.exports = Cache; |  | ||||||
| @@ -22,13 +22,13 @@ class Config { | |||||||
|  |  | ||||||
|    get(section, option) { |    get(section, option) { | ||||||
|       if (this.fields[section] === undefined || this.fields[section][option] === undefined) { |       if (this.fields[section] === undefined || this.fields[section][option] === undefined) { | ||||||
|          throw new Error(`Field "${section} => ${option}" does not exist.`); |          throw new Error(`Filed "${section} => ${option}" does not exist.`); | ||||||
|       } |       } | ||||||
|  |  | ||||||
|       const field = new Field(this.fields[section][option]); |       const field = new Field(this.fields[section][option]); | ||||||
|  |  | ||||||
|       if (field.value === '') { |       if (field.value === '') { | ||||||
|          const envField = process.env[[section.toUpperCase(), option.toUpperCase()].join('_')]; |          const envField = process.env[['SEASONED', section.toUpperCase(), option.toUpperCase()].join('_')]; | ||||||
|          if (envField !== undefined && envField.length !== 0) { return envField; } |          if (envField !== undefined && envField.length !== 0) { return envField; } | ||||||
|       } |       } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,19 +1,11 @@ | |||||||
| CREATE TABLE IF NOT EXISTS user ( | CREATE TABLE IF NOT EXISTS user ( | ||||||
|     user_name varchar(127) UNIQUE, |     user_name varchar(127) UNIQUE, | ||||||
|     password varchar(127), |     password varchar(127), | ||||||
|     admin boolean DEFAULT 0, |  | ||||||
|     email varchar(127) UNIQUE, |     email varchar(127) UNIQUE, | ||||||
|  |     admin boolean DEFAULT 0, | ||||||
|     primary key (user_name) |     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 ( | CREATE TABLE IF NOT EXISTS cache ( | ||||||
|     key varchar(255), |     key varchar(255), | ||||||
|     value blob, |     value blob, | ||||||
| @@ -31,18 +23,17 @@ CREATE TABLE IF NOT EXISTS search_history ( | |||||||
| ); | ); | ||||||
|  |  | ||||||
| CREATE TABLE IF NOT EXISTS requests( | CREATE TABLE IF NOT EXISTS requests( | ||||||
|     id NUMBER, |     id TEXT, | ||||||
|     title TEXT, |     title TEXT, | ||||||
|     year NUMBER, |     year NUMBER, | ||||||
|     poster_path TEXT DEFAULT NULL, |     poster_path TEXT DEFAULT NULL, | ||||||
|     background_path TEXT DEFAULT NULL, |     background_path TEXT DEFAULT NULL, | ||||||
|     requested_by varchar(127) DEFAULT NULL, |     requested_by TEXT, | ||||||
|     ip TEXT, |     ip TEXT, | ||||||
|     date DATE DEFAULT CURRENT_TIMESTAMP, |     date DATE DEFAULT CURRENT_TIMESTAMP, | ||||||
|     status CHAR(25) DEFAULT 'requested' NOT NULL, |     status CHAR(25) DEFAULT 'requested' NOT NULL, | ||||||
|     user_agent CHAR(255) DEFAULT 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( | CREATE TABLE IF NOT EXISTS request( | ||||||
|   | |||||||
| @@ -1,5 +1,4 @@ | |||||||
| DROP TABLE IF EXISTS user; | DROP TABLE IF EXISTS user; | ||||||
| DROP TABLE IF EXISTS settings; |  | ||||||
| DROP TABLE IF EXISTS search_history; | DROP TABLE IF EXISTS search_history; | ||||||
| DROP TABLE IF EXISTS requests; | DROP TABLE IF EXISTS requests; | ||||||
| DROP TABLE IF EXISTS request; | DROP TABLE IF EXISTS request; | ||||||
|   | |||||||
| @@ -6,7 +6,6 @@ class SqliteDatabase { | |||||||
|    constructor(host) { |    constructor(host) { | ||||||
|       this.host = host; |       this.host = host; | ||||||
|       this.connection = new sqlite3.Database(this.host); |       this.connection = new sqlite3.Database(this.host); | ||||||
|       this.execute('pragma foreign_keys = on;'); |  | ||||||
|       this.schemaDirectory = path.join(__dirname, 'schemas'); |       this.schemaDirectory = path.join(__dirname, 'schemas'); | ||||||
|    } |    } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,35 +0,0 @@ | |||||||
| 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,104 +1,84 @@ | |||||||
| const assert = require("assert"); | const assert = require('assert'); | ||||||
| const http = require("http"); | const http = require('http'); | ||||||
| const { URL } = require("url"); | const { URL } = require('url'); | ||||||
| const PythonShell = require("python-shell"); | const PythonShell = require('python-shell'); | ||||||
|  |  | ||||||
| const establishedDatabase = require("src/database/database"); | const establishedDatabase = require('src/database/database'); | ||||||
|  |  | ||||||
| const RedisCache = require("src/cache/redis"); |  | ||||||
| const cache = new RedisCache(); |  | ||||||
|  |  | ||||||
| function getMagnetFromURL(url) { | function getMagnetFromURL(url) { | ||||||
|   return new Promise((resolve, reject) => { |    return new Promise((resolve, reject) => { | ||||||
|     const options = new URL(url); |       const options = new URL(url); | ||||||
|     if (options.protocol.includes("magnet")) resolve(url); |       if (options.protocol.includes('magnet')) | ||||||
|  |          resolve(url) | ||||||
|  |  | ||||||
|     http.get(options, res => { |       http.get(options, (res) => { | ||||||
|       if (res.statusCode == 301 || res.statusCode == 302) { |          if (res.statusCode == 301 || res.statusCode == 302) { | ||||||
|         resolve(res.headers.location); |             resolve(res.headers.location) | ||||||
|       } |          } | ||||||
|     }); |       }); | ||||||
|   }); |    }); | ||||||
| } | } | ||||||
|  |  | ||||||
| async function find(searchterm, callback) { | async function find(searchterm, callback) { | ||||||
|   const options = { |    const options = { | ||||||
|     pythonPath: "../torrent_search/env/bin/python3", |       pythonPath: '../torrent_search/env/bin/python3', | ||||||
|     scriptPath: "../torrent_search", |       scriptPath: '../torrent_search', | ||||||
|     args: [searchterm, "-s", "jackett", "--print"] |       args: [searchterm, '-s', 'jackett', '-f', '--print'] | ||||||
|   }; |    } | ||||||
|  |  | ||||||
|   PythonShell.run("torrentSearch/search.py", options, callback); |    PythonShell.run('torrentSearch/search.py', options, callback); | ||||||
|   // PythonShell does not support return |    // PythonShell does not support return | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
| async function callPythonAddMagnet(url, callback) { | async function callPythonAddMagnet(url, callback) { | ||||||
|   getMagnetFromURL(url) |    getMagnetFromURL(url) | ||||||
|     .then(magnet => { |    .then((magnet) => { | ||||||
|       const options = { |       const options = { | ||||||
|         pythonPath: "../delugeClient/env/bin/python3", |         pythonPath: '../delugeClient/env/bin/python3', | ||||||
|         scriptPath: "../delugeClient", |         scriptPath: '../delugeClient', | ||||||
|         args: ["add", magnet] |         args: ['add', magnet] | ||||||
|       }; |       }; | ||||||
|  |  | ||||||
|       PythonShell.run("deluge_cli.py", options, callback); |       PythonShell.run('deluge_cli.py', options, callback); | ||||||
|     }) |    }) | ||||||
|     .catch(err => { |    .catch((err) => { | ||||||
|       console.log(err); |       console.log(err); | ||||||
|       throw new Error(err); |       throw new Error(err); | ||||||
|     }); |    }) | ||||||
| } | } | ||||||
|  |  | ||||||
| async function SearchPiratebay(query) { | async function SearchPiratebay(query) { | ||||||
|   if (query && query.includes("+")) { |    return await new Promise((resolve, reject) => find(query, (err, results) => { | ||||||
|     query = query.replace("+", "%20"); |       if (err) { | ||||||
|   } |          console.log('THERE WAS A FUCKING ERROR!\n', err); | ||||||
|  |          reject(Error('There was a error when searching for torrents')); | ||||||
|   const cacheKey = `pirate/${query}`; |       } | ||||||
|  |       if (results) { | ||||||
|   return new Promise((resolve, reject) => |          resolve(JSON.parse(results, null, '\t')); | ||||||
|     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) { | async function AddMagnet(magnet, name, tmdb_id) { | ||||||
|   return await new Promise((resolve, reject) => |    return await new Promise((resolve, reject) => callPythonAddMagnet(magnet, (err, results) => { | ||||||
|     callPythonAddMagnet(magnet, (err, results) => { |  | ||||||
|       if (err) { |       if (err) { | ||||||
|         /* eslint-disable no-console */ |          /* eslint-disable no-console */ | ||||||
|         console.log(err); |          console.log(err); | ||||||
|         reject(Error("Enable to add torrent", err)); |          reject(Error('Enable to add torrent', err))  | ||||||
|       } |       } | ||||||
|       /* eslint-disable no-console */ |       /* eslint-disable no-console */ | ||||||
|       console.log("result/error:", err, results); |       console.log('result/error:', err, results); | ||||||
|  |  | ||||||
|       database = establishedDatabase; |       database = establishedDatabase; | ||||||
|       insert_query = |       insert_query = "INSERT INTO requested_torrent(magnet,torrent_name,tmdb_id) \ | ||||||
|         "INSERT INTO requested_torrent(magnet,torrent_name,tmdb_id) \ |  | ||||||
|          VALUES (?,?,?)"; |          VALUES (?,?,?)"; | ||||||
|  |  | ||||||
|       let response = database.run(insert_query, [magnet, name, tmdb_id]); |       let response = database.run(insert_query, [magnet, name, tmdb_id]); | ||||||
|       console.log("Response from requsted_torrent insert: " + response); |       console.log('Response from requsted_torrent insert: ' + response); | ||||||
|  |  | ||||||
|       resolve({ success: true }); |       resolve({ success: true }); | ||||||
|     }) |    })); | ||||||
|   ); |  | ||||||
| } | } | ||||||
|  |  | ||||||
| module.exports = { SearchPiratebay, AddMagnet }; | module.exports = { SearchPiratebay, AddMagnet }; | ||||||
|   | |||||||
| @@ -1,240 +1,89 @@ | |||||||
| const fetch = require("node-fetch"); | const fetch = require('node-fetch') | ||||||
| const convertPlexToMovie = require("src/plex/convertPlexToMovie"); | const convertPlexToMovie = require('src/plex/convertPlexToMovie') | ||||||
| const convertPlexToShow = require("src/plex/convertPlexToShow"); | const convertPlexToShow = require('src/plex/convertPlexToShow') | ||||||
| const convertPlexToEpisode = require("src/plex/convertPlexToEpisode"); | const convertPlexToEpisode = require('src/plex/convertPlexToEpisode') | ||||||
|  |  | ||||||
| const { Movie, Show, Person } = require("src/tmdb/types"); |  | ||||||
|  |  | ||||||
| const RedisCache = require("src/cache/redis"); | const { Movie, Show, Person } = require('src/tmdb/types'); | ||||||
| const redisCache = new RedisCache(); |  | ||||||
|  |  | ||||||
| const sanitize = string => string.toLowerCase().replace(/[^\w]/gi, ""); | // const { Movie, }  | ||||||
|  | // TODO? import class definitions to compare types ? | ||||||
| function fixedEncodeURIComponent(str) { | // what would typescript do? | ||||||
|   return encodeURIComponent(str).replace(/[!'()*]/g, function (c) { |  | ||||||
|     return "%" + c.charCodeAt(0).toString(16).toUpperCase(); |  | ||||||
|   }); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| const matchingTitleAndYear = (plex, tmdb) => { |  | ||||||
|   let matchingTitle, matchingYear; |  | ||||||
|  |  | ||||||
|   if (plex["title"] != null && tmdb["title"] != null) { |  | ||||||
|     const plexTitle = sanitize(plex.title); |  | ||||||
|     const tmdbTitle = sanitize(tmdb.title); |  | ||||||
|     matchingTitle = plexTitle == tmdbTitle; |  | ||||||
|     matchingTitle = matchingTitle |  | ||||||
|       ? matchingTitle |  | ||||||
|       : plexTitle.startsWith(tmdbTitle); |  | ||||||
|   } else matchingTitle = false; |  | ||||||
|  |  | ||||||
|   if (plex["year"] != null && tmdb["year"] != null) |  | ||||||
|     matchingYear = plex.year == tmdb.year; |  | ||||||
|   else matchingYear = false; |  | ||||||
|  |  | ||||||
|   return matchingTitle && matchingYear; |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| const successfullResponse = response => { |  | ||||||
|   if (response && response["MediaContainer"]) return response; |  | ||||||
|  |  | ||||||
|   if ( |  | ||||||
|     response == null || |  | ||||||
|     response["status"] == null || |  | ||||||
|     response["statusText"] == null |  | ||||||
|   ) { |  | ||||||
|     throw Error("Unable to decode response"); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   const { status, statusText } = response; |  | ||||||
|  |  | ||||||
|   if (status === 200) { |  | ||||||
|     return response.json(); |  | ||||||
|   } else { |  | ||||||
|     throw { message: statusText, status: status }; |  | ||||||
|   } |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| class Plex { | class Plex { | ||||||
|   constructor(ip, port = 32400, cache = null) { |   constructor(ip, port=32400) { | ||||||
|     this.plexIP = ip; |     this.plexIP = ip | ||||||
|     this.plexPort = port; |     this.plexPort = port | ||||||
|  |  | ||||||
|     this.cache = cache || redisCache; |  | ||||||
|     this.cacheTags = { |  | ||||||
|       machineInfo: "plex/mi", |  | ||||||
|       search: "plex/s" |  | ||||||
|     }; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   fetchMachineIdentifier() { |  | ||||||
|     const cacheKey = `${this.cacheTags.machineInfo}`; |  | ||||||
|     const url = `http://${this.plexIP}:${this.plexPort}/`; |  | ||||||
|     const options = { |  | ||||||
|       timeout: 20000, |  | ||||||
|       headers: { Accept: "application/json" } |  | ||||||
|     }; |  | ||||||
|  |  | ||||||
|     return new Promise((resolve, reject) => |  | ||||||
|       this.cache |  | ||||||
|         .get(cacheKey) |  | ||||||
|         .then(machineInfo => resolve(machineInfo["machineIdentifier"])) |  | ||||||
|         .catch(() => fetch(url, options)) |  | ||||||
|         .then(response => response.json()) |  | ||||||
|         .then(machineInfo => |  | ||||||
|           this.cache.set(cacheKey, machineInfo["MediaContainer"], 2628000) |  | ||||||
|         ) |  | ||||||
|         .then(machineInfo => resolve(machineInfo["machineIdentifier"])) |  | ||||||
|         .catch(error => { |  | ||||||
|           if (error != undefined && error.type === "request-timeout") { |  | ||||||
|             reject({ |  | ||||||
|               message: "Plex did not respond", |  | ||||||
|               status: 408, |  | ||||||
|               success: false |  | ||||||
|             }); |  | ||||||
|           } |  | ||||||
|  |  | ||||||
|           reject(error); |  | ||||||
|         }) |  | ||||||
|     ); |  | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   matchTmdbAndPlexMedia(plex, tmdb) { |   matchTmdbAndPlexMedia(plex, tmdb) { | ||||||
|     let match; |     if (plex === undefined || tmdb === undefined) | ||||||
|  |       return false | ||||||
|  |  | ||||||
|     if (plex == null || tmdb == null) return false; |     const sanitize = (string) => string.toLowerCase() | ||||||
|  |  | ||||||
|     if (plex instanceof Array) { |     const matchTitle = sanitize(plex.title) === sanitize(tmdb.title) | ||||||
|       let possibleMatches = plex.map(plexItem => |     const matchYear = plex.year === tmdb.year | ||||||
|         matchingTitleAndYear(plexItem, tmdb) |  | ||||||
|       ); |     return matchTitle && matchYear | ||||||
|       match = possibleMatches.includes(true); |   } | ||||||
|  |  | ||||||
|  |   existsInPlex(tmdbMovie) { | ||||||
|  |     return this.search(tmdbMovie.title) | ||||||
|  |       .then(plexMovies => plexMovies.some(plex => this.matchTmdbAndPlexMedia(plex, tmdbMovie))) | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   successfullResponse(response) { | ||||||
|  |     const { status, statusText } = response | ||||||
|  |  | ||||||
|  |     if (status === 200) { | ||||||
|  |       return response.json() | ||||||
|     } else { |     } else { | ||||||
|       match = matchingTitleAndYear(plex, tmdb); |       throw { message: statusText, status: status } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     return match; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   async existsInPlex(tmdb) { |  | ||||||
|     const plexMatch = await this.findPlexItemByTitleAndYear( |  | ||||||
|       tmdb.title, |  | ||||||
|       tmdb.year |  | ||||||
|     ); |  | ||||||
|     return plexMatch ? true : false; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   findPlexItemByTitleAndYear(title, year) { |  | ||||||
|     const query = { title, year }; |  | ||||||
|  |  | ||||||
|     return this.search(title).then(plexResults => { |  | ||||||
|       const matchesInPlex = plexResults.map(plex => |  | ||||||
|         this.matchTmdbAndPlexMedia(plex, query) |  | ||||||
|       ); |  | ||||||
|       const matchesIndex = matchesInPlex.findIndex(el => el === true); |  | ||||||
|       return matchesInPlex != -1 ? plexResults[matchesIndex] : null; |  | ||||||
|     }); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   getDirectLinkByTitleAndYear(title, year) { |  | ||||||
|     const machineIdentifierPromise = this.fetchMachineIdentifier(); |  | ||||||
|     const matchingObjectInPlexPromise = this.findPlexItemByTitleAndYear( |  | ||||||
|       title, |  | ||||||
|       year |  | ||||||
|     ); |  | ||||||
|  |  | ||||||
|     return Promise.all([ |  | ||||||
|       machineIdentifierPromise, |  | ||||||
|       matchingObjectInPlexPromise |  | ||||||
|     ]).then(([machineIdentifier, matchingObjectInPlex]) => { |  | ||||||
|       if ( |  | ||||||
|         matchingObjectInPlex == false || |  | ||||||
|         matchingObjectInPlex == null || |  | ||||||
|         matchingObjectInPlex["key"] == null || |  | ||||||
|         machineIdentifier == null |  | ||||||
|       ) |  | ||||||
|         return false; |  | ||||||
|  |  | ||||||
|       const keyUriComponent = fixedEncodeURIComponent(matchingObjectInPlex.key); |  | ||||||
|       return `https://app.plex.tv/desktop#!/server/${machineIdentifier}/details?key=${keyUriComponent}`; |  | ||||||
|     }); |  | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   search(query) { |   search(query) { | ||||||
|     const cacheKey = `${this.cacheTags.search}:${query}`; |     const url = `http://${this.plexIP}:${this.plexPort}/hubs/search?query=${query}` | ||||||
|  |  | ||||||
|     const url = `http://${this.plexIP}:${ |  | ||||||
|       this.plexPort |  | ||||||
|     }/hubs/search?query=${fixedEncodeURIComponent(query)}`; |  | ||||||
|     const options = { |     const options = { | ||||||
|       timeout: 20000, |       timeout: 2000, | ||||||
|       headers: { Accept: "application/json" } |       headers: { 'Accept': 'application/json' } | ||||||
|     }; |     } | ||||||
|  |  | ||||||
|     return new Promise((resolve, reject) => |     return fetch(url, options) | ||||||
|       this.cache |       .then(this.successfullResponse) | ||||||
|         .get(cacheKey) |       .then(this.mapResults) | ||||||
|         .catch(() => fetch(url, options)) // else fetch fresh data |       .catch(error => { | ||||||
|         .then(successfullResponse) |         if (error.type === 'request-timeout') { | ||||||
|         .then(results => this.cache.set(cacheKey, results, 21600)) // 6 hours |           throw { message: 'Plex did not respond', status: 408, success: false } | ||||||
|         .then(this.mapResults) |         } | ||||||
|         .then(resolve) |  | ||||||
|         .catch(error => { |  | ||||||
|           if (error != undefined && error.type === "request-timeout") { |  | ||||||
|             reject({ |  | ||||||
|               message: "Plex did not respond", |  | ||||||
|               status: 408, |  | ||||||
|               success: false |  | ||||||
|             }); |  | ||||||
|           } |  | ||||||
|  |  | ||||||
|           reject(error); |         throw error | ||||||
|         }) |  | ||||||
|     ); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   // this is not guarenteed to work, but if we see a movie or |  | ||||||
|   // show has been imported, this function can be helpfull to call |  | ||||||
|   // in order to try bust the cache preventing movieInfo and |  | ||||||
|   // showInfo from seeing updates through existsInPlex. |  | ||||||
|   bustSearchCacheWithTitle(title) { |  | ||||||
|     const query = title; |  | ||||||
|     const cacheKey = `${this.cacheTags.search}/${query}*`; |  | ||||||
|  |  | ||||||
|     this.cache.del( |  | ||||||
|       cacheKey, |  | ||||||
|       (error, |  | ||||||
|       response => { |  | ||||||
|         if (response == 1) return true; |  | ||||||
|  |  | ||||||
|         // TODO improve cache key matching by lowercasing it on the backend. |  | ||||||
|         // what do we actually need to check for if the key was deleted or not |  | ||||||
|         // it might be an error or another response code. |  | ||||||
|         console.log("Unable to delete, key might not exists"); |  | ||||||
|       }) |       }) | ||||||
|     ); |  | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   mapResults(response) { |   mapResults(response) { | ||||||
|     if ( |     if (response === undefined || response.MediaContainer === undefined) { | ||||||
|       response == null || |       console.log('response was not valid to map', response) | ||||||
|       response.MediaContainer == null || |       return [] | ||||||
|       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 => { |       .map(category => { | ||||||
|         if (category.type === "movie") { |         if (category.type === 'movie') { | ||||||
|           return category.Metadata; |           return category.Metadata.map(movie => { | ||||||
|         } else if (category.type === "show") { |             const ovie = Movie.convertFromPlexResponse(movie) | ||||||
|           return category.Metadata.map(convertPlexToShow); |             return ovie.createJsonResponse() | ||||||
|         } else if (category.type === "episode") { |           }) | ||||||
|           return category.Metadata.map(convertPlexToEpisode); |         } else if (category.type === 'show') { | ||||||
|  |           return category.Metadata.map(convertPlexToShow) | ||||||
|  |         } else if (category.type === 'episode') { | ||||||
|  |           return category.Metadata.map(convertPlexToEpisode) | ||||||
|         } |         } | ||||||
|       }) |       }) | ||||||
|       .filter(result => result !== undefined); |       .filter(result => result !== undefined) | ||||||
|  |       .flat() | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,130 +1,101 @@ | |||||||
| const PlexRepository = require("src/plex/plexRepository"); | const PlexRepository = require('src/plex/plexRepository'); | ||||||
| const configuration = require("src/config/configuration").getInstance(); | const Cache = require('src/tmdb/cache'); | ||||||
| const TMDB = require("src/tmdb/tmdb"); | const configuration = require('src/config/configuration').getInstance(); | ||||||
| const establishedDatabase = require("src/database/database"); | const TMDB = require('src/tmdb/tmdb'); | ||||||
|  | const establishedDatabase = require('src/database/database'); | ||||||
|  |  | ||||||
| const plexRepository = new PlexRepository(configuration.get("plex", "ip")); | const plexRepository = new PlexRepository(configuration.get('plex', 'ip')); | ||||||
| const tmdb = new TMDB(configuration.get("tmdb", "apiKey")); | const cache = new Cache(); | ||||||
|  | const tmdb = new TMDB(cache, configuration.get('tmdb', 'apiKey')); | ||||||
|  |  | ||||||
| class RequestRepository { | class RequestRepository { | ||||||
|   constructor(database) { |    constructor(database) { | ||||||
|     this.database = database || establishedDatabase; |       this.database = database || establishedDatabase; | ||||||
|     this.queries = { |       this.queries = { | ||||||
|       insertRequest: `INSERT INTO requests(id,title,year,poster_path,background_path,requested_by,ip,user_agent,type) |          insertRequest: `INSERT INTO requests(id,title,year,poster_path,background_path,requested_by,ip,user_agent,type) | ||||||
|           VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, |           VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, | ||||||
|       fetchRequestedItems: |          fetchRequestedItems: 'SELECT * FROM requests ORDER BY date DESC LIMIT 25 OFFSET ?*25-25', | ||||||
|         "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', | ||||||
|       fetchRequestedItemsByStatus: |          updateRequestedById: 'UPDATE requests SET status = ? WHERE id is ? AND type is ?', | ||||||
|         "SELECT * FROM requests WHERE status IS ? AND type LIKE ? ORDER BY date DESC LIMIT 25 OFFSET ?*25-25", |          checkIfIdRequested: 'SELECT * FROM requests WHERE id IS ? AND type IS ?', | ||||||
|       updateRequestedById: |          userRequests: 'SELECT * FROM requests WHERE requested_by IS ? ORDER BY date DESC', | ||||||
|         "UPDATE requests SET status = ? WHERE id is ? AND type is ?", |       }; | ||||||
|       checkIfIdRequested: "SELECT * FROM requests WHERE id IS ? AND type IS ?", |       this.cacheTags = { | ||||||
|       userRequests: |          search: 'se', | ||||||
|         "SELECT * FROM requests WHERE requested_by IS ? ORDER BY date DESC" |          lookup: 'i', | ||||||
|     }; |       }; | ||||||
|     this.cacheTags = { |    } | ||||||
|       search: "se", |  | ||||||
|       lookup: "i" |  | ||||||
|     }; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   search(query, type, page) { |    search(query, type, page) { | ||||||
|     return Promise.resolve() |       return Promise.resolve() | ||||||
|       .then(() => tmdb.search(query, type, page)) |          .then(() => tmdb.search(query, type, page)) | ||||||
|       .catch(error => Error(`error in the house${error}`)); |          .catch(error => Error(`error in the house${error}`)); | ||||||
|   } |    } | ||||||
|  |  | ||||||
|   lookup(identifier, type = "movie") { |    lookup(identifier, type = 'movie') { | ||||||
|     return Promise.resolve() |       return Promise.resolve() | ||||||
|       .then(() => tmdb.lookup(identifier, type)) |          .then(() => tmdb.lookup(identifier, type)) | ||||||
|       .then(tmdbMovie => this.checkID(tmdbMovie)) |          .then(tmdbMovie => this.checkID(tmdbMovie)) | ||||||
|       .then(tmdbMovie => plexRepository.inPlex(tmdbMovie)) |          .then(tmdbMovie => plexRepository.inPlex(tmdbMovie)) | ||||||
|       .catch(error => { |          .catch((error) => { | ||||||
|         throw new Error(error); |             throw new Error(error); | ||||||
|       }); |          }); | ||||||
|   } |    } | ||||||
|  |  | ||||||
|   checkID(tmdbMovie) { |    checkID(tmdbMovie) { | ||||||
|     return Promise.resolve() |       return Promise.resolve() | ||||||
|       .then(() => |          .then(() => this.database.get(this.queries.checkIfIdRequested, [tmdbMovie.id, tmdbMovie.type])) | ||||||
|         this.database.get(this.queries.checkIfIdRequested, [ |          .then((result, error) => { | ||||||
|           tmdbMovie.id, |             if (error) { throw new Error(error); } | ||||||
|           tmdbMovie.type |             tmdbMovie.requested = result ? true : false; | ||||||
|         ]) |             return tmdbMovie; | ||||||
|       ) |          }); | ||||||
|       .then((result, error) => { |    } | ||||||
|         if (error) { |  | ||||||
|           throw new Error(error); |  | ||||||
|         } |  | ||||||
|         tmdbMovie.requested = result ? true : false; |  | ||||||
|         return tmdbMovie; |  | ||||||
|       }); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   /** |    /** | ||||||
|    * Send request for given media id. |    * Send request for given media id. | ||||||
|    * @param {identifier, type} the id of the media object and type of media must be defined |    * @param {identifier, type} the id of the media object and type of media must be defined | ||||||
|    * @returns {Promise} If nothing has gone wrong. |    * @returns {Promise} If nothing has gone wrong. | ||||||
|    */ |    */ | ||||||
|   sendRequest(identifier, type, ip, user_agent, user) { |    sendRequest(identifier, type, ip, user_agent, user) { | ||||||
|     return Promise.resolve() |    	return Promise.resolve() | ||||||
|       .then(() => tmdb.lookup(identifier, type)) |    	.then(() => tmdb.lookup(identifier, type)) | ||||||
|       .then(movie => { |       .then((movie) => { | ||||||
|         const username = user === undefined ? undefined : user.username; |          const username = user === undefined ? undefined : user.username; | ||||||
|         // Add request to database |          // Add request to database | ||||||
|         return this.database.run(this.queries.insertRequest, [ |          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]); | ||||||
|           movie.id, |  | ||||||
|           movie.title, |  | ||||||
|           movie.year, |  | ||||||
|           movie.poster_path, |  | ||||||
|           movie.background_path, |  | ||||||
|           username, |  | ||||||
|           ip, |  | ||||||
|           user_agent, |  | ||||||
|           movie.type |  | ||||||
|         ]); |  | ||||||
|       }); |       }); | ||||||
|   } |    } | ||||||
|  |  | ||||||
|   fetchRequested(status, page = "1", type = "%") { |    fetchRequested(status, page = '1', type = '%') { | ||||||
|     return Promise.resolve().then(() => { |    	return Promise.resolve() | ||||||
|       if ( |    	.then(() => { | ||||||
|         status === "requested" || | 	      if (status === 'requested' || status === 'downloading' || status === 'downloaded') | ||||||
|         status === "downloading" || | 	         return this.database.all(this.queries.fetchRequestedItemsByStatus, [status, type, page]); | ||||||
|         status === "downloaded" | 	      else | ||||||
|       ) | 	         return this.database.all(this.queries.fetchRequestedItems, page); | ||||||
|         return this.database.all(this.queries.fetchRequestedItemsByStatus, [ |    	}) | ||||||
|           status, |    } | ||||||
|           type, |  | ||||||
|           page |  | ||||||
|         ]); |  | ||||||
|       else return this.database.all(this.queries.fetchRequestedItems, page); |  | ||||||
|     }); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   userRequests(username) { |    userRequests(user) { | ||||||
|     return Promise.resolve() |       return Promise.resolve() | ||||||
|       .then(() => this.database.all(this.queries.userRequests, username)) |          .then(() => this.database.all(this.queries.userRequests, user.username)) | ||||||
|       .catch(error => { |          .catch((error) => { | ||||||
|         if (String(error).includes("no such column")) { |             if (String(error).includes('no such column')) { | ||||||
|           throw new Error("Username not found"); |                throw new Error('Username not found'); | ||||||
|         } |             } | ||||||
|         throw new Error("Unable to fetch your requests"); |             throw new Error('Unable to fetch your requests'); | ||||||
|       }) |          }) | ||||||
|       .then(result => { |          .then((result) => { | ||||||
|         // TODO do a correct mapping before sending, not just a dump of the database |             // TODO do a correct mapping before sending, not just a dump of the database | ||||||
|         result.map(item => (item.poster = item.poster_path)); |             result.map(item => item.poster = item.poster_path) | ||||||
|         return result; |             return result | ||||||
|       }); |          }); | ||||||
|   } |    } | ||||||
|  |  | ||||||
|   updateRequestedById(id, type, status) { |    updateRequestedById(id, type, status) { | ||||||
|     return this.database.run(this.queries.updateRequestedById, [ |       return this.database.run(this.queries.updateRequestedById, [status, id, type]); | ||||||
|       status, |    } | ||||||
|       id, |  | ||||||
|       type |  | ||||||
|     ]); |  | ||||||
|   } |  | ||||||
| } | } | ||||||
|  |  | ||||||
| module.exports = RequestRepository; | module.exports = RequestRepository; | ||||||
|   | |||||||
							
								
								
									
										39
									
								
								seasoned_api/src/plex/updateRequestsInPlex.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								seasoned_api/src/plex/updateRequestsInPlex.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,39 @@ | |||||||
|  | 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,7 +1,9 @@ | |||||||
| const assert = require('assert') | const assert = require('assert') | ||||||
| const configuration = require('src/config/configuration').getInstance(); | const configuration = require('src/config/configuration').getInstance(); | ||||||
|  | const Cache = require('src/tmdb/cache'); | ||||||
| const TMDB = require('src/tmdb/tmdb'); | const TMDB = require('src/tmdb/tmdb'); | ||||||
| const tmdb = new TMDB(configuration.get('tmdb', 'apiKey')); | const cache = new Cache(); | ||||||
|  | const tmdb = new TMDB(cache, configuration.get('tmdb', 'apiKey')); | ||||||
| const establishedDatabase = require('src/database/database'); | const establishedDatabase = require('src/database/database'); | ||||||
| const utils = require('./utils'); | const utils = require('./utils'); | ||||||
|  |  | ||||||
| @@ -84,18 +86,18 @@ class RequestRepository { | |||||||
|    * @param {tmdb} tmdb class of movie|show to add |    * @param {tmdb} tmdb class of movie|show to add | ||||||
|    * @returns {Promise} |    * @returns {Promise} | ||||||
|    */ |    */ | ||||||
|   requestFromTmdb(tmdb, ip, user_agent, username) { |   requestFromTmdb(tmdb, ip, user_agent, user) { | ||||||
|     return Promise.resolve() |     return Promise.resolve() | ||||||
|       .then(() => this.database.get(this.queries.read, [tmdb.id, tmdb.type])) |     .then(() => this.database.get(this.queries.read, [tmdb.id, tmdb.type])) | ||||||
|       .then(row => assert.equal(row, undefined, 'Id has already been requested')) |     .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])) |     .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) => { |     .catch((error) => { | ||||||
|         if (error.name === 'AssertionError' || error.message.endsWith('been requested')) { |       if (error.name === 'AssertionError' || error.message.endsWith('been requested')) { | ||||||
|           throw new Error('This id is already requested', error.message); |         throw new Error('This id is already requested', error.message); | ||||||
|         } |       } | ||||||
|         console.log('Error @ request.addTmdb:', error); |       console.log('Error @ request.addTmdb:', error); | ||||||
|         throw new Error('Could not add request'); |       throw new Error('Could not add request'); | ||||||
|       }); |     }); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   /** |   /** | ||||||
|   | |||||||
| @@ -28,23 +28,17 @@ class SearchHistory { | |||||||
|  |  | ||||||
|    /** |    /** | ||||||
|    * Creates a new search entry in the database. |    * Creates a new search entry in the database. | ||||||
|    * @param {String} username logged in user doing the search |    * @param {User} user a new user | ||||||
|    * @param {String} searchQuery the query the user searched for |    * @param {String} searchQuery the query the user searched for | ||||||
|    * @returns {Promise} |    * @returns {Promise} | ||||||
|    */ |    */ | ||||||
|    create(username, searchQuery) { |    create(user, searchQuery) { | ||||||
|       return this.database.run(this.queries.create, [searchQuery, username]) |       return Promise.resolve() | ||||||
|          .catch(error => { |          .then(() => this.database.run(this.queries.create, [searchQuery, user])) | ||||||
|  |          .catch((error) => { | ||||||
|             if (error.message.includes('FOREIGN')) { |             if (error.message.includes('FOREIGN')) { | ||||||
|                throw new Error('Could not create search history.'); |                throw new Error('Could not create search history.'); | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             throw { |  | ||||||
|                success: false, |  | ||||||
|                status: 500, |  | ||||||
|                message: 'An unexpected error occured', |  | ||||||
|                source: 'database' |  | ||||||
|             } |  | ||||||
|          }); |          }); | ||||||
|    } |    } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,74 +0,0 @@ | |||||||
| 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,74 +1,31 @@ | |||||||
| const moviedb = require("km-moviedb"); | const moviedb = require('km-moviedb'); | ||||||
| const RedisCache = require("src/cache/redis"); |  | ||||||
| const redisCache = new RedisCache(); |  | ||||||
|  |  | ||||||
| const { | const { Movie, Show, Person, Credits, ReleaseDates } = require('src/tmdb/types'); | ||||||
|   Movie, | // const { tmdbInfo } = require('src/tmdb/types') | ||||||
|   Show, |  | ||||||
|   Person, |  | ||||||
|   Credits, |  | ||||||
|   ReleaseDates |  | ||||||
| } = require("src/tmdb/types"); |  | ||||||
|  |  | ||||||
| const tmdbErrorResponse = (error, typeString = undefined) => { |  | ||||||
|   if (error.status === 404) { |  | ||||||
|     let message = error.response.body.status_message; |  | ||||||
|  |  | ||||||
|     throw { |  | ||||||
|       status: 404, |  | ||||||
|       message: message.slice(0, -1) + " in tmdb." |  | ||||||
|     }; |  | ||||||
|   } else if (error.status === 401) { |  | ||||||
|     throw { |  | ||||||
|       status: 401, |  | ||||||
|       message: error.response.body.status_message |  | ||||||
|     }; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   throw { |  | ||||||
|     status: 500, |  | ||||||
|     message: `An unexpected error occured while fetching ${typeString} from tmdb` |  | ||||||
|   }; |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| class TMDB { | class TMDB { | ||||||
|   constructor(apiKey, cache, tmdbLibrary) { |   constructor(cache, apiKey, tmdbLibrary) { | ||||||
|  |     this.cache = cache; | ||||||
|     this.tmdbLibrary = tmdbLibrary || moviedb(apiKey); |     this.tmdbLibrary = tmdbLibrary || moviedb(apiKey); | ||||||
|  |  | ||||||
|     this.cache = cache || redisCache; |  | ||||||
|     this.cacheTags = { |     this.cacheTags = { | ||||||
|       multiSearch: "mus", |       multiSearch: 'mus',  | ||||||
|       movieSearch: "mos", |       movieSearch: 'mos',  | ||||||
|       showSearch: "ss", |       showSearch: 'ss', | ||||||
|       personSearch: "ps", |       personSearch: 'ps', | ||||||
|       movieInfo: "mi", |       movieInfo: 'mi', | ||||||
|       movieCredits: "mc", |       movieCredits: 'mc', | ||||||
|       movieReleaseDates: "mrd", |       movieReleaseDates: 'mrd', | ||||||
|       movieImages: "mimg", |       showInfo: 'si', | ||||||
|       showInfo: "si", |       showCredits: 'sc', | ||||||
|       showCredits: "sc", |       personInfo: 'pi', | ||||||
|       personInfo: "pi", |       miscNowPlayingMovies: 'npm', | ||||||
|       personCredits: "pc", |       miscPopularMovies: 'pm', | ||||||
|       miscNowPlayingMovies: "npm", |       miscTopRatedMovies: 'tpm', | ||||||
|       miscPopularMovies: "pm", |       miscUpcomingMovies: 'um', | ||||||
|       miscTopRatedMovies: "tpm", |       tvOnTheAir: 'toa', | ||||||
|       miscUpcomingMovies: "um", |       miscPopularTvs: 'pt', | ||||||
|       tvOnTheAir: "toa", |       miscTopRatedTvs: 'trt', | ||||||
|       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))) |  | ||||||
|     ); |  | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   /** |   /** | ||||||
| @@ -80,11 +37,13 @@ class TMDB { | |||||||
|    */ |    */ | ||||||
|   movieInfo(identifier) { |   movieInfo(identifier) { | ||||||
|     const query = { id: identifier }; |     const query = { id: identifier }; | ||||||
|     const cacheKey = `tmdb/${this.cacheTags.movieInfo}:${identifier}`; |     const cacheKey = `${this.cacheTags.movieInfo}:${identifier}`; | ||||||
|  |  | ||||||
|     return this.getFromCacheOrFetchFromTmdb(cacheKey, "movieInfo", query) |     return this.cache.get(cacheKey) | ||||||
|       .then(movie => this.cache.set(cacheKey, movie, this.defaultTTL)) |       .catch(() => this.tmdb('movieInfo', query)) | ||||||
|       .then(movie => Movie.convertFromTmdbResponse(movie)); |       .catch(tmdbError => tmdbErrorResponse(tmdbError, 'movie info')) | ||||||
|  |       .then(movie => this.cache.set(cacheKey, movie, 1)) | ||||||
|  |       .then(movie => Movie.convertFromTmdbResponse(movie)) | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   /** |   /** | ||||||
| @@ -93,12 +52,14 @@ class TMDB { | |||||||
|    * @returns {Promise} movie cast object |    * @returns {Promise} movie cast object | ||||||
|    */ |    */ | ||||||
|   movieCredits(identifier) { |   movieCredits(identifier) { | ||||||
|     const query = { id: identifier }; |     const query = { id: identifier } | ||||||
|     const cacheKey = `tmdb/${this.cacheTags.movieCredits}:${identifier}`; |     const cacheKey = `${this.cacheTags.movieCredits}:${identifier}` | ||||||
|  |  | ||||||
|     return this.getFromCacheOrFetchFromTmdb(cacheKey, "movieCredits", query) |     return this.cache.get(cacheKey) | ||||||
|       .then(credits => this.cache.set(cacheKey, credits, this.defaultTTL)) |       .catch(() => this.tmdb('movieCredits', query)) | ||||||
|       .then(credits => Credits.convertFromTmdbResponse(credits)); |       .catch(tmdbError => tmdbErrorResponse(tmdbError, 'movie credits')) | ||||||
|  |       .then(credits => this.cache.set(cacheKey, credits, 1)) | ||||||
|  |       .then(credits => Credits.convertFromTmdbResponse(credits)) | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   /** |   /** | ||||||
| @@ -108,10 +69,12 @@ class TMDB { | |||||||
|    */ |    */ | ||||||
|   movieReleaseDates(identifier) { |   movieReleaseDates(identifier) { | ||||||
|     const query = { id: identifier } |     const query = { id: identifier } | ||||||
|     const cacheKey = `tmdb/${this.cacheTags.movieReleaseDates}:${identifier}` |     const cacheKey = `${this.cacheTags.movieReleaseDates}:${identifier}` | ||||||
|  |  | ||||||
|     return this.getFromCacheOrFetchFromTmdb(cacheKey, 'movieReleaseDates', query) |     return this.cache.get(cacheKey) | ||||||
|       .then(releaseDates => this.cache.set(cacheKey, releaseDates, this.defaultTTL)) |       .catch(() => this.tmdb('movieReleaseDates', query)) | ||||||
|  |       .catch(tmdbError => tmdbErrorResponse(tmdbError, 'movie release dates')) | ||||||
|  |       .then(releaseDates => this.cache.set(cacheKey, releaseDates, 1)) | ||||||
|       .then(releaseDates => ReleaseDates.convertFromTmdbResponse(releaseDates)) |       .then(releaseDates => ReleaseDates.convertFromTmdbResponse(releaseDates)) | ||||||
|   } |   } | ||||||
|   |   | ||||||
| @@ -123,20 +86,24 @@ class TMDB { | |||||||
|    */ |    */ | ||||||
|   showInfo(identifier) { |   showInfo(identifier) { | ||||||
|     const query = { id: identifier }; |     const query = { id: identifier }; | ||||||
|     const cacheKey = `tmdb/${this.cacheTags.showInfo}:${identifier}`; |     const cacheKey = `${this.cacheTags.showInfo}:${identifier}`; | ||||||
|  |  | ||||||
|     return this.getFromCacheOrFetchFromTmdb(cacheKey, "tvInfo", query) |     return this.cache.get(cacheKey) | ||||||
|       .then(show => this.cache.set(cacheKey, show, this.defaultTTL)) |       .catch(() => this.tmdb('tvInfo', query)) | ||||||
|       .then(show => Show.convertFromTmdbResponse(show)); |       .catch(tmdbError => tmdbErrorResponse(tmdbError, 'tv info')) | ||||||
|  |       .then(show => this.cache.set(cacheKey, show, 1)) | ||||||
|  |       .then(show => Show.convertFromTmdbResponse(show)) | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   showCredits(identifier) { |   showCredits(identifier) { | ||||||
|     const query = { id: identifier }; |     const query = { id: identifier } | ||||||
|     const cacheKey = `tmdb/${this.cacheTags.showCredits}:${identifier}`; |     const cacheKey = `${this.cacheTags.showCredits}:${identifier}` | ||||||
|  |  | ||||||
|     return this.getFromCacheOrFetchFromTmdb(cacheKey, "tvCredits", query) |     return this.cache.get(cacheKey) | ||||||
|       .then(credits => this.cache.set(cacheKey, credits, this.defaultTTL)) |       .catch(() => this.tmdb('tvCredits', query)) | ||||||
|       .then(credits => Credits.convertFromTmdbResponse(credits)); |       .catch(tmdbError => tmdbErrorResponse(tmdbError, 'show credits')) | ||||||
|  |       .then(credits => this.cache.set(cacheKey, credits, 1)) | ||||||
|  |       .then(credits => Credits.convertFromTmdbResponse(credits)) | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   /** |   /** | ||||||
| @@ -147,32 +114,22 @@ class TMDB { | |||||||
|    */ |    */ | ||||||
|   personInfo(identifier) { |   personInfo(identifier) { | ||||||
|     const query = { id: identifier }; |     const query = { id: identifier }; | ||||||
|     const cacheKey = `tmdb/${this.cacheTags.personInfo}:${identifier}`; |     const cacheKey = `${this.cacheTags.personInfo}:${identifier}`; | ||||||
|  |  | ||||||
|     return this.getFromCacheOrFetchFromTmdb(cacheKey, "personInfo", query) |     return this.cache.get(cacheKey) | ||||||
|       .then(person => this.cache.set(cacheKey, person, this.defaultTTL)) |       .catch(() => this.tmdb('personInfo', query)) | ||||||
|       .then(person => Person.convertFromTmdbResponse(person)); |       .catch(tmdbError => tmdbErrorResponse(tmdbError, 'person info')) | ||||||
|  |       .then(person => this.cache.set(cacheKey, person, 1)) | ||||||
|  |       .then(person => Person.convertFromTmdbResponse(person)) | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   personCredits(identifier) { |   multiSearch(search_query, page=1) { | ||||||
|     const query = { id: identifier }; |     const query = { query: search_query, page: page }; | ||||||
|     const cacheKey = `tmdb/${this.cacheTags.personCredits}:${identifier}`; |     const cacheKey = `${this.cacheTags.multiSearch}:${page}:${search_query}`; | ||||||
|  |     return this.cache.get(cacheKey) | ||||||
|     return this.getFromCacheOrFetchFromTmdb( |       .catch(() => this.tmdb('searchMulti', query)) | ||||||
|       cacheKey, |       .catch(tmdbError => tmdbErrorResponse(tmdbError, 'search results')) | ||||||
|       "personCombinedCredits", |       .then(response => this.cache.set(cacheKey, response, 1)) | ||||||
|       query |  | ||||||
|     ) |  | ||||||
|       .then(credits => this.cache.set(cacheKey, credits, this.defaultTTL)) |  | ||||||
|       .then(credits => Credits.convertFromTmdbResponse(credits)); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   multiSearch(search_query, page = 1, include_adult = true) { |  | ||||||
|     const query = { query: search_query, page, include_adult }; |  | ||||||
|     const cacheKey = `tmdb/${this.cacheTags.multiSearch}:${page}:${search_query}:${include_adult}`; |  | ||||||
|  |  | ||||||
|     return this.getFromCacheOrFetchFromTmdb(cacheKey, "searchMulti", query) |  | ||||||
|       .then(response => this.cache.set(cacheKey, response, this.defaultTTL)) |  | ||||||
|       .then(response => this.mapResults(response)); |       .then(response => this.mapResults(response)); | ||||||
|   } |   } | ||||||
|  |  | ||||||
| @@ -182,13 +139,15 @@ class TMDB { | |||||||
|    * @param {Number} page representing pagination of results |    * @param {Number} page representing pagination of results | ||||||
|    * @returns {Promise} dict with query results, current page and total_pages |    * @returns {Promise} dict with query results, current page and total_pages | ||||||
|    */ |    */ | ||||||
|   movieSearch(search_query, page = 1, include_adult = true) { |   movieSearch(query, page=1) { | ||||||
|     const tmdbquery = { query: search_query, page, include_adult }; |     const tmdbquery = { query: query, page: page }; | ||||||
|     const cacheKey = `tmdb/${this.cacheTags.movieSearch}:${page}:${search_query}:${include_adult}`; |     const cacheKey = `${this.cacheTags.movieSearch}:${page}:${query}`; | ||||||
|  |  | ||||||
|     return this.getFromCacheOrFetchFromTmdb(cacheKey, "searchMovie", tmdbquery) |     return this.cache.get(cacheKey) | ||||||
|       .then(response => this.cache.set(cacheKey, response, this.defaultTTL)) |       .catch(() => this.tmdb('searchMovie', tmdbquery)) | ||||||
|       .then(response => this.mapResults(response, "movie")); |       .catch(tmdbError => tmdbErrorResponse(tmdbError, 'movie search results')) | ||||||
|  |       .then(response => this.cache.set(cacheKey, response, 1)) | ||||||
|  |       .then(response => this.mapResults(response, 'movie')) | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   /** |   /** | ||||||
| @@ -197,13 +156,15 @@ class TMDB { | |||||||
|    * @param {Number} page representing pagination of results |    * @param {Number} page representing pagination of results | ||||||
|    * @returns {Promise} dict with query results, current page and total_pages |    * @returns {Promise} dict with query results, current page and total_pages | ||||||
|    */ |    */ | ||||||
|   showSearch(search_query, page = 1, include_adult = true) { |   showSearch(query, page=1) { | ||||||
|     const tmdbquery = { query: search_query, page, include_adult }; |     const tmdbquery = { query: query, page: page }; | ||||||
|     const cacheKey = `tmdb/${this.cacheTags.showSearch}:${page}:${search_query}:${include_adult}`; |     const cacheKey = `${this.cacheTags.showSearch}:${page}:${query}`; | ||||||
|  |  | ||||||
|     return this.getFromCacheOrFetchFromTmdb(cacheKey, "searchTv", tmdbquery) |     return this.cache.get(cacheKey) | ||||||
|       .then(response => this.cache.set(cacheKey, response, this.defaultTTL)) |       .catch(() => this.tmdb('searchTv', tmdbquery)) | ||||||
|       .then(response => this.mapResults(response, "show")); |       .catch(tmdbError => tmdbErrorResponse(tmdbError, 'tv search results')) | ||||||
|  |       .then(response => this.cache.set(cacheKey, response, 1)) | ||||||
|  |       .then(response => this.mapResults(response, 'show')) | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   /** |   /** | ||||||
| @@ -212,31 +173,37 @@ class TMDB { | |||||||
|    * @param {Number} page representing pagination of results |    * @param {Number} page representing pagination of results | ||||||
|    * @returns {Promise} dict with query results, current page and total_pages |    * @returns {Promise} dict with query results, current page and total_pages | ||||||
|    */ |    */ | ||||||
|   personSearch(search_query, page = 1, include_adult = true) { |   personSearch(query, page=1) { | ||||||
|     const tmdbquery = { query: search_query, page, include_adult }; |  | ||||||
|     const cacheKey = `tmdb/${this.cacheTags.personSearch}:${page}:${search_query}:${include_adult}`; |  | ||||||
|  |  | ||||||
|     return this.getFromCacheOrFetchFromTmdb(cacheKey, "searchPerson", tmdbquery) |     const tmdbquery = { query: query, page: page, include_adult: true }; | ||||||
|       .then(response => this.cache.set(cacheKey, response, this.defaultTTL)) |     const cacheKey = `${this.cacheTags.personSearch}:${page}:${query}`; | ||||||
|       .then(response => this.mapResults(response, "person")); |  | ||||||
|  |     return this.cache.get(cacheKey) | ||||||
|  |       .catch(() => this.tmdb('searchPerson', tmdbquery)) | ||||||
|  |       .catch(tmdbError => tmdbErrorResponse(tmdbError, 'person search results')) | ||||||
|  |       .then(response => this.cache.set(cacheKey, response, 1)) | ||||||
|  |       .then(response => this.mapResults(response, 'person')) | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   movieList(listname, page = 1) { |   movieList(listname, page = 1) { | ||||||
|     const query = { page: page }; |     const query = { page: page }; | ||||||
|     const cacheKey = `tmdb/${this.cacheTags[listname]}:${page}`; |     const cacheKey = `${this.cacheTags[listname]}:${page}`; | ||||||
|  |     return this.cache.get(cacheKey) | ||||||
|     return this.getFromCacheOrFetchFromTmdb(cacheKey, listname, query) |       .catch(() => this.tmdb(listname, query)) | ||||||
|       .then(response => this.cache.set(cacheKey, response, this.defaultTTL)) |       .catch(tmdbError => this.tmdbErrorResponse(tmdbError, 'movie list ' + listname)) | ||||||
|       .then(response => this.mapResults(response, "movie")); |       .then(response => this.cache.set(cacheKey, response, 1)) | ||||||
|  |       .then(response => this.mapResults(response, 'movie')) | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   showList(listname, page = 1) { |   showList(listname, page = 1) { | ||||||
|     const query = { page: page }; |     const query = { page: page }; | ||||||
|     const cacheKey = `tmdb/${this.cacheTags[listname]}:${page}`; |     const cacheKey = `${this.cacheTags[listname]}:${page}`; | ||||||
|  |  | ||||||
|     return this.getFromCacheOrFetchFromTmdb(cacheKey, listName, query) |     return this.cache.get(cacheKey) | ||||||
|       .then(response => this.cache.set(cacheKey, response, this.defaultTTL)) |       .catch(() => this.tmdb(listname, query)) | ||||||
|       .then(response => this.mapResults(response, "show")); |       .catch(tmdbError => this.tmdbErrorResponse(tmdbError, 'show list ' + listname)) | ||||||
|  |       .then(response => this.cache.set(cacheKey, response, 1)) | ||||||
|  |       .then(response => this.mapResults(response, 'show')) | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   /** |   /** | ||||||
| @@ -245,26 +212,29 @@ class TMDB { | |||||||
|    * @param {String} The type declared in listSearch. |    * @param {String} The type declared in listSearch. | ||||||
|    * @returns {Promise} dict with tmdb results, mapped as movie/show objects. |    * @returns {Promise} dict with tmdb results, mapped as movie/show objects. | ||||||
|    */ |    */ | ||||||
|   mapResults(response, type = undefined) { |   mapResults(response, type=undefined) { | ||||||
|  |     // console.log(response.results) | ||||||
|  |     // response.results.map(te => console.table(te)) | ||||||
|  |  | ||||||
|     let results = response.results.map(result => { |     let results = response.results.map(result => { | ||||||
|       if (type === "movie" || result.media_type === "movie") { |       if (type === 'movie' || result.media_type === 'movie') { | ||||||
|         const movie = Movie.convertFromTmdbResponse(result); |         const movie = Movie.convertFromTmdbResponse(result) | ||||||
|         return movie.createJsonResponse(); |         return movie.createJsonResponse() | ||||||
|       } else if (type === "show" || result.media_type === "tv") { |       } else if (type === 'show' || result.media_type === 'tv') { | ||||||
|         const show = Show.convertFromTmdbResponse(result); |         const show = Show.convertFromTmdbResponse(result) | ||||||
|         return show.createJsonResponse(); |         return show.createJsonResponse() | ||||||
|       } else if (type === "person" || result.media_type === "person") { |       } else if (type === 'person' || result.media_type === 'person') { | ||||||
|         const person = Person.convertFromTmdbResponse(result); |         const person = Person.convertFromTmdbResponse(result) | ||||||
|         return person.createJsonResponse(); |         return person.createJsonResponse() | ||||||
|       } |       } | ||||||
|     }); |     }) | ||||||
|  |  | ||||||
|     return { |     return { | ||||||
|       results: results, |       results: results, | ||||||
|       page: response.page, |       page: response.page, | ||||||
|       total_results: response.total_results, |       total_results: response.total_results, | ||||||
|       total_pages: response.total_pages |       total_pages: response.total_pages | ||||||
|     }; |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   /** |   /** | ||||||
| @@ -273,21 +243,43 @@ class TMDB { | |||||||
|    * @param {Object} argument argument to function being called |    * @param {Object} argument argument to function being called | ||||||
|    * @returns {Promise} succeeds if callback succeeds |    * @returns {Promise} succeeds if callback succeeds | ||||||
|    */ |    */ | ||||||
|   tmdb(method, argument) { |    tmdb(method, argument) { | ||||||
|     return new Promise((resolve, reject) => { |       return new Promise((resolve, reject) => { | ||||||
|       const callback = (error, reponse) => { |          const callback = (error, reponse) => { | ||||||
|         if (error) { |             if (error) { | ||||||
|           return reject(error); |                return reject(error); | ||||||
|         } |             } | ||||||
|         resolve(reponse); |             resolve(reponse); | ||||||
|       }; |          }; | ||||||
|  |  | ||||||
|       if (!argument) { |          if (!argument) { | ||||||
|         this.tmdbLibrary[method](callback); |             this.tmdbLibrary[method](callback); | ||||||
|       } else { |          } else { | ||||||
|         this.tmdbLibrary[method](argument, callback); |             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` | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,7 +1,7 @@ | |||||||
| const Movie = require('./types/movie.js') | import Movie from './types/movie.js' | ||||||
| const Show = require('./types/show.js') | import Show from './types/show.js' | ||||||
| const Person = require('./types/person.js') | import Person from './types/person.js' | ||||||
| const Credits = require('./types/credits.js') | import Credits from './types/credits.js' | ||||||
| const ReleaseDates = require('./types/releaseDates.js') | import ReleaseDates from './types/releaseDates.js' | ||||||
|  |  | ||||||
| module.exports = { Movie, Show, Person, Credits, ReleaseDates } | module.exports = { Movie, Show, Person, Credits, ReleaseDates } | ||||||
|   | |||||||
| @@ -1,55 +1,20 @@ | |||||||
| import Movie from "./movie"; | class Credits {  | ||||||
| import Show from "./show"; |   constructor(id, cast=[], crew=[]) { | ||||||
|  |  | ||||||
| class Credits { |  | ||||||
|   constructor(id, cast = [], crew = []) { |  | ||||||
|     this.id = id; |     this.id = id; | ||||||
|     this.cast = cast; |     this.cast = cast; | ||||||
|     this.crew = crew; |     this.crew = crew; | ||||||
|     this.type = "credits"; |     this.type = 'credits'; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   static convertFromTmdbResponse(response) { |   static convertFromTmdbResponse(response) { | ||||||
|     const { id, cast, crew } = response; |     const { id, cast, crew } = response; | ||||||
|  |  | ||||||
|     const allCast = cast.map(cast => { |     const allCast = cast.map(cast =>  | ||||||
|       if (cast["media_type"]) { |       new CastMember(cast.character, cast.gender, cast.id, cast.name, cast.profile_path)) | ||||||
|         if (cast.media_type === "movie") { |     const allCrew = crew.map(crew => | ||||||
|           return CreditedMovie.convertFromTmdbResponse(cast); |       new CrewMember(crew.department, crew.gender, crew.id, crew.job, crew.name, crew.profile_path)) | ||||||
|         } else if (cast.media_type === "tv") { |  | ||||||
|           return CreditedShow.convertFromTmdbResponse(cast); |  | ||||||
|         } |  | ||||||
|       } |  | ||||||
|  |  | ||||||
|       return new CastMember( |     return new Credits(id, allCast, allCrew) | ||||||
|         cast.character, |  | ||||||
|         cast.gender, |  | ||||||
|         cast.id, |  | ||||||
|         cast.name, |  | ||||||
|         cast.profile_path |  | ||||||
|       ); |  | ||||||
|     }); |  | ||||||
|  |  | ||||||
|     const allCrew = crew.map(crew => { |  | ||||||
|       if (cast["media_type"]) { |  | ||||||
|         if (cast.media_type === "movie") { |  | ||||||
|           return CreditedMovie.convertFromTmdbResponse(cast); |  | ||||||
|         } else if (cast.media_type === "tv") { |  | ||||||
|           return CreditedShow.convertFromTmdbResponse(cast); |  | ||||||
|         } |  | ||||||
|       } |  | ||||||
|  |  | ||||||
|       return new CrewMember( |  | ||||||
|         crew.department, |  | ||||||
|         crew.gender, |  | ||||||
|         crew.id, |  | ||||||
|         crew.job, |  | ||||||
|         crew.name, |  | ||||||
|         crew.profile_path |  | ||||||
|       ); |  | ||||||
|     }); |  | ||||||
|  |  | ||||||
|     return new Credits(id, allCast, allCrew); |  | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   createJsonResponse() { |   createJsonResponse() { | ||||||
| @@ -57,7 +22,7 @@ class Credits { | |||||||
|       id: this.id, |       id: this.id, | ||||||
|       cast: this.cast.map(cast => cast.createJsonResponse()), |       cast: this.cast.map(cast => cast.createJsonResponse()), | ||||||
|       crew: this.crew.map(crew => crew.createJsonResponse()) |       crew: this.crew.map(crew => crew.createJsonResponse()) | ||||||
|     }; |     } | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -68,7 +33,7 @@ class CastMember { | |||||||
|     this.id = id; |     this.id = id; | ||||||
|     this.name = name; |     this.name = name; | ||||||
|     this.profile_path = profile_path; |     this.profile_path = profile_path; | ||||||
|     this.type = "person"; |     this.type = 'cast member'; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   createJsonResponse() { |   createJsonResponse() { | ||||||
| @@ -79,7 +44,7 @@ class CastMember { | |||||||
|       name: this.name, |       name: this.name, | ||||||
|       profile_path: this.profile_path, |       profile_path: this.profile_path, | ||||||
|       type: this.type |       type: this.type | ||||||
|     }; |     } | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -91,7 +56,7 @@ class CrewMember { | |||||||
|     this.job = job; |     this.job = job; | ||||||
|     this.name = name; |     this.name = name; | ||||||
|     this.profile_path = profile_path; |     this.profile_path = profile_path; | ||||||
|     this.type = "person"; |     this.type = 'crew member'; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   createJsonResponse() { |   createJsonResponse() { | ||||||
| @@ -103,11 +68,8 @@ class CrewMember { | |||||||
|       name: this.name, |       name: this.name, | ||||||
|       profile_path: this.profile_path, |       profile_path: this.profile_path, | ||||||
|       type: this.type |       type: this.type | ||||||
|     }; |     } | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| class CreditedMovie extends Movie {} |  | ||||||
| class CreditedShow extends Show {} |  | ||||||
|  |  | ||||||
| module.exports = Credits; | module.exports = Credits; | ||||||
|   | |||||||
| @@ -1,54 +1,23 @@ | |||||||
| class Person { | class Person {  | ||||||
|   constructor( |   constructor(id, name, poster=undefined, birthday=undefined, deathday=undefined, | ||||||
|     id, |               adult=undefined, knownForDepartment=undefined) { | ||||||
|     name, |  | ||||||
|     poster = undefined, |  | ||||||
|     birthday = undefined, |  | ||||||
|     deathday = undefined, |  | ||||||
|     adult = undefined, |  | ||||||
|     placeOfBirth = undefined, |  | ||||||
|     biography = undefined, |  | ||||||
|     knownForDepartment = undefined |  | ||||||
|   ) { |  | ||||||
|     this.id = id; |     this.id = id; | ||||||
|     this.name = name; |     this.name = name; | ||||||
|     this.poster = poster; |     this.poster = poster; | ||||||
|     this.birthday = birthday; |     this.birthday = birthday; | ||||||
|     this.deathday = deathday; |     this.deathday = deathday; | ||||||
|     this.adult = adult; |     this.adult = adult; | ||||||
|     this.placeOfBirth = placeOfBirth; |  | ||||||
|     this.biography = biography; |  | ||||||
|     this.knownForDepartment = knownForDepartment; |     this.knownForDepartment = knownForDepartment; | ||||||
|     this.type = "person"; |     this.type = 'person'; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   static convertFromTmdbResponse(response) { |   static convertFromTmdbResponse(response) { | ||||||
|     const { |     const { id, name, poster, birthday, deathday, adult, known_for_department } = response; | ||||||
|       id, |  | ||||||
|       name, |  | ||||||
|       profile_path, |  | ||||||
|       birthday, |  | ||||||
|       deathday, |  | ||||||
|       adult, |  | ||||||
|       place_of_birth, |  | ||||||
|       biography, |  | ||||||
|       known_for_department |  | ||||||
|     } = response; |  | ||||||
|  |  | ||||||
|     const birthDay = new Date(birthday); |     const birthDay = new Date(birthday) | ||||||
|     const deathDay = deathday ? new Date(deathday) : null; |     const deathDay = deathday ? new Date(deathday) : null | ||||||
|  |  | ||||||
|     return new Person( |     return new Person(id, name, poster, birthDay, deathDay, adult, known_for_department) | ||||||
|       id, |  | ||||||
|       name, |  | ||||||
|       profile_path, |  | ||||||
|       birthDay, |  | ||||||
|       deathDay, |  | ||||||
|       adult, |  | ||||||
|       place_of_birth, |  | ||||||
|       biography, |  | ||||||
|       known_for_department |  | ||||||
|     ); |  | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   createJsonResponse() { |   createJsonResponse() { | ||||||
| @@ -58,12 +27,10 @@ class Person { | |||||||
|       poster: this.poster, |       poster: this.poster, | ||||||
|       birthday: this.birthday, |       birthday: this.birthday, | ||||||
|       deathday: this.deathday, |       deathday: this.deathday, | ||||||
|       place_of_birth: this.placeOfBirth, |  | ||||||
|       biography: this.biography, |  | ||||||
|       known_for_department: this.knownForDepartment, |       known_for_department: this.knownForDepartment, | ||||||
|       adult: this.adult, |       adult: this.adult, | ||||||
|       type: this.type |       type: this.type | ||||||
|     }; |     } | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,25 +1,26 @@ | |||||||
| const User = require("src/user/user"); | const User = require('src/user/user'); | ||||||
| const jwt = require("jsonwebtoken"); | const jwt = require('jsonwebtoken'); | ||||||
|  |  | ||||||
| class Token { | class Token { | ||||||
|   constructor(user, admin = false, settings = null) { |   constructor(user, admin=false) { | ||||||
|     this.user = user; |     this.user = user; | ||||||
|     this.admin = admin; |     this.admin = admin; | ||||||
|     this.settings = settings; |  | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   /** |   /** | ||||||
|    * Generate a new token. |     * Generate a new token. | ||||||
|    * @param {String} secret a cipher of the token |     * @param {String} secret a cipher of the token | ||||||
|    * @returns {String} |     * @returns {String} | ||||||
|    */ |     */ | ||||||
|   toString(secret) { |   toString(secret) { | ||||||
|     const { user, admin, settings } = this; |     const username = this.user.username; | ||||||
|  |     const admin = this.admin; | ||||||
|  |     let data = { username } | ||||||
|  |  | ||||||
|     let data = { username: user.username, settings }; |     if (admin) | ||||||
|     if (admin) data["admin"] = admin; |       data = { ...data, admin } | ||||||
|  |  | ||||||
|     return jwt.sign(data, secret, { expiresIn: "90d" }); |     return jwt.sign(data, secret, { expiresIn: '90d' }); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   /** |   /** | ||||||
| @@ -29,12 +30,15 @@ class Token { | |||||||
|    * @returns {Token} |    * @returns {Token} | ||||||
|    */ |    */ | ||||||
|   static fromString(jwtToken, secret) { |   static fromString(jwtToken, secret) { | ||||||
|     const token = jwt.verify(jwtToken, secret, { clockTolerance: 10000 }); |     let username = null; | ||||||
|     if (token.username == null) throw new Error("Malformed token"); |  | ||||||
|  |  | ||||||
|     const { username, admin, settings } = token; |     const token = jwt.verify(jwtToken, secret, { clockTolerance: 10000 }) | ||||||
|     const user = new User(username); |     if (token.username === undefined || token.username === null) | ||||||
|     return new Token(user, admin, settings); |       throw new Error('Malformed token') | ||||||
|  |  | ||||||
|  |     username = token.username | ||||||
|  |     const user = new User(username) | ||||||
|  |     return new Token(user) | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,256 +1,65 @@ | |||||||
| const assert = require("assert"); | const assert = require('assert'); | ||||||
| const establishedDatabase = require("src/database/database"); | const establishedDatabase = require('src/database/database'); | ||||||
|  |  | ||||||
| class UserRepository { | class UserRepository { | ||||||
|   constructor(database) { |    constructor(database) { | ||||||
|     this.database = database || establishedDatabase; |       this.database = database || establishedDatabase; | ||||||
|     this.queries = { |       this.queries = { | ||||||
|       read: "select * from user where lower(user_name) = lower(?)", |          read: 'select * from user where lower(user_name) = lower(?)', | ||||||
|       create: "insert into user (user_name) values (?)", |          create: 'insert into user (user_name) values (?)', | ||||||
|       change: "update user set password = ? where user_name = ?", |          change: 'update user set password = ? where user_name = ?', | ||||||
|       retrieveHash: "select * from user where user_name = ?", |          retrieveHash: 'select * from user where user_name = ?', | ||||||
|       getAdminStateByUser: "select admin from user where user_name = ?", |          getAdminStateByUser: 'select admin from user where user_name = ?' | ||||||
|       link: "update settings set plex_userid = ? where user_name = ?", |       }; | ||||||
|       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. |    * Create a user in a database. | ||||||
|    * @param {User} user the user you want to create |    * @param {User} user the user you want to create | ||||||
|    * @returns {Promise} |    * @returns {Promise} | ||||||
|    */ |    */ | ||||||
|   create(user) { |    create(user) { | ||||||
|     return this.database |       return Promise.resolve() | ||||||
|       .get(this.queries.read, user.username) |          .then(() => this.database.get(this.queries.read, user.username)) | ||||||
|       .then(() => this.database.run(this.queries.create, user.username)) |          .then(() => this.database.run(this.queries.create, user.username)) | ||||||
|       .catch(error => { |          .catch((error) => { | ||||||
|         if ( |             if (error.name === 'AssertionError' || error.message.endsWith('user_name')) { | ||||||
|           error.name === "AssertionError" || |                throw new Error('That username is already registered'); | ||||||
|           error.message.endsWith("user_name") |             } | ||||||
|         ) { |             throw Error(error) | ||||||
|           throw new Error("That username is already registered"); |          }); | ||||||
|         } |    } | ||||||
|         throw Error(error); |  | ||||||
|       }); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   /** |    /** | ||||||
|    * Retrieve a password from a database. |    * Retrieve a password from a database. | ||||||
|    * @param {User} user the user you want to retrieve the password |    * @param {User} user the user you want to retrieve the password | ||||||
|    * @returns {Promise} |    * @returns {Promise} | ||||||
|    */ |    */ | ||||||
|   retrieveHash(user) { |    retrieveHash(user) { | ||||||
|     return this.database |       return Promise.resolve() | ||||||
|       .get(this.queries.retrieveHash, user.username) |          .then(() => this.database.get(this.queries.retrieveHash, user.username)) | ||||||
|       .then(row => { |          .then((row) => { | ||||||
|         assert(row, "The user does not exist."); |             assert(row, 'The user does not exist.'); | ||||||
|         return row.password; |             return row.password; | ||||||
|       }) |          }) | ||||||
|       .catch(err => { |          .catch((err) => { console.log(error); throw new Error('Unable to find your user.'); }); | ||||||
|         console.log(error); |    } | ||||||
|         throw new Error("Unable to find your user."); |  | ||||||
|       }); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   /** |    /** | ||||||
|    * Change a user's password in a database. |    * Change a user's password in a database. | ||||||
|    * @param {User} user the user you want to create |    * @param {User} user the user you want to create | ||||||
|    * @param {String} password the new password you want to change |    * @param {String} password the new password you want to change | ||||||
|    * @returns {Promise} |    * @returns {Promise} | ||||||
|    */ |    */ | ||||||
|   changePassword(user, password) { |    changePassword(user, password) { | ||||||
|     return this.database.run(this.queries.change, [password, user.username]); |       return Promise.resolve(this.database.run(this.queries.change, [password, user.username])); | ||||||
|   } |    } | ||||||
|  |  | ||||||
|   /** |    checkAdmin(user) { | ||||||
|    * Link plex userid with seasoned user |       return this.database.get(this.queries.getAdminStateByUser, user.username).then((row) => { | ||||||
|    * @param {String} username the user you want to lunk plex userid with |          return row.admin; | ||||||
|    * @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; | module.exports = UserRepository; | ||||||
|   | |||||||
| @@ -1,10 +1,10 @@ | |||||||
| const bcrypt = require("bcrypt"); | const bcrypt = require('bcrypt'); | ||||||
| const UserRepository = require("src/user/userRepository"); | const UserRepository = require('src/user/userRepository'); | ||||||
|  |  | ||||||
| class UserSecurity { | class UserSecurity { | ||||||
|   constructor(database) { |   constructor(database) { | ||||||
|     this.userRepository = new UserRepository(database); |   this.userRepository = new UserRepository(database); | ||||||
|   } | } | ||||||
|  |  | ||||||
|   /** |   /** | ||||||
|    * Create a new user in PlanFlix. |    * Create a new user in PlanFlix. | ||||||
| @@ -13,15 +13,15 @@ class UserSecurity { | |||||||
|    * @returns {Promise} |    * @returns {Promise} | ||||||
|    */ |    */ | ||||||
|   createNewUser(user, clearPassword) { |   createNewUser(user, clearPassword) { | ||||||
|     if (user.username.trim() === "") { |     if (user.username.trim() === '') { | ||||||
|       throw new Error("The username is empty."); |       throw new Error('The username is empty.'); | ||||||
|     } else if (clearPassword.trim() === "") { |     } else if (clearPassword.trim() === '') { | ||||||
|       throw new Error("The password is empty."); |       throw new Error('The password is empty.'); | ||||||
|     } else { |     } else { | ||||||
|       return this.userRepository |       return Promise.resolve() | ||||||
|         .create(user) |         .then(() => this.userRepository.create(user)) | ||||||
|         .then(() => UserSecurity.hashPassword(clearPassword)) |         .then(() => UserSecurity.hashPassword(clearPassword)) | ||||||
|         .then(hash => this.userRepository.changePassword(user, hash)); |         .then(hash => this.userRepository.changePassword(user, hash)) | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
| @@ -32,25 +32,24 @@ class UserSecurity { | |||||||
|    * @returns {Promise} |    * @returns {Promise} | ||||||
|    */ |    */ | ||||||
|   login(user, clearPassword) { |   login(user, clearPassword) { | ||||||
|     return this.userRepository |     return Promise.resolve() | ||||||
|       .retrieveHash(user) |       .then(() => this.userRepository.retrieveHash(user)) | ||||||
|       .then(hash => UserSecurity.compareHashes(hash, clearPassword)) |       .then(hash => UserSecurity.compareHashes(hash, clearPassword)) | ||||||
|       .catch(() => { |       .catch(() => { throw new Error('Incorrect username or password.'); }); | ||||||
|         throw new Error("Incorrect username or password."); |  | ||||||
|       }); |  | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   /** |    /** | ||||||
|    * Compare between a password and a hash password from database. |     * Compare between a password and a hash password from database. | ||||||
|    * @param {String} hash the hash password from database |     * @param {String} hash the hash password from database | ||||||
|    * @param {String} clearPassword the user's password |     * @param {String} clearPassword the user's password | ||||||
|    * @returns {Promise} |     * @returns {Promise} | ||||||
|    */ |     */ | ||||||
|   static compareHashes(hash, clearPassword) { |   static compareHashes(hash, clearPassword) { | ||||||
|     return new Promise((resolve, reject) => { |     return new Promise((resolve, reject) => { | ||||||
|       bcrypt.compare(clearPassword, hash, (error, match) => { |       bcrypt.compare(clearPassword, hash, (error, match) => { | ||||||
|         if (match) resolve(true); |         if (match) | ||||||
|         reject(false); |           resolve() | ||||||
|  |         reject() | ||||||
|       }); |       }); | ||||||
|     }); |     }); | ||||||
|   } |   } | ||||||
| @@ -61,11 +60,9 @@ class UserSecurity { | |||||||
|    * @returns {Promise} |    * @returns {Promise} | ||||||
|    */ |    */ | ||||||
|   static hashPassword(clearPassword) { |   static hashPassword(clearPassword) { | ||||||
|     return new Promise(resolve => { |     return new Promise((resolve) => { | ||||||
|       const saltRounds = 10; |       const saltRounds = 10; | ||||||
|       bcrypt.hash(clearPassword, saltRounds, (error, hash) => { |       bcrypt.hash(clearPassword, saltRounds, (error, hash) => { | ||||||
|         if (error) reject(error); |  | ||||||
|  |  | ||||||
|         resolve(hash); |         resolve(hash); | ||||||
|       }); |       }); | ||||||
|     }); |     }); | ||||||
|   | |||||||
| @@ -1,239 +1,160 @@ | |||||||
| const express = require("express"); | const express = require('express'); | ||||||
| const Raven = require("raven"); | // const reload = require('express-reload'); | ||||||
| const cookieParser = require("cookie-parser"); | const Raven = require('raven'); | ||||||
| const bodyParser = require("body-parser"); | 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 configuration = require("src/config/configuration").getInstance(); | const listController = require('./controllers/list/listController'); | ||||||
|  |  | ||||||
| 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. | // TODO: Have our raven router check if there is a value, if not don't enable raven. | ||||||
| Raven.config(configuration.get("raven", "DSN")).install(); | Raven.config(configuration.get('raven', 'DSN')).install(); | ||||||
|  |  | ||||||
| const app = express(); // define our app using express | const app = express(); // define our app using express | ||||||
| app.use(Raven.requestHandler()); | app.use(Raven.requestHandler()); | ||||||
| app.use(bodyParser.json()); | app.use(bodyParser.json()); | ||||||
| app.use(cookieParser()); |  | ||||||
|  |  | ||||||
| const router = express.Router(); | const router = express.Router(); | ||||||
| const allowedOrigins = configuration.get("webserver", "origins"); | const allowedOrigins = ['https://kevinmidboe.com', 'http://localhost:8080']; | ||||||
|  |  | ||||||
| // TODO: All JSON handling in a single router | // TODO: All JSON handling in a single router | ||||||
| // router.use(bodyParser.json()); | // router.use(bodyParser.json()); | ||||||
| app.use(bodyParser.urlencoded({ extended: true })); | app.use(bodyParser.urlencoded({ extended: true })); | ||||||
|  |  | ||||||
| /* Check header and cookie for authentication and set req.loggedInUser */ | /* Decode the Authorization header if provided */ | ||||||
| router.use(reqTokenToUser); | app.use(tokenToUser); | ||||||
|  |  | ||||||
| // TODO: Should have a separate middleware/router for handling headers. | // TODO: Should have a separate middleware/router for handling headers. | ||||||
| router.use((req, res, next) => { | router.use((req, res, next) => { | ||||||
|   // TODO add logging of all incoming |    // TODO add logging of all incoming | ||||||
|   // const origin = req.headers.origin; |   const origin = req.headers.origin; | ||||||
|   // if (allowedOrigins.indexOf(origin) > -1) { |    if (allowedOrigins.indexOf(origin) > -1) { | ||||||
|   //   res.setHeader("Access-Control-Allow-Origin", origin); |        res.setHeader('Access-Control-Allow-Origin', origin); | ||||||
|   // } |    } | ||||||
|  |    res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization, loggedinuser'); | ||||||
|  |    res.header('Access-Control-Allow-Methods', 'POST, GET, PUT'); | ||||||
|  |  | ||||||
|   res.header( |    next(); | ||||||
|     "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("/", (req, res) => { | router.get('/', function mainHandler(req, res) { | ||||||
|   res.send("welcome to seasoned api"); |    throw new Error('Broke!'); | ||||||
| }); | }); | ||||||
|  |  | ||||||
| app.use(Raven.errorHandler()); | app.use(Raven.errorHandler()); | ||||||
| app.use((err, req, res, next) => { | app.use(function onError(err, req, res, next) { | ||||||
|   res.statusCode = 500; |    res.statusCode = 500; | ||||||
|   res.end(res.sentry + "\n"); |    res.end(res.sentry + '\n'); | ||||||
| }); | }); | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * GraphQL | ||||||
|  |  */ | ||||||
|  | var graphqlHTTP = require('express-graphql'); | ||||||
|  | var { buildSchema } = require('graphql'); | ||||||
|  |  | ||||||
|  | // var schema = buildSchema(` | ||||||
|  | //   type Query { | ||||||
|  | //     hello: String | ||||||
|  | //   }`); | ||||||
|  | const schema = require('./graphql/requests.js'); | ||||||
|  |  | ||||||
|  | const roots = { hello: () => 'Hello world!' }; | ||||||
|  |  | ||||||
|  | app.use('/graphql', graphqlHTTP({ | ||||||
|  |   schema: schema.schema, | ||||||
|  |   graphiql: true | ||||||
|  | })) | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * User |  * User | ||||||
|  */ |  */ | ||||||
| router.post("/v1/user", require("./controllers/user/register.js")); | router.post('/v1/user', require('./controllers/user/register.js')); | ||||||
| router.post("/v1/user/login", require("./controllers/user/login.js")); | router.post('/v1/user/login', require('./controllers/user/login.js')); | ||||||
| router.post("/v1/user/logout", require("./controllers/user/logout.js")); | router.get('/v1/user/history', mustBeAuthenticated, require('./controllers/user/history.js')); | ||||||
| router.get( | router.get('/v1/user/requests', mustBeAuthenticated, require('./controllers/user/requests.js')); | ||||||
|   "/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 |  * Seasoned | ||||||
|  */ |  */ | ||||||
| router.get("/v1/seasoned/all", require("./controllers/seasoned/readStrays.js")); | router.get('/v1/seasoned/all', require('./controllers/seasoned/readStrays.js')); | ||||||
| router.get( | router.get('/v1/seasoned/:strayId', require('./controllers/seasoned/strayById.js')); | ||||||
|   "/v1/seasoned/:strayId", | router.post('/v1/seasoned/verify/:strayId', require('./controllers/seasoned/verifyStray.js')); | ||||||
|   require("./controllers/seasoned/strayById.js") |  | ||||||
| ); |  | ||||||
| router.post( |  | ||||||
|   "/v1/seasoned/verify/:strayId", |  | ||||||
|   require("./controllers/seasoned/verifyStray.js") |  | ||||||
| ); |  | ||||||
|  |  | ||||||
| router.get("/v2/search/", require("./controllers/search/multiSearch.js")); | router.get('/v2/search/', require('./controllers/search/multiSearch.js')); | ||||||
| router.get("/v2/search/movie", require("./controllers/search/movieSearch.js")); | router.get('/v2/search/movie', require('./controllers/search/movieSearch.js')); | ||||||
| router.get("/v2/search/show", require("./controllers/search/showSearch.js")); | router.get('/v2/search/show', require('./controllers/search/showSearch.js')); | ||||||
| router.get( | router.get('/v2/search/person', require('./controllers/search/personSearch.js')); | ||||||
|   "/v2/search/person", |  | ||||||
|   require("./controllers/search/personSearch.js") |  | ||||||
| ); |  | ||||||
|  |  | ||||||
| router.get("/v2/movie/now_playing", listController.nowPlayingMovies); | router.get('/v2/movie/now_playing', listController.nowPlayingMovies); | ||||||
| router.get("/v2/movie/popular", listController.popularMovies); | router.get('/v2/movie/popular', listController.popularMovies); | ||||||
| router.get("/v2/movie/top_rated", listController.topRatedMovies); | router.get('/v2/movie/top_rated', listController.topRatedMovies); | ||||||
| router.get("/v2/movie/upcoming", listController.upcomingMovies); | router.get('/v2/movie/upcoming', listController.upcomingMovies); | ||||||
| router.get("/v2/movie/:id/credits", require("./controllers/movie/credits.js")); |  | ||||||
| router.get( |  | ||||||
|   "/v2/movie/:id/release_dates", |  | ||||||
|   require("./controllers/movie/releaseDates.js") |  | ||||||
| ); |  | ||||||
| router.get("/v2/movie/:id", require("./controllers/movie/info.js")); |  | ||||||
|  |  | ||||||
| router.get("/v2/show/now_playing", listController.nowPlayingShows); | router.get('/v2/show/now_playing', listController.nowPlayingShows); | ||||||
| router.get("/v2/show/popular", listController.popularShows); | router.get('/v2/show/popular', listController.popularShows); | ||||||
| router.get("/v2/show/top_rated", listController.topRatedShows); | router.get('/v2/show/top_rated', listController.topRatedShows); | ||||||
| router.get("/v2/show/:id/credits", require("./controllers/show/credits.js")); |  | ||||||
| router.get("/v2/show/:id", require("./controllers/show/info.js")); |  | ||||||
|  |  | ||||||
| router.get( | router.get('/v2/movie/:id/credits', require('./controllers/movie/credits.js')); | ||||||
|   "/v2/person/:id/credits", | router.get('/v2/movie/:id/release_dates', require('./controllers/movie/releaseDates.js')); | ||||||
|   require("./controllers/person/credits.js") | router.get('/v2/show/:id/credits', require('./controllers/show/credits.js')); | ||||||
| ); |  | ||||||
| router.get("/v2/person/:id", require("./controllers/person/info.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')); | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * Plex |  * Plex | ||||||
|  */ |  */ | ||||||
| router.get("/v2/plex/search", require("./controllers/plex/search")); | router.get('/v2/plex/search', require('./controllers/plex/search')); | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * List |  * List | ||||||
|  */ |  */ | ||||||
| router.get("/v1/plex/search", require("./controllers/plex/searchMedia.js")); | router.get('/v1/plex/search', require('./controllers/plex/searchMedia.js')); | ||||||
| router.get("/v1/plex/playing", require("./controllers/plex/plexPlaying.js")); | router.get('/v1/plex/playing', require('./controllers/plex/plexPlaying.js')); | ||||||
| router.get("/v1/plex/request", require("./controllers/plex/searchRequest.js")); | router.get('/v1/plex/request', require('./controllers/plex/searchRequest.js')); | ||||||
| router.get( | router.get('/v1/plex/request/:mediaId', require('./controllers/plex/readRequest.js')); | ||||||
|   "/v1/plex/request/:mediaId", | router.post('/v1/plex/request/:mediaId', require('./controllers/plex/submitRequest.js')); | ||||||
|   require("./controllers/plex/readRequest.js") | router.post('/v1/plex/hook', require('./controllers/plex/hookDump.js')); | ||||||
| ); |  | ||||||
| router.post( |  | ||||||
|   "/v1/plex/request/:mediaId", |  | ||||||
|   require("./controllers/plex/submitRequest.js") |  | ||||||
| ); |  | ||||||
| router.post("/v1/plex/hook", require("./controllers/plex/hookDump.js")); |  | ||||||
|  |  | ||||||
| router.get( |  | ||||||
|   "/v1/plex/watch-link", |  | ||||||
|   mustBeAuthenticated, |  | ||||||
|   require("./controllers/plex/watchDirectLink.js") |  | ||||||
| ); |  | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * Requests |  * Requests | ||||||
|  */ |  */ | ||||||
|  |  | ||||||
| router.get("/v2/request", require("./controllers/request/fetchAllRequests.js")); | router.get('/v2/request', require('./controllers/request/fetchAllRequests.js')); | ||||||
| router.get("/v2/request/:id", require("./controllers/request/getRequest.js")); | router.get('/v2/request/:id', require('./controllers/request/getRequest.js')); | ||||||
| router.post("/v2/request", require("./controllers/request/requestTmdbId.js")); | router.post('/v2/request', require('./controllers/request/requestTmdbId.js')); | ||||||
| router.get( | router.get('/v1/plex/requests/all', require('./controllers/plex/fetchRequested.js')); | ||||||
|   "/v1/plex/requests/all", | router.put('/v1/plex/request/:requestId', mustBeAuthenticated, require('./controllers/plex/updateRequested.js')); | ||||||
|   require("./controllers/plex/fetchRequested.js") |  | ||||||
| ); |  | ||||||
| router.put( |  | ||||||
|   "/v1/plex/request/:requestId", |  | ||||||
|   mustBeAuthenticated, |  | ||||||
|   require("./controllers/plex/updateRequested.js") |  | ||||||
| ); |  | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * Pirate |  * Pirate | ||||||
|  */ |  */ | ||||||
| router.get( | router.get('/v1/pirate/search', mustBeAuthenticated, require('./controllers/pirate/searchTheBay.js')); | ||||||
|   "/v1/pirate/search", | router.post('/v1/pirate/add', mustBeAuthenticated, require('./controllers/pirate/addMagnet.js')); | ||||||
|   mustBeAuthenticated, |  | ||||||
|   require("./controllers/pirate/searchTheBay.js") |  | ||||||
| ); |  | ||||||
| router.post( |  | ||||||
|   "/v1/pirate/add", |  | ||||||
|   mustBeAuthenticated, |  | ||||||
|   require("./controllers/pirate/addMagnet.js") |  | ||||||
| ); |  | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * git |  * git | ||||||
|  */ |  */ | ||||||
| router.post("/v1/git/dump", require("./controllers/git/dumpHook.js")); | router.post('/v1/git/dump', require('./controllers/git/dumpHook.js')); | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * misc |  * misc | ||||||
|  */ |  */ | ||||||
| router.get("/v1/emoji", require("./controllers/misc/emoji.js")); |  router.get('/v1/emoji', require('./controllers/misc/emoji.js')); | ||||||
|  |  | ||||||
| // REGISTER OUR ROUTES ------------------------------- | // REGISTER OUR ROUTES ------------------------------- | ||||||
| // all of our routes will be prefixed with /api | // all of our routes will be prefixed with /api | ||||||
| app.use("/api", router); | app.use('/api', router); | ||||||
|  |  | ||||||
| module.exports = app; | module.exports = app; | ||||||
|   | |||||||
| @@ -1,6 +1,8 @@ | |||||||
| const configuration = require('src/config/configuration').getInstance(); | const configuration = require('src/config/configuration').getInstance(); | ||||||
|  | const Cache = require('src/tmdb/cache'); | ||||||
| const TMDB = require('src/tmdb/tmdb'); | const TMDB = require('src/tmdb/tmdb'); | ||||||
| const tmdb = new TMDB(configuration.get('tmdb', 'apiKey')); | const cache = new Cache(); | ||||||
|  | const tmdb = new TMDB(cache, configuration.get('tmdb', 'apiKey')); | ||||||
|  |  | ||||||
| // there should be a translate function from query params to  | // there should be a translate function from query params to  | ||||||
| // tmdb list that is valid. Should it be a helper function or does it  | // tmdb list that is valid. Should it be a helper function or does it  | ||||||
|   | |||||||
| @@ -1,7 +1,9 @@ | |||||||
| const configuration = require('src/config/configuration').getInstance(); | const configuration = require('src/config/configuration').getInstance(); | ||||||
|  | const Cache = require('src/tmdb/cache'); | ||||||
| const TMDB = require('src/tmdb/tmdb'); | const TMDB = require('src/tmdb/tmdb'); | ||||||
|  |  | ||||||
| const tmdb = new TMDB(configuration.get('tmdb', 'apiKey')); | const cache = new Cache(); | ||||||
|  | const tmdb = new TMDB(cache, configuration.get('tmdb', 'apiKey')); | ||||||
|  |  | ||||||
| const movieCreditsController = (req, res) => { | const movieCreditsController = (req, res) => { | ||||||
|   const movieId = req.params.id; |   const movieId = req.params.id; | ||||||
| @@ -21,4 +23,4 @@ const movieCreditsController = (req, res) => { | |||||||
|     }) |     }) | ||||||
| } | } | ||||||
|  |  | ||||||
| module.exports = movieCreditsController; | module.exports = movieCreditsController; | ||||||
| @@ -1,19 +1,19 @@ | |||||||
| const configuration = require("src/config/configuration").getInstance(); | const configuration = require('src/config/configuration').getInstance(); | ||||||
| const TMDB = require("src/tmdb/tmdb"); | const Cache = require('src/tmdb/cache'); | ||||||
| const Plex = require("src/plex/plex"); | const TMDB = require('src/tmdb/tmdb'); | ||||||
| const tmdb = new TMDB(configuration.get("tmdb", "apiKey")); | const Plex = require('src/plex/plex'); | ||||||
| const plex = new Plex(configuration.get("plex", "ip")); | const cache = new Cache(); | ||||||
|  | const tmdb = new TMDB(cache, configuration.get('tmdb', 'apiKey')); | ||||||
|  | const plex = new Plex(configuration.get('plex', 'ip')); | ||||||
|  |  | ||||||
| function handleError(error, res) { | function handleError(error, res) { | ||||||
|   const { status, message } = error; |   const { status, message } = error; | ||||||
|  |  | ||||||
|   if (status && message) { |   if (status && message) { | ||||||
|     res.status(status).send({ success: false, message }); |     res.status(status).send({ success: false, message }) | ||||||
|   } else { |   } else { | ||||||
|     console.log("caught movieinfo controller error", error); |     console.log('caught movieinfo controller error', error) | ||||||
|     res.status(500).send({ |     res.status(500).send({ message: 'An unexpected error occured while requesting movie info'}) | ||||||
|       message: "An unexpected error occured while requesting movie info" |  | ||||||
|     }); |  | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -27,44 +27,31 @@ async function movieInfoController(req, res) { | |||||||
|   const movieId = req.params.id; |   const movieId = req.params.id; | ||||||
|   let { credits, release_dates, check_existance } = req.query; |   let { credits, release_dates, check_existance } = req.query; | ||||||
|  |  | ||||||
|   credits && credits.toLowerCase() === "true" |   credits && credits.toLowerCase() === 'true' ? credits = true : credits = false | ||||||
|     ? (credits = true) |   release_dates && release_dates.toLowerCase() === 'true' ? release_dates = true : release_dates = false | ||||||
|     : (credits = false); |   check_existance && check_existance.toLowerCase() === 'true' ? check_existance = true : check_existance = false | ||||||
|   release_dates && release_dates.toLowerCase() === "true" |  | ||||||
|     ? (release_dates = true) |  | ||||||
|     : (release_dates = false); |  | ||||||
|   check_existance && check_existance.toLowerCase() === "true" |  | ||||||
|     ? (check_existance = true) |  | ||||||
|     : (check_existance = false); |  | ||||||
|  |  | ||||||
|   let tmdbQueue = [tmdb.movieInfo(movieId)]; |   let tmdbQueue = [tmdb.movieInfo(movieId)] | ||||||
|   if (credits) tmdbQueue.push(tmdb.movieCredits(movieId)); |   if (credits) | ||||||
|   if (release_dates) tmdbQueue.push(tmdb.movieReleaseDates(movieId)); |     tmdbQueue.push(tmdb.movieCredits(movieId)) | ||||||
|  |   if (release_dates) | ||||||
|  |     tmdbQueue.push(tmdb.movieReleaseDates(movieId)) | ||||||
|  |  | ||||||
|   try { |   try { | ||||||
|     const [Movie, Credits, ReleaseDates] = await Promise.all(tmdbQueue); |     const [ Movie, Credits, ReleaseDates ] = await Promise.all(tmdbQueue) | ||||||
|  |  | ||||||
|     const movie = Movie.createJsonResponse(); |     const movie = Movie.createJsonResponse() | ||||||
|     if (Credits) movie.credits = Credits.createJsonResponse(); |     if (Credits) | ||||||
|  |       movie.credits = Credits.createJsonResponse() | ||||||
|     if (ReleaseDates) |     if (ReleaseDates) | ||||||
|       movie.release_dates = ReleaseDates.createJsonResponse().results; |       movie.release_dates = ReleaseDates.createJsonResponse().results | ||||||
|  |  | ||||||
|     if (check_existance) { |     if (check_existance) | ||||||
|       try { |       movie.exists_in_plex = await plex.existsInPlex(movie) | ||||||
|         movie.exists_in_plex = await plex.existsInPlex(movie); |    | ||||||
|       } catch (error) { |     res.send(movie) | ||||||
|         if (error.status === 401) { |   } catch(error) { | ||||||
|           console.log("Unathorized request, check plex server LAN settings"); |     handleError(error, res) | ||||||
|         } else { |  | ||||||
|           console.log("Unkown error from plex!"); |  | ||||||
|         } |  | ||||||
|         console.log(error); |  | ||||||
|       } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     res.send(movie); |  | ||||||
|   } catch (error) { |  | ||||||
|     handleError(error, res); |  | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,7 +1,9 @@ | |||||||
| const configuration = require('src/config/configuration').getInstance(); | const configuration = require('src/config/configuration').getInstance(); | ||||||
|  | const Cache = require('src/tmdb/cache'); | ||||||
| const TMDB = require('src/tmdb/tmdb'); | const TMDB = require('src/tmdb/tmdb'); | ||||||
|  |  | ||||||
| const tmdb = new TMDB(configuration.get('tmdb', 'apiKey')); | const cache = new Cache(); | ||||||
|  | const tmdb = new TMDB(cache, configuration.get('tmdb', 'apiKey')); | ||||||
|  |  | ||||||
| const movieReleaseDatesController = (req, res) => { | const movieReleaseDatesController = (req, res) => { | ||||||
|   const movieId = req.params.id; |   const movieId = req.params.id; | ||||||
| @@ -21,4 +23,4 @@ const movieReleaseDatesController = (req, res) => { | |||||||
|     }) |     }) | ||||||
| } | } | ||||||
|  |  | ||||||
| module.exports = movieReleaseDatesController; | module.exports = movieReleaseDatesController; | ||||||
| @@ -1,26 +0,0 @@ | |||||||
| 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,49 +1,25 @@ | |||||||
| const configuration = require("src/config/configuration").getInstance(); | const configuration = require('src/config/configuration').getInstance(); | ||||||
| const TMDB = require("src/tmdb/tmdb"); | const Cache = require('src/tmdb/cache'); | ||||||
| const tmdb = new TMDB(configuration.get("tmdb", "apiKey")); | const TMDB = require('src/tmdb/tmdb'); | ||||||
|  | const cache = new Cache(); | ||||||
| function handleError(error, res) { | const tmdb = new TMDB(cache, configuration.get('tmdb', 'apiKey')); | ||||||
|   const { status, message } = error; |  | ||||||
|  |  | ||||||
|   if (status && message) { |  | ||||||
|     res.status(status).send({ success: false, message }); |  | ||||||
|   } else { |  | ||||||
|     console.log("caught personinfo controller error", error); |  | ||||||
|     res.status(500).send({ |  | ||||||
|       message: "An unexpected error occured while requesting person info." |  | ||||||
|     }); |  | ||||||
|   } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * Controller: Retrieve information for a person |  * Controller: Retrieve information for a person  | ||||||
|  * @param {Request} req http request variable |  * @param {Request} req http request variable | ||||||
|  * @param {Response} res |  * @param {Response} res | ||||||
|  * @returns {Callback} |  * @returns {Callback} | ||||||
|  */ |  */ | ||||||
|  |  | ||||||
| async function personInfoController(req, res) { | function personInfoController(req, res) { | ||||||
|   const personId = req.params.id; |   const personId = req.params.id; | ||||||
|   let { credits } = req.query; |  | ||||||
|   arguments; |  | ||||||
|  |  | ||||||
|   credits && credits.toLowerCase() === "true" |  | ||||||
|     ? (credits = true) |  | ||||||
|     : (credits = false); |  | ||||||
|  |  | ||||||
|   let tmdbQueue = [tmdb.personInfo(personId)]; |   tmdb.personInfo(personId) | ||||||
|   if (credits) tmdbQueue.push(tmdb.personCredits(personId)); |   .then(person => res.send(person.createJsonResponse())) | ||||||
|  |   .catch(error => { | ||||||
|   try { |     res.status(404).send({ success: false, message: error.message }); | ||||||
|     const [Person, Credits] = await Promise.all(tmdbQueue); |   }); | ||||||
|  |  | ||||||
|     const person = Person.createJsonResponse(); |  | ||||||
|     if (credits) person.credits = Credits.createJsonResponse(); |  | ||||||
|  |  | ||||||
|     return res.send(person); |  | ||||||
|   } catch (error) { |  | ||||||
|     handleError(error, res); |  | ||||||
|   } |  | ||||||
| } | } | ||||||
|  |  | ||||||
| module.exports = personInfoController; | module.exports = personInfoController; | ||||||
|   | |||||||
| @@ -1,11 +1,11 @@ | |||||||
| /* | /* | ||||||
|  * @Author: KevinMidboe | * @Author: KevinMidboe | ||||||
|  * @Date:   2017-10-21 09:54:31 | * @Date:   2017-10-21 09:54:31 | ||||||
|  * @Last Modified by:   KevinMidboe | * @Last Modified by:   KevinMidboe | ||||||
|  * @Last Modified time: 2018-02-26 19:56:32 | * @Last Modified time: 2018-02-26 19:56:32 | ||||||
|  */ | */ | ||||||
|  |  | ||||||
| const PirateRepository = require("src/pirate/pirateRepository"); | const PirateRepository = require('src/pirate/pirateRepository'); | ||||||
| // const pirateRepository = new PirateRepository(); | // const pirateRepository = new PirateRepository(); | ||||||
|  |  | ||||||
| /** | /** | ||||||
| @@ -15,15 +15,15 @@ const PirateRepository = require("src/pirate/pirateRepository"); | |||||||
|  * @returns {Callback} |  * @returns {Callback} | ||||||
|  */ |  */ | ||||||
| function updateRequested(req, res) { | function updateRequested(req, res) { | ||||||
|   const { query, page, type } = req.query; |    const { query, page, type } = req.query; | ||||||
|  |  | ||||||
|   PirateRepository.SearchPiratebay(query, page, type) |    PirateRepository.SearchPiratebay(query, page, type) | ||||||
|     .then(result => { |       .then((result) => { | ||||||
|       res.send({ success: true, results: result }); |          res.send({ success: true, results: result }); | ||||||
|     }) |       }) | ||||||
|     .catch(error => { |       .catch(error => { | ||||||
|       res.status(401).send({ success: false, message: error.message }); |          res.status(401).send({ success: false, message: error.message }); | ||||||
|     }); |       }); | ||||||
| } | } | ||||||
|  |  | ||||||
| module.exports = updateRequested; | module.exports = updateRequested; | ||||||
|   | |||||||
| @@ -1,24 +1,26 @@ | |||||||
| const SearchHistory = require("src/searchHistory/searchHistory"); | const SearchHistory = require('src/searchHistory/searchHistory'); | ||||||
| const Cache = require("src/tmdb/cache"); | const Cache = require('src/tmdb/cache'); | ||||||
| const RequestRepository = require("src/plex/requestRepository.js"); | const RequestRepository = require('src/plex/requestRepository.js'); | ||||||
|  |  | ||||||
| const cache = new Cache(); | const cache = new Cache(); | ||||||
| const requestRepository = new RequestRepository(cache); | const requestRepository = new RequestRepository(cache); | ||||||
| const searchHistory = new SearchHistory(); | const searchHistory = new SearchHistory(); | ||||||
|  |  | ||||||
| function searchRequestController(req, res) { |  | ||||||
|   const { query, page, type } = req.query; |  | ||||||
|   const username = req.loggedInUser ? req.loggedInUser.username : null; |  | ||||||
|  |  | ||||||
|   Promise.resolve() | function searchRequestController(req, res) { | ||||||
|     .then(() => searchHistory.create(username, query)) |    const user = req.loggedInUser; | ||||||
|     .then(() => requestRepository.search(query, page, type)) |    const { query, page, type } = req.query; | ||||||
|     .then(searchResult => { |    const username = user === undefined ? undefined : user.username; | ||||||
|       res.send(searchResult); |  | ||||||
|     }) |    Promise.resolve() | ||||||
|     .catch(error => { |       .then(() => searchHistory.create(username, query)) | ||||||
|       res.status(500).send({ success: false, message: error.message }); |       .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; | module.exports = searchRequestController; | ||||||
|   | |||||||
| @@ -1,16 +1,19 @@ | |||||||
| const configuration = require("src/config/configuration").getInstance(); | const configuration = require('src/config/configuration').getInstance() | ||||||
| const RequestRepository = require("src/request/request"); | const RequestRepository = require('src/request/request'); | ||||||
| const TMDB = require("src/tmdb/tmdb"); | const Cache = require('src/tmdb/cache') | ||||||
| const tmdb = new TMDB(configuration.get("tmdb", "apiKey")); | const TMDB = require('src/tmdb/tmdb') | ||||||
| const request = new RequestRepository(); |  | ||||||
|  |  | ||||||
| const tmdbMovieInfo = id => { | const cache = new Cache() | ||||||
|   return tmdb.movieInfo(id); | const tmdb = new TMDB(cache, configuration.get('tmdb', 'apiKey')) | ||||||
| }; | const request = new RequestRepository() | ||||||
|  |  | ||||||
| const tmdbShowInfo = id => { | const tmdbMovieInfo = (id) => { | ||||||
|   return tmdb.showInfo(id); |   return tmdb.movieInfo(id) | ||||||
| }; | } | ||||||
|  |  | ||||||
|  | const tmdbShowInfo = (id) => { | ||||||
|  |   return tmdb.showInfo(id) | ||||||
|  | } | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * Controller: POST a media id to be donwloaded |  * Controller: POST a media id to be donwloaded | ||||||
| @@ -21,43 +24,28 @@ const tmdbShowInfo = id => { | |||||||
| function submitRequestController(req, res) { | function submitRequestController(req, res) { | ||||||
|   // This is the id that is the param of the url |   // This is the id that is the param of the url | ||||||
|   const id = req.params.mediaId; |   const id = req.params.mediaId; | ||||||
|   const type = req.query.type ? req.query.type.toLowerCase() : undefined; |   const type = req.query.type ? req.query.type.toLowerCase() : undefined | ||||||
|   const ip = req.headers["x-forwarded-for"] || req.connection.remoteAddress; |   const ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress; | ||||||
|   const user_agent = req.headers["user-agent"]; |   const user_agent = req.headers['user-agent']; | ||||||
|   const username = req.loggedInUser ? req.loggedInUser.username : null; |   const user = req.loggedInUser; | ||||||
|  |   let mediaFunction = undefined | ||||||
|  |  | ||||||
|   let mediaFunction = undefined; |   if (type === 'movie') { | ||||||
|  |     console.log('movie') | ||||||
|   if (type === "movie") { |     mediaFunction = tmdbMovieInfo | ||||||
|     console.log("movie"); |   } else if (type === 'show') { | ||||||
|     mediaFunction = tmdbMovieInfo; |     console.log('show') | ||||||
|   } else if (type === "show") { |     mediaFunction = tmdbShowInfo | ||||||
|     console.log("show"); |  | ||||||
|     mediaFunction = tmdbShowInfo; |  | ||||||
|   } else { |   } else { | ||||||
|     res |     res.status(422).send({ success: false, message: 'Incorrect type. Allowed types: "movie" or "show"'}) | ||||||
|       .status(422) |  | ||||||
|       .send({ |  | ||||||
|         success: false, |  | ||||||
|         message: 'Incorrect type. Allowed types: "movie" or "show"' |  | ||||||
|       }); |  | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   if (mediaFunction === undefined) { |   if (mediaFunction === undefined) { res.status(200); return } | ||||||
|     res.status(200); |  | ||||||
|     return; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   mediaFunction(id) |   mediaFunction(id) | ||||||
|     .then(tmdbMedia => |     .then(tmdbMedia => request.requestFromTmdb(tmdbMedia, ip, user_agent, user)) | ||||||
|       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 })) | ||||||
|     .then(() => |  | ||||||
|       res.send({ success: true, message: "Media item successfully requested" }) |  | ||||||
|     ) |  | ||||||
|     .catch(err => |  | ||||||
|       res.status(500).send({ success: false, message: err.message }) |  | ||||||
|     ); |  | ||||||
| } | } | ||||||
|  |  | ||||||
| module.exports = submitRequestController; | module.exports = submitRequestController; | ||||||
|   | |||||||
| @@ -1,27 +0,0 @@ | |||||||
| 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,17 +1,18 @@ | |||||||
| const configuration = require("src/config/configuration").getInstance(); | const configuration = require('src/config/configuration').getInstance(); | ||||||
| const TMDB = require("src/tmdb/tmdb"); | const Cache = require('src/tmdb/cache'); | ||||||
| const RequestRepository = require("src/request/request"); | const TMDB = require('src/tmdb/tmdb'); | ||||||
| const tmdb = new TMDB(configuration.get("tmdb", "apiKey")); | const RequestRepository = require('src/request/request'); | ||||||
|  | const cache = new Cache(); | ||||||
|  | const tmdb = new TMDB(cache, configuration.get('tmdb', 'apiKey')); | ||||||
| const request = new RequestRepository(); | const request = new RequestRepository(); | ||||||
| // const { sendSMS } = require("src/notifications/sms"); |  | ||||||
|  |  | ||||||
| const tmdbMovieInfo = id => { | const tmdbMovieInfo = (id) => { | ||||||
|   return tmdb.movieInfo(id); |   return tmdb.movieInfo(id) | ||||||
| }; | } | ||||||
|  |  | ||||||
| const tmdbShowInfo = id => { | const tmdbShowInfo = (id) => { | ||||||
|   return tmdb.showInfo(id); |   return tmdb.showInfo(id) | ||||||
| }; | } | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * Controller: Request by id with type param |  * Controller: Request by id with type param | ||||||
| @@ -20,48 +21,33 @@ const tmdbShowInfo = id => { | |||||||
|  * @returns {Callback} |  * @returns {Callback} | ||||||
|  */ |  */ | ||||||
| function requestTmdbIdController(req, res) { | function requestTmdbIdController(req, res) { | ||||||
|   const { id, type } = req.body; |   const { id, type } = req.body | ||||||
|  |  | ||||||
|   const ip = req.headers["x-forwarded-for"] || req.connection.remoteAddress; |   const ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress; | ||||||
|   const user_agent = req.headers["user-agent"]; |   const user_agent = req.headers['user-agent']; | ||||||
|   const username = req.loggedInUser ? req.loggedInUser.username : null; |   const user = req.loggedInUser; | ||||||
|  |  | ||||||
|   let mediaFunction = undefined; |   let mediaFunction = undefined | ||||||
|  |  | ||||||
|   if (id === undefined || type === undefined) { |   if (id === undefined || type === undefined) { | ||||||
|     res.status(422).send({ |     res.status(422).send({ success: false, message: "'Missing parameteres: 'id' and/or 'type'"}) | ||||||
|       success: false, |  | ||||||
|       message: "'Missing parameteres: 'id' and/or 'type'" |  | ||||||
|     }); |  | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   if (type === "movie") { |   if (type === 'movie') { | ||||||
|     mediaFunction = tmdbMovieInfo; |     mediaFunction = tmdbMovieInfo | ||||||
|   } else if (type === "show") { |   } else if (type === 'show') { | ||||||
|     mediaFunction = tmdbShowInfo; |     mediaFunction = tmdbShowInfo | ||||||
|   } else { |   } else { | ||||||
|     res.status(422).send({ |     res.status(422).send({ success: false, message: 'Incorrect type. Allowed types: "movie" or "show"'}) | ||||||
|       success: false, |  | ||||||
|       message: 'Incorrect type. Allowed types: "movie" or "show"' |  | ||||||
|     }); |  | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   mediaFunction(id) |   mediaFunction(id) | ||||||
|     // .catch((error) => { console.error(error); res.status(404).send({ success: false, error: 'Id not found' }) }) |     // .catch((error) => { console.error(error); res.status(404).send({ success: false, error: 'Id not found' }) }) | ||||||
|     .then(tmdbMedia => { |     .then(tmdbMedia => request.requestFromTmdb(tmdbMedia, ip, user_agent, user)) | ||||||
|       request.requestFromTmdb(tmdbMedia, ip, user_agent, username); |     .then(() => res.send({success: true, message: 'Request has been submitted.'})) | ||||||
|  |  | ||||||
|       // TODO enable SMS |  | ||||||
|       // const url = `https://request.movie?${tmdbMedia.type}=${tmdbMedia.id}`; |  | ||||||
|       // const message = `${tmdbMedia.title} (${tmdbMedia.year}) requested!\n${url}`; |  | ||||||
|       // sendSMS(message); |  | ||||||
|     }) |  | ||||||
|     .then(() => |  | ||||||
|       res.send({ success: true, message: "Request has been submitted." }) |  | ||||||
|     ) |  | ||||||
|     .catch(error => { |     .catch(error => { | ||||||
|       res.send({ success: false, message: error.message }); |       res.send({ success: false, message: error.message }); | ||||||
|     }); |     }) | ||||||
| } | } | ||||||
|  |  | ||||||
| module.exports = requestTmdbIdController; | module.exports = requestTmdbIdController; | ||||||
|   | |||||||
| @@ -1,7 +1,9 @@ | |||||||
| const configuration = require("src/config/configuration").getInstance(); | const configuration = require('src/config/configuration').getInstance(); | ||||||
| const TMDB = require("src/tmdb/tmdb"); | const Cache = require('src/tmdb/cache'); | ||||||
| const SearchHistory = require("src/searchHistory/searchHistory"); | const TMDB = require('src/tmdb/tmdb'); | ||||||
| const tmdb = new TMDB(configuration.get("tmdb", "apiKey")); | const SearchHistory = require('src/searchHistory/searchHistory'); | ||||||
|  | const cache = new Cache(); | ||||||
|  | const tmdb = new TMDB(cache, configuration.get('tmdb', 'apiKey')); | ||||||
| const searchHistory = new SearchHistory(); | const searchHistory = new SearchHistory(); | ||||||
|  |  | ||||||
| /** | /** | ||||||
| @@ -11,30 +13,28 @@ const searchHistory = new SearchHistory(); | |||||||
|  * @returns {Callback} |  * @returns {Callback} | ||||||
|  */ |  */ | ||||||
| function movieSearchController(req, res) { | function movieSearchController(req, res) { | ||||||
|   const { query, page, adult } = req.query; |   const user = req.loggedInUser; | ||||||
|   const username = req.loggedInUser ? req.loggedInUser.username : null; |   const { query, page } = req.query; | ||||||
|   const includeAdult = adult == "true" ? true : false; |  | ||||||
|  |  | ||||||
|   if (username) { |   if (user) { | ||||||
|     searchHistory.create(username, query); |     return searchHistory.create(user, query); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   return tmdb |   tmdb.movieSearch(query, page) | ||||||
|     .movieSearch(query, page, includeAdult) |  | ||||||
|     .then(movieSearchResults => res.send(movieSearchResults)) |     .then(movieSearchResults => res.send(movieSearchResults)) | ||||||
|     .catch(error => { |     .catch(error => { | ||||||
|       const { status, message } = error; |       const { status, message } = error; | ||||||
|  |  | ||||||
|       if (status && message) { |       if (status && message) { | ||||||
|         res.status(status).send({ success: false, message }); |         res.status(status).send({ success: false, message }) | ||||||
|       } else { |       } else { | ||||||
|         // TODO log unhandled errors |         // TODO log unhandled errors | ||||||
|         console.log("caugth movie search controller error", error); |         console.log('caugth movie search controller error', error) | ||||||
|         res.status(500).send({ |         res.status(500).send({ | ||||||
|           message: `An unexpected error occured while searching movies with query: ${query}` |           message: `An unexpected error occured while searching movies with query: ${query}` | ||||||
|         }); |         }) | ||||||
|       } |       } | ||||||
|     }); |     }) | ||||||
| } | } | ||||||
|  |  | ||||||
| module.exports = movieSearchController; | module.exports = movieSearchController; | ||||||
|   | |||||||
| @@ -1,14 +1,16 @@ | |||||||
| const configuration = require("src/config/configuration").getInstance(); | const configuration = require('src/config/configuration').getInstance(); | ||||||
| const TMDB = require("src/tmdb/tmdb"); | const Cache = require('src/tmdb/cache'); | ||||||
| const SearchHistory = require("src/searchHistory/searchHistory"); | const TMDB = require('src/tmdb/tmdb'); | ||||||
| const tmdb = new TMDB(configuration.get("tmdb", "apiKey")); | const SearchHistory = require('src/searchHistory/searchHistory'); | ||||||
|  | const cache = new Cache(); | ||||||
|  | const tmdb = new TMDB(cache, configuration.get('tmdb', 'apiKey')); | ||||||
| const searchHistory = new SearchHistory(); | const searchHistory = new SearchHistory(); | ||||||
|  |  | ||||||
| function checkAndCreateJsonResponse(result) { | function checkAndCreateJsonResponse(result) { | ||||||
|   if (typeof result["createJsonResponse"] === "function") { |   if (typeof result['createJsonResponse'] === 'function') { | ||||||
|     return result.createJsonResponse(); |     return result.createJsonResponse() | ||||||
|   } |   } | ||||||
|   return result; |   return result | ||||||
| } | } | ||||||
|  |  | ||||||
| /** | /** | ||||||
| @@ -18,31 +20,26 @@ function checkAndCreateJsonResponse(result) { | |||||||
|  * @returns {Callback} |  * @returns {Callback} | ||||||
|  */ |  */ | ||||||
| function multiSearchController(req, res) { | function multiSearchController(req, res) { | ||||||
|   const { query, page, adult } = req.query; |   const user = req.loggedInUser; | ||||||
|   const username = req.loggedInUser ? req.loggedInUser.username : null; |   const { query, page } = req.query; | ||||||
|  |  | ||||||
|   if (username) { |   if (user) { | ||||||
|     searchHistory.create(username, query); |     searchHistory.create(user, query) | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   return tmdb |   return tmdb.multiSearch(query, page) | ||||||
|     .multiSearch(query, page, adult) |   .then(multiSearchResults => res.send(multiSearchResults)) | ||||||
|     .then(multiSearchResults => res.send(multiSearchResults)) |   .catch(error => { | ||||||
|     .catch(error => { |     const { status, message } = error; | ||||||
|       const { status, message } = error; |  | ||||||
|  |  | ||||||
|       if (status && message) { |     if (status && message) { | ||||||
|         res.status(status).send({ success: false, message }); |       res.status(status).send({ success: false, message }) | ||||||
|       } else { |     } else { | ||||||
|         // TODO log unhandled errors |       // TODO log unhandled errors | ||||||
|         console.log("caugth multi search controller error", error); |       console.log('caugth multi search controller error', error) | ||||||
|         res |       res.status(500).send({ message: `An unexpected error occured while searching with query: ${query}` }) | ||||||
|           .status(500) |     } | ||||||
|           .send({ |   }) | ||||||
|             message: `An unexpected error occured while searching with query: ${query}` |  | ||||||
|           }); |  | ||||||
|       } |  | ||||||
|     }); |  | ||||||
| } | } | ||||||
|  |  | ||||||
| module.exports = multiSearchController; | module.exports = multiSearchController; | ||||||
|   | |||||||
| @@ -1,7 +1,9 @@ | |||||||
| const configuration = require("src/config/configuration").getInstance(); | const configuration = require('src/config/configuration').getInstance(); | ||||||
| const TMDB = require("src/tmdb/tmdb"); | const Cache = require('src/tmdb/cache'); | ||||||
| const SearchHistory = require("src/searchHistory/searchHistory"); | const TMDB = require('src/tmdb/tmdb'); | ||||||
| const tmdb = new TMDB(configuration.get("tmdb", "apiKey")); | const SearchHistory = require('src/searchHistory/searchHistory'); | ||||||
|  | const cache = new Cache(); | ||||||
|  | const tmdb = new TMDB(cache, configuration.get('tmdb', 'apiKey')); | ||||||
| const searchHistory = new SearchHistory(); | const searchHistory = new SearchHistory(); | ||||||
|  |  | ||||||
| /** | /** | ||||||
| @@ -11,30 +13,30 @@ const searchHistory = new SearchHistory(); | |||||||
|  * @returns {Callback} |  * @returns {Callback} | ||||||
|  */ |  */ | ||||||
| function personSearchController(req, res) { | function personSearchController(req, res) { | ||||||
|   const { query, page, adult } = req.query; |   const user = req.loggedInUser; | ||||||
|   const username = req.loggedInUser ? req.loggedInUser.username : null; |   const { query, page } = req.query; | ||||||
|   const includeAdult = adult == "true" ? true : false; |  | ||||||
|  |  | ||||||
|   if (username) { |   if (user) { | ||||||
|     searchHistory.create(username, query); |     return searchHistory.create(user, query); | ||||||
|   } |   } | ||||||
|  |    | ||||||
|   return tmdb |   tmdb.personSearch(query, page) | ||||||
|     .personSearch(query, page, includeAdult) |     .then((person) => { | ||||||
|     .then(persons => res.send(persons)) |       res.send(person); | ||||||
|  |     }) | ||||||
|     .catch(error => { |     .catch(error => { | ||||||
|       const { status, message } = error; |       const { status, message } = error; | ||||||
|  |  | ||||||
|       if (status && message) { |       if (status && message) { | ||||||
|         res.status(status).send({ success: false, message }); |         res.status(status).send({ success: false, message }) | ||||||
|       } else { |       } else { | ||||||
|         // TODO log unhandled errors |         // TODO log unhandled errors | ||||||
|         console.log("caugth person search controller error", error); |         console.log('caugth person search controller error', error) | ||||||
|         res.status(500).send({ |         res.status(500).send({ | ||||||
|           message: `An unexpected error occured while searching people with query: ${query}` |           message: `An unexpected error occured while searching people with query: ${query}` | ||||||
|         }); |         }) | ||||||
|       } |       } | ||||||
|     }); |     }) | ||||||
| } | } | ||||||
|  |  | ||||||
| module.exports = personSearchController; | module.exports = personSearchController; | ||||||
|   | |||||||
| @@ -1,7 +1,9 @@ | |||||||
| const SearchHistory = require("src/searchHistory/searchHistory"); | const SearchHistory = require('src/searchHistory/searchHistory'); | ||||||
| const configuration = require("src/config/configuration").getInstance(); | const configuration = require('src/config/configuration').getInstance(); | ||||||
| const TMDB = require("src/tmdb/tmdb"); | const Cache = require('src/tmdb/cache'); | ||||||
| const tmdb = new TMDB(configuration.get("tmdb", "apiKey")); | const TMDB = require('src/tmdb/tmdb'); | ||||||
|  | const cache = new Cache(); | ||||||
|  | const tmdb = new TMDB(cache, configuration.get('tmdb', 'apiKey')); | ||||||
| const searchHistory = new SearchHistory(); | const searchHistory = new SearchHistory(); | ||||||
|  |  | ||||||
| /** | /** | ||||||
| @@ -11,22 +13,23 @@ const searchHistory = new SearchHistory(); | |||||||
|  * @returns {Callback} |  * @returns {Callback} | ||||||
|  */ |  */ | ||||||
| function showSearchController(req, res) { | function showSearchController(req, res) { | ||||||
|   const { query, page, adult } = req.query; |   const user = req.loggedInUser; | ||||||
|   const username = req.loggedInUser ? req.loggedInUser.username : null; |   const { query, page } = req.query; | ||||||
|   const includeAdult = adult == "true" ? true : false; |  | ||||||
|  |  | ||||||
|   if (username) { |   Promise.resolve() | ||||||
|     searchHistory.create(username, query); |   .then(() => { | ||||||
|   } |     if (user) { | ||||||
|  |       return searchHistory.create(user, query); | ||||||
|   return tmdb |     } | ||||||
|     .showSearch(query, page, includeAdult) |     return null | ||||||
|     .then(shows => { |   }) | ||||||
|       res.send(shows); |   .then(() => tmdb.showSearch(query, page)) | ||||||
|     }) |   .then((shows) => { | ||||||
|     .catch(error => { |     res.send(shows); | ||||||
|       res.status(500).send({ success: false, message: error.message }); |   }) | ||||||
|     }); |   .catch(error => { | ||||||
|  |     res.status(500).send({ success: false, message: error.message }); | ||||||
|  |   }); | ||||||
| } | } | ||||||
|  |  | ||||||
| module.exports = showSearchController; | module.exports = showSearchController; | ||||||
|   | |||||||
| @@ -1,6 +1,9 @@ | |||||||
| const configuration = require('src/config/configuration').getInstance(); | const configuration = require('src/config/configuration').getInstance(); | ||||||
|  | const Cache = require('src/tmdb/cache'); | ||||||
| const TMDB = require('src/tmdb/tmdb'); | const TMDB = require('src/tmdb/tmdb'); | ||||||
| const tmdb = new TMDB(configuration.get('tmdb', 'apiKey')); |  | ||||||
|  | const cache = new Cache(); | ||||||
|  | const tmdb = new TMDB(cache, configuration.get('tmdb', 'apiKey')); | ||||||
|  |  | ||||||
| const showCreditsController = (req, res) => { | const showCreditsController = (req, res) => { | ||||||
|   const showId = req.params.id; |   const showId = req.params.id; | ||||||
| @@ -20,4 +23,4 @@ const showCreditsController = (req, res) => { | |||||||
|     }) |     }) | ||||||
| } | } | ||||||
|  |  | ||||||
| module.exports = showCreditsController; | module.exports = showCreditsController; | ||||||
| @@ -1,7 +1,9 @@ | |||||||
| const configuration = require('src/config/configuration').getInstance(); | const configuration = require('src/config/configuration').getInstance(); | ||||||
|  | const Cache = require('src/tmdb/cache'); | ||||||
| const TMDB = require('src/tmdb/tmdb'); | const TMDB = require('src/tmdb/tmdb'); | ||||||
| const Plex = require('src/plex/plex'); | const Plex = require('src/plex/plex'); | ||||||
| const tmdb = new TMDB(configuration.get('tmdb', 'apiKey')); | const cache = new Cache(); | ||||||
|  | const tmdb = new TMDB(cache, configuration.get('tmdb', 'apiKey')); | ||||||
| const plex = new Plex(configuration.get('plex', 'ip')); | const plex = new Plex(configuration.get('plex', 'ip')); | ||||||
|  |  | ||||||
| function handleError(error, res) { | function handleError(error, res) { | ||||||
|   | |||||||
| @@ -1,93 +0,0 @@ | |||||||
| 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 |  | ||||||
| }; |  | ||||||
							
								
								
									
										24
									
								
								seasoned_api/src/webserver/controllers/user/history.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								seasoned_api/src/webserver/controllers/user/history.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 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,61 +1,33 @@ | |||||||
| const User = require("src/user/user"); | const User = require('src/user/user'); | ||||||
| const Token = require("src/user/token"); | const Token = require('src/user/token'); | ||||||
| const UserSecurity = require("src/user/userSecurity"); | const UserSecurity = require('src/user/userSecurity'); | ||||||
| const UserRepository = require("src/user/userRepository"); | const UserRepository = require('src/user/userRepository'); | ||||||
| const configuration = require("src/config/configuration").getInstance(); | const configuration = require('src/config/configuration').getInstance(); | ||||||
|  |  | ||||||
| const secret = configuration.get("authentication", "secret"); | const secret = configuration.get('authentication', 'secret'); | ||||||
| const userSecurity = new UserSecurity(); | const userSecurity = new UserSecurity(); | ||||||
| const userRepository = new UserRepository(); | const userRepository = new UserRepository(); | ||||||
|  |  | ||||||
| // TODO look to move some of the token generation out of the reach of the final "catch-all" |  | ||||||
| // catch including the, maybe sensitive, error message. |  | ||||||
|  |  | ||||||
| const isProduction = process.env.NODE_ENV === "production"; |  | ||||||
| const cookieOptions = { |  | ||||||
|   httpOnly: false, |  | ||||||
|   secure: isProduction, |  | ||||||
|   maxAge: 90 * 24 * 3600000, // 90 days |  | ||||||
|   sameSite: isProduction ? "Strict" : "Lax" |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * Controller: Log in a user provided correct credentials. |  * Controller: Log in a user provided correct credentials. | ||||||
|  * @param {Request} req http request variable |  * @param {Request} req http request variable | ||||||
|  * @param {Response} res |  * @param {Response} res | ||||||
|  * @returns {Callback} |  * @returns {Callback} | ||||||
|  */ |  */ | ||||||
| async function loginController(req, res) { | function loginController(req, res) { | ||||||
|   const user = new User(req.body.username); |    const user = new User(req.body.username); | ||||||
|   const password = req.body.password; |    const password = req.body.password; | ||||||
|  |  | ||||||
|   try { |    userSecurity.login(user, password) | ||||||
|     const [loggedIn, isAdmin, settings] = await Promise.all([ |       .then(() => userRepository.checkAdmin(user)) | ||||||
|       userSecurity.login(user, password), |       .then(checkAdmin => { | ||||||
|       userRepository.checkAdmin(user), |          const isAdmin = checkAdmin === 1 ? true : false; | ||||||
|       userRepository.getSettings(user.username) |          const token = new Token(user, isAdmin).toString(secret); | ||||||
|     ]); |          res.send({ success: true, token }); | ||||||
|  |       }) | ||||||
|     if (!loggedIn) { |       .catch(error => { | ||||||
|       return res.status(503).send({ |          res.status(401).send({ success: false, message: error.message }); | ||||||
|         success: false, |  | ||||||
|         message: "Unexpected error! Unable to create user." |  | ||||||
|       }); |       }); | ||||||
|     } |  | ||||||
|  |  | ||||||
|     const token = new Token( |  | ||||||
|       user, |  | ||||||
|       isAdmin === 1 ? true : false, |  | ||||||
|       settings |  | ||||||
|     ).toString(secret); |  | ||||||
|  |  | ||||||
|     return res.cookie("authorization", token, cookieOptions).status(200).send({ |  | ||||||
|       success: true, |  | ||||||
|       message: "Welcome to request.movie!" |  | ||||||
|     }); |  | ||||||
|   } catch (error) { |  | ||||||
|     return res.status(401).send({ success: false, message: error.message }); |  | ||||||
|   } |  | ||||||
| } | } | ||||||
|  |  | ||||||
| module.exports = loginController; | module.exports = loginController; | ||||||
|   | |||||||
| @@ -1,16 +0,0 @@ | |||||||
| /** |  | ||||||
|  * 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,21 +1,13 @@ | |||||||
| const User = require("src/user/user"); | const User = require('src/user/user'); | ||||||
| const Token = require("src/user/token"); | const Token = require('src/user/token'); | ||||||
| const UserSecurity = require("src/user/userSecurity"); | const UserSecurity = require('src/user/userSecurity'); | ||||||
| const UserRepository = require("src/user/userRepository"); | const UserRepository = require('src/user/userRepository'); | ||||||
| const configuration = require("src/config/configuration").getInstance(); | const configuration = require('src/config/configuration').getInstance(); | ||||||
|  |  | ||||||
| const secret = configuration.get("authentication", "secret"); | const secret = configuration.get('authentication', 'secret'); | ||||||
| const userSecurity = new UserSecurity(); | const userSecurity = new UserSecurity(); | ||||||
| const userRepository = new UserRepository(); | const userRepository = new UserRepository(); | ||||||
|  |  | ||||||
| const isProduction = process.env.NODE_ENV === "production"; |  | ||||||
| const cookieOptions = { |  | ||||||
|   httpOnly: false, |  | ||||||
|   secure: isProduction, |  | ||||||
|   maxAge: 90 * 24 * 3600000, // 90 days |  | ||||||
|   sameSite: isProduction ? "Strict" : "Lax" |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * Controller: Register a new user |  * Controller: Register a new user | ||||||
|  * @param {Request} req http request variable |  * @param {Request} req http request variable | ||||||
| @@ -23,25 +15,21 @@ const cookieOptions = { | |||||||
|  * @returns {Callback} |  * @returns {Callback} | ||||||
|  */ |  */ | ||||||
| function registerController(req, res) { | function registerController(req, res) { | ||||||
|   const user = new User(req.body.username, req.body.email); |    const user = new User(req.body.username, req.body.email); | ||||||
|   const password = req.body.password; |    const password = req.body.password; | ||||||
|  |  | ||||||
|   userSecurity |    userSecurity.createNewUser(user, password) | ||||||
|     .createNewUser(user, password) |       .then(() => userRepository.checkAdmin(user)) | ||||||
|     .then(() => { |       .then(checkAdmin => { | ||||||
|       const token = new Token(user, false).toString(secret); |          const isAdmin = checkAdmin === 1 ? true : false; | ||||||
|  |          const token = new Token(user, isAdmin).toString(secret); | ||||||
|       return res |          res.send({ | ||||||
|         .cookie("authorization", token, cookieOptions) |             success: true, message: 'Welcome to Seasoned!', token | ||||||
|         .status(200) |          }); | ||||||
|         .send({ |       }) | ||||||
|           success: true, |       .catch(error => { | ||||||
|           message: "Welcome to Seasoned!" |          res.status(401).send({ success: false, message: error.message }); | ||||||
|         }); |       }); | ||||||
|     }) |  | ||||||
|     .catch(error => { |  | ||||||
|       res.status(401).send({ success: false, message: error.message }); |  | ||||||
|     }); |  | ||||||
| } | } | ||||||
|  |  | ||||||
| module.exports = registerController; | 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(); | const requestRepository = new RequestRepository(); | ||||||
|  |  | ||||||
| @@ -9,20 +9,15 @@ const requestRepository = new RequestRepository(); | |||||||
|  * @returns {Callback} |  * @returns {Callback} | ||||||
|  */ |  */ | ||||||
| function requestsController(req, res) { | function requestsController(req, res) { | ||||||
|   const username = req.loggedInUser ? req.loggedInUser.username : null; |    const user = req.loggedInUser; | ||||||
|  |  | ||||||
|   requestRepository |    requestRepository.userRequests(user) | ||||||
|     .userRequests(username) |       .then(requests => { | ||||||
|     .then(requests => { |          res.send({ success: true, results: requests, total_results: requests.length }); | ||||||
|       res.send({ |       }) | ||||||
|         success: true, |       .catch(error => { | ||||||
|         results: requests, |          res.status(500).send({ success: false, message: error.message }); | ||||||
|         total_results: requests.length |  | ||||||
|       }); |       }); | ||||||
|     }) |  | ||||||
|     .catch(error => { |  | ||||||
|       res.status(500).send({ success: false, message: error.message }); |  | ||||||
|     }); |  | ||||||
| } | } | ||||||
|  |  | ||||||
| module.exports = requestsController; | module.exports = requestsController; | ||||||
|   | |||||||
| @@ -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 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; |  | ||||||
| @@ -1,41 +0,0 @@ | |||||||
| 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 |  | ||||||
| }; |  | ||||||
| @@ -1,107 +0,0 @@ | |||||||
| 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 |  | ||||||
| }; |  | ||||||
							
								
								
									
										294
									
								
								seasoned_api/src/webserver/graphql/requests.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										294
									
								
								seasoned_api/src/webserver/graphql/requests.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,294 @@ | |||||||
|  | const graphql = require("graphql"); | ||||||
|  | const establishedDatabase = require('src/database/database'); | ||||||
|  | const fetch = require('node-fetch'); | ||||||
|  |  | ||||||
|  |  | ||||||
|  | const TorrentType = new graphql.GraphQLObjectType({ | ||||||
|  |   name: "Torrent", | ||||||
|  |   fields: { | ||||||
|  |     magnet: { type: graphql.GraphQLString }, | ||||||
|  |     torrent_name: { type: graphql.GraphQLString}, | ||||||
|  |     tmdb_id: { type: graphql.GraphQLString } | ||||||
|  |   } | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | const RequestType = new graphql.GraphQLObjectType({ | ||||||
|  |   name: "Request", | ||||||
|  |   fields: { | ||||||
|  |     id: { type: graphql.GraphQLID }, | ||||||
|  |     title: { type: graphql.GraphQLString }, | ||||||
|  |     year: { type: graphql.GraphQLInt}, | ||||||
|  |     poster_path: { type: graphql.GraphQLString }, | ||||||
|  |     background_path: { type: graphql.GraphQLString }, | ||||||
|  |     requested_by: { type: graphql.GraphQLString }, | ||||||
|  |     ip: { type: graphql.GraphQLString }, | ||||||
|  |     date: { type: graphql.GraphQLString }, | ||||||
|  |     status: { type: graphql.GraphQLString }, | ||||||
|  |     user_agent: { type: graphql.GraphQLString }, | ||||||
|  |     type: { type: graphql.GraphQLString } | ||||||
|  |   } | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | const RequestsType = new graphql.GraphQLObjectType({ | ||||||
|  |   name: 'Requests', | ||||||
|  |   type: graphql.GraphQLList(RequestType), | ||||||
|  |   resolve: (root, args, context, info) => { | ||||||
|  |     return establishedDatabase.all("SELECT * FROM requests;") | ||||||
|  |       .catch(error => console.error("something went wrong fetching 'all' query. Error:", error)) | ||||||
|  |   } | ||||||
|  | }) | ||||||
|  |  | ||||||
|  | const ProgressType = new graphql.GraphQLObjectType({ | ||||||
|  |   name: 'TorrentProgress', | ||||||
|  |   fields: { | ||||||
|  |     eta: { type: graphql.GraphQLInt }, | ||||||
|  |     finished: { type: graphql.GraphQLBoolean }, | ||||||
|  |     key: { type: graphql.GraphQLString }, | ||||||
|  |     name: { type: graphql.GraphQLString }, | ||||||
|  |     progress: { type: graphql.GraphQLFloat }, | ||||||
|  |     state: { type: graphql.GraphQLString }, | ||||||
|  |     Torrent: { | ||||||
|  |       type: TorrentType, | ||||||
|  |       resolve(parent) { | ||||||
|  |         console.log('prante: ', parent.name) | ||||||
|  |         console.log(parent.name.slice(0,10)) | ||||||
|  |         return establishedDatabase.get(`select magnet, torrent_name, tmdb_id from requested_torrent where torrent_name like (?);`, [parent.name.slice(0, 10) + '%']) | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     Requested: { | ||||||
|  |       type: graphql.GraphQLList(RequestType), | ||||||
|  |       // resolve: () => fetch('https://api.kevinmidboe.com/api/v2/request?page=1/').then(resp => resp.json()) | ||||||
|  |       // .then(data => { | ||||||
|  |       //   // console.log('data', data) | ||||||
|  |       //   return data.results | ||||||
|  |       // }) | ||||||
|  |       resolve: (parent) => { | ||||||
|  |         return establishedDatabase.all("SELECT * FROM requests;") | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | }) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | const TorrentsRequestedType = new graphql.GraphQLObjectType({ | ||||||
|  |   name: 'TorrentsRequested', | ||||||
|  |   fields: { | ||||||
|  |     magnet: { type: graphql.GraphQLString }, | ||||||
|  |     torrent_name: { type: graphql.GraphQLString }, | ||||||
|  |     tmdb_id: { type: graphql.GraphQLString }, | ||||||
|  |     date_added: { type: graphql.GraphQLString }, | ||||||
|  |     Request: { | ||||||
|  |       type: RequestType, | ||||||
|  |       // resolve: () => fetch('https://api.kevinmidboe.com/api/v2/request?page=1/').then(resp => resp.json()) | ||||||
|  |       // .then(data => { | ||||||
|  |       //   return data.results | ||||||
|  |       // }) | ||||||
|  |       resolve(parentValue, args) { | ||||||
|  |         return establishedDatabase.get('select * from requests where id = (?);', [parentValue.tmdb_id]) | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     Progress: { | ||||||
|  |       type: ProgressType, | ||||||
|  |       resolve(parentValue, args) { | ||||||
|  |         return fetch('http://localhost:5000/') | ||||||
|  |           .then(resp => resp.json()) | ||||||
|  |           // .then(data => { console.log('data', data); return data.filter(download => download.name === parentValue.torrent_name) }) | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | }) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | // create a graphql query to select all and by id | ||||||
|  | var queryType = new graphql.GraphQLObjectType({ | ||||||
|  |     name: 'Query', | ||||||
|  |     fields: { | ||||||
|  |         //first query to select all | ||||||
|  |         Requests: { | ||||||
|  |             type: graphql.GraphQLList(RequestType), | ||||||
|  |             resolve: (root, args, context, info) => { | ||||||
|  |                 return establishedDatabase.all("SELECT * FROM requests;") | ||||||
|  |                   .catch(error => console.error("something went wrong fetching 'all' query. Error:", error)) | ||||||
|  |             } | ||||||
|  |         }, | ||||||
|  |         Progress: { | ||||||
|  |           type: graphql.GraphQLList(ProgressType), | ||||||
|  |           resolve: (root, args, context, info) => { | ||||||
|  |             console.log('user', context.loggedInUser) | ||||||
|  |             return fetch('http://localhost:5000') | ||||||
|  |               .then(resp => resp.json()) | ||||||
|  |           } | ||||||
|  |         }, | ||||||
|  |         ProgressRequested: { | ||||||
|  |           type: graphql.GraphQLList(ProgressType), | ||||||
|  |           resolve: (root, args, context, info) => { | ||||||
|  |             console.log('root & args', root, args) | ||||||
|  |           } | ||||||
|  |         }, | ||||||
|  |         TorrentsRequested: { | ||||||
|  |           type: graphql.GraphQLList(TorrentsRequestedType), | ||||||
|  |           resolve: (root, args, context, info) => { | ||||||
|  |             return establishedDatabase.all("SELECT * FROM requested_torrent;") | ||||||
|  |               .then(data => data.filter(request => { if (request.tmdb_id === '83666') { console.log('request', request, root);}; return request })) | ||||||
|  |               .catch(error => console.error("something went wrong fetching 'all' query. Error:", error)) | ||||||
|  |           } | ||||||
|  |         }, | ||||||
|  |         DownloadingRequestByName: { | ||||||
|  |           type: RequestType, | ||||||
|  |           args:{ | ||||||
|  |             name:{ | ||||||
|  |               type: new graphql.GraphQLNonNull(graphql.GraphQLString) | ||||||
|  |             }                | ||||||
|  |           }, | ||||||
|  |           resolve: (root, { name }, context, info) => { | ||||||
|  |             return establishedDatabase.all("SELECT * FROM requests where requested_by = (?);", [name]) | ||||||
|  |               .catch(error => console.error("something went wrong fetching 'all' query. Error:", error)) | ||||||
|  |           } | ||||||
|  |         }, | ||||||
|  |         //second query to select by id | ||||||
|  |         Request:{ | ||||||
|  |             type: RequestType, | ||||||
|  |             args:{ | ||||||
|  |                 id:{ | ||||||
|  |                     type: new graphql.GraphQLNonNull(graphql.GraphQLID) | ||||||
|  |                 }                | ||||||
|  |             }, | ||||||
|  |             resolve: (root, {id}, context, info) => { | ||||||
|  |               return establishedDatabase.get("SELECT * FROM requests WHERE id = (?);",[id]) | ||||||
|  |                 .catch(error => console.error(`something went wrong fetching by id: '${ id }'. Error: ${ error }`)) | ||||||
|  |             } | ||||||
|  |         }, | ||||||
|  |         Torrents: { | ||||||
|  |           type: graphql.GraphQLList(TorrentType), | ||||||
|  |           resolve: (root, {id}, context, info) => { | ||||||
|  |             // console.log('parent', parent) | ||||||
|  |             return establishedDatabase.all("SELECT * FROM requested_torrent") | ||||||
|  |               .catch(error => console.error(`something went wrong fetching all torrents. Error: ${ error }`)) | ||||||
|  |           } | ||||||
|  |         }, | ||||||
|  |         Torrent: { | ||||||
|  |           type: TorrentType, | ||||||
|  |           args: { | ||||||
|  |             id: { | ||||||
|  |               type: new graphql.GraphQLNonNull(graphql.GraphQLID) | ||||||
|  |             } | ||||||
|  |           }, | ||||||
|  |           resolve: (parent, {id}, context, info) => { | ||||||
|  |             // console.log('searcing from parent', parent) | ||||||
|  |             return establishedDatabase.get("SELECT * FROM requested_torrent WHERE tmdb_id = (?);", [id]) | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | }); | ||||||
|  | //mutation type is a type of object to modify data (INSERT,DELETE,UPDATE) | ||||||
|  | // var mutationType = new graphql.GraphQLObjectType({ | ||||||
|  | //     name: 'Mutation', | ||||||
|  | //     fields: { | ||||||
|  | //       //mutation for creacte | ||||||
|  | //       createPost: { | ||||||
|  | //         //type of object to return after create in SQLite | ||||||
|  | //         type: PostType, | ||||||
|  | //         //argument of mutation creactePost to get from request | ||||||
|  | //         args: { | ||||||
|  | //           title: { | ||||||
|  | //             type: new graphql.GraphQLNonNull(graphql.GraphQLString) | ||||||
|  | //           }, | ||||||
|  | //           description:{ | ||||||
|  | //               type: new graphql.GraphQLNonNull(graphql.GraphQLString) | ||||||
|  | //           }, | ||||||
|  | //           createDate:{ | ||||||
|  | //               type: new graphql.GraphQLNonNull(graphql.GraphQLString) | ||||||
|  | //           }, | ||||||
|  | //           author:{ | ||||||
|  | //               type: new graphql.GraphQLNonNull(graphql.GraphQLString) | ||||||
|  | //           } | ||||||
|  | //         }, | ||||||
|  | //         resolve: (root, {title, description, createDate, author}) => { | ||||||
|  | //             return new Promise((resolve, reject) => { | ||||||
|  | //                 //raw SQLite to insert a new post in post table | ||||||
|  | //                 database.run('INSERT INTO Posts (title, description, createDate, author) VALUES (?,?,?,?);', [title, description, createDate, author], (err) => { | ||||||
|  | //                     if(err) { | ||||||
|  | //                         reject(null); | ||||||
|  | //                     } | ||||||
|  | //                     database.get("SELECT last_insert_rowid() as id", (err, row) => { | ||||||
|  |                          | ||||||
|  | //                         resolve({ | ||||||
|  | //                             id: row["id"], | ||||||
|  | //                             title: title, | ||||||
|  | //                             description: description, | ||||||
|  | //                             createDate:createDate, | ||||||
|  | //                             author: author | ||||||
|  | //                         }); | ||||||
|  | //                     }); | ||||||
|  | //                 }); | ||||||
|  | //             }) | ||||||
|  | //         } | ||||||
|  | //       }, | ||||||
|  | //       //mutation for update | ||||||
|  | //       updatePost: { | ||||||
|  | //         //type of object to return afater update in SQLite | ||||||
|  | //         type: graphql.GraphQLString, | ||||||
|  | //         //argument of mutation creactePost to get from request | ||||||
|  | //         args:{ | ||||||
|  | //             id:{ | ||||||
|  | //                 type: new graphql.GraphQLNonNull(graphql.GraphQLID) | ||||||
|  | //             }, | ||||||
|  | //             title: { | ||||||
|  | //                 type: new graphql.GraphQLNonNull(graphql.GraphQLString) | ||||||
|  | //             }, | ||||||
|  | //             description:{ | ||||||
|  | //                   type: new graphql.GraphQLNonNull(graphql.GraphQLString) | ||||||
|  | //             }, | ||||||
|  | //             createDate:{ | ||||||
|  | //                   type: new graphql.GraphQLNonNull(graphql.GraphQLString) | ||||||
|  | //             }, | ||||||
|  | //             author:{ | ||||||
|  | //                   type: new graphql.GraphQLNonNull(graphql.GraphQLString) | ||||||
|  | //             }              | ||||||
|  | //         }, | ||||||
|  | //         resolve: (root, {id, title, description, createDate, author}) => { | ||||||
|  | //             return new Promise((resolve, reject) => { | ||||||
|  | //                 //raw SQLite to update a post in post table | ||||||
|  | //                 database.run('UPDATE Posts SET title = (?), description = (?), createDate = (?), author = (?) WHERE id = (?);', [title, description, createDate, author, id], (err) => { | ||||||
|  | //                     if(err) { | ||||||
|  | //                         reject(err); | ||||||
|  | //                     } | ||||||
|  | //                     resolve(`Post #${id} updated`); | ||||||
|  | //                 }); | ||||||
|  | //             }) | ||||||
|  | //         } | ||||||
|  | //       }, | ||||||
|  | //       //mutation for update | ||||||
|  | //       deletePost: { | ||||||
|  | //          //type of object resturn after delete in SQLite | ||||||
|  | //         type: graphql.GraphQLString, | ||||||
|  | //         args:{ | ||||||
|  | //             id:{ | ||||||
|  | //                 type: new graphql.GraphQLNonNull(graphql.GraphQLID) | ||||||
|  | //             }                | ||||||
|  | //         }, | ||||||
|  | //         resolve: (root, {id}) => { | ||||||
|  | //             return new Promise((resolve, reject) => { | ||||||
|  | //                 //raw query to delete from post table by id | ||||||
|  | //                 database.run('DELETE from Posts WHERE id =(?);', [id], (err) => { | ||||||
|  | //                     if(err) { | ||||||
|  | //                         reject(err); | ||||||
|  | //                     } | ||||||
|  | //                     resolve(`Post #${id} deleted`);                     | ||||||
|  | //                 }); | ||||||
|  | //             }) | ||||||
|  | //         } | ||||||
|  | //       } | ||||||
|  | //     } | ||||||
|  | // }); | ||||||
|  |  | ||||||
|  | //define schema with post object, queries, and mustation  | ||||||
|  | const schema = new graphql.GraphQLSchema({ | ||||||
|  |     query: queryType, | ||||||
|  |     // mutation: mutationType  | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | //export schema to use on index.js | ||||||
|  | module.exports = { | ||||||
|  |     schema | ||||||
|  | } | ||||||
| @@ -6,7 +6,7 @@ const mustBeAdmin = (req, res, next) => { | |||||||
|    if (req.loggedInUser === undefined) { |    if (req.loggedInUser === undefined) { | ||||||
|       return res.status(401).send({ |       return res.status(401).send({ | ||||||
|          success: false, |          success: false, | ||||||
|          message: 'You must be logged in.', |          error: 'You must be logged in.', | ||||||
|       }); |       }); | ||||||
|    } else { |    } else { | ||||||
|       database.get(`SELECT admin FROM user WHERE user_name IS ?`, req.loggedInUser.username) |       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) { |          if (isAdmin.admin == 0) { | ||||||
|             return res.status(401).send({ |             return res.status(401).send({ | ||||||
|                success: false, |                success: false, | ||||||
|                message: 'You must be logged in as a admin.' |                error: 'You must be logged in as a admin.' | ||||||
|             }) |             }) | ||||||
|          } |          } | ||||||
|       }) |       }) | ||||||
|   | |||||||
| @@ -1,11 +1,11 @@ | |||||||
| const mustBeAuthenticated = (req, res, next) => { | const mustBeAuthenticated = (req, res, next) => { | ||||||
|   if (req.loggedInUser === undefined) { |    if (req.loggedInUser === undefined) { | ||||||
|     return res.status(401).send({ |       return res.status(401).send({ | ||||||
|       success: false, |          success: false, | ||||||
|       message: "You must be logged in." |          error: 'You must be logged in.', | ||||||
|     }); |       }); | ||||||
|   } |    } | ||||||
|   return next(); |    return next(); | ||||||
| }; | }; | ||||||
|  |  | ||||||
| module.exports = mustBeAuthenticated; | module.exports = mustBeAuthenticated; | ||||||
|   | |||||||
| @@ -1,31 +0,0 @@ | |||||||
| 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; |  | ||||||
| @@ -1,32 +0,0 @@ | |||||||
| /* 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; |  | ||||||
							
								
								
									
										23
									
								
								seasoned_api/src/webserver/middleware/tokenToUser.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								seasoned_api/src/webserver/middleware/tokenToUser.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,23 @@ | |||||||
|  | /* 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