60 Commits

Author SHA1 Message Date
1b7a754224 Merge pull request #131 from KevinMidboe/feat/credits
Feat/credits
2022-08-15 23:58:54 +02:00
5d91f1bae7 TODO sms code 2022-08-15 23:51:18 +02:00
cdcfae56e7 Allow set-cookie & allow credentials 2022-08-15 23:39:39 +02:00
f2c77e092d Safer imports in credits 2022-08-15 23:38:53 +02:00
d6ac7e55e9 Better matching when getting matchin plex title & year 2022-08-15 23:38:35 +02:00
a3543090f2 Translate plex query params to fixed 2022-08-15 23:37:07 +02:00
041e944783 Upgraded bcrypt & sqlite3 2022-08-15 23:36:02 +02:00
bfd31ebd23 Linting 2022-03-06 11:59:32 +01:00
5036f4ca36 Read and pass adult search query param consistently for movie, show & person info 2022-03-06 11:57:45 +01:00
61b59ae3ea Person info now handles optional credits query parameter 2022-03-06 11:55:32 +01:00
92c49ac523 Registered endpoint for person credits 2022-03-06 11:54:56 +01:00
f680642f25 When fetching credits for person we get movie & show objects, now handled here. 2022-03-06 11:54:22 +01:00
f89486ae9e Sepearate controller and tmdb function for fetching personCredits 2022-03-06 11:53:45 +01:00
4d853565d1 Person gets biography & place_of_birth attributes 2022-03-06 11:50:20 +01:00
91c81e5cf6 Use cookie-parser, updated tokenToUser middleware & set logout endpoint 2022-03-06 10:39:34 +01:00
0ecbde9675 Logout endpoint for deleting auth cookie 2022-03-06 10:36:24 +01:00
d8e951c2ef Can now pass settings to new Token. 2022-03-06 10:34:48 +01:00
90f3d86511 Removed unused Promise resolves 2022-03-06 10:34:39 +01:00
c6791a7027 Login and register builds and sets cookie auth token 2022-03-06 10:31:58 +01:00
5b6a2c2651 Linting 2022-03-06 10:27:09 +01:00
4f7a22fff1 New tokenToUser middleware checks both header and cookie for Auth token 2022-03-06 10:17:56 +01:00
31b0c998a8 Removed filter option when searching jackett 2022-01-03 19:28:50 +01:00
9ce5b476ef Prettierrc file 2022-01-03 19:28:42 +01:00
554f292e4c Don't fail request when plex failes existance check 2022-01-03 19:28:09 +01:00
d8985aaff7 Correctly use username from req.loggedInUser 2022-01-03 19:17:27 +01:00
be889b8100 Lining 2022-01-03 19:06:11 +01:00
b5bd672f44 More accurate checks for what response contains and throws error if it does not exist. 2022-01-03 18:38:32 +01:00
4501bc5302 Updated yarn lock file 2022-01-03 18:33:45 +01:00
b384e748af Updated and moved update requests to /scripts 2022-01-03 18:32:48 +01:00
c676f182b4 Cache time in HR & future cache bust function 2022-01-03 18:31:28 +01:00
95d2b0095b Linting 2022-01-03 18:23:49 +01:00
8165cf8e85 Keep whitespace when sanitizing string 2022-01-03 18:04:10 +01:00
14775744b0 Linting 2022-01-03 18:03:53 +01:00
559e32c059 Use list index 1 results 2022-01-03 18:02:58 +01:00
f4dbaf4c58 Translate symbol + to space in query 2022-01-03 18:02:45 +01:00
1d25914ae0 Linting 2022-01-03 17:56:00 +01:00
4d3d8c874c Moved cors allowed origins to conf file. 2020-04-08 23:17:45 +02:00
08433523b7 Correctly export poster_path from person tmdb response object. 2020-04-08 23:09:37 +02:00
fce8879994 Searching with adult parameter is now handled and also cached correctly. 2020-04-08 23:08:42 +02:00
505b126043 Cleanup, removed comments and logs. 2020-04-08 23:07:09 +02:00
589bd7b08d Better comparison between plex and tmdb. Now also checks if plexTitle starts with tmdbTitle. 2020-04-08 23:05:56 +02:00
f0049ffb4e Merge pull request #129 from KevinMidboe/feat/redis-cache
Feat/redis cache
2020-04-08 23:04:06 +02:00
2b25397253 Misc updates to caching from plex and tmdb. 2020-04-08 23:01:33 +02:00
776f83553a Changed more instances of tmdb to new TMDB constructor not needing cache instance anymore. 2020-04-08 22:59:23 +02:00
815aaedffb Changed tmdb constructor to have apiKey as first parameter. If cache is not defined we use redis. 2020-04-08 22:54:19 +02:00
578eff30fb Id of request has been incorrectly saved as string, this has dictated the returntype to the frontend. Now set from string to int. 2020-04-08 21:58:37 +02:00
943cbe5cb8 Return rejected promise and don't set cache if key or value is null. 2020-04-08 21:57:49 +02:00
f89db46bf2 findPlexItemByTitleAndYear returns a promise so we await its response. 2020-02-21 23:09:42 +01:00
085fb76e11 Cache pirate respons for 3 hours in redis. 2020-02-21 22:09:42 +01:00
aa4a1c2a57 Registed route watch-link, by title & year
Fetch watch link for plex item matching query parameters title and year.
2020-02-21 21:53:51 +01:00
74340afd16 Full re-write. Refactored, cache and watchlink.
- Now the api calls to plex are cached.
- Search function has been refactored.
- Exists in plex has been re-written to use findPlexItemByTitleAndYear
if anything is found we return true. findPlexItemByTitleAndYear is then
also used for our new endpoint to get direct watch link for plex item.
Function parameters are title and year. Title is used when querying plex
and year is used with title to match correct plex search result. The
direct link includes the machine identifier so a function was added to
get system information from plex (read: 'PLEX_URL/' e.g. base path).
2020-02-21 21:53:19 +01:00
2672266908 Simplified every function towards tmdb by having the shared functionality separated into a function. 2020-02-21 21:47:00 +01:00
f37786aa76 Redis cache script with get and set functions.
This replaces old way of caching to sqlite. Still have same
functionality, so users of old cache functions just need to rename their
import.
2020-02-21 18:11:39 +01:00
91f64e5cfb Redis new dependency 2020-02-21 18:09:04 +01:00
a4d3123910 Stricter check for plex search results. 2020-01-16 21:25:45 +01:00
bc6fe3ed48 Encode search query before searching plex. 2020-01-16 21:25:13 +01:00
b23566509f Merge pull request #128 from KevinMidboe/fix/match-list-plex
Fixed issue matching list of plex object to tmdb.
2020-01-16 21:22:34 +01:00
341a07621d Fixed issue matching list of plex object to tmdb.
We have a Plex function that allows us to input a tmdb object and a plex
search result too see if the tmdb object has anything similar when
searching in plex.
Fixed an issue where plex returned a list of items. This list is now
mapped over each list element.
2020-01-16 21:21:40 +01:00
259ed9b06f Error. Fixed capitalization of file name. 2019-12-25 21:12:29 +01:00
cddf06cbcc Travis should make a copy of the example development config. 2019-12-23 02:08:20 +01:00
53 changed files with 4721 additions and 3273 deletions

10
.prettierrc Normal file
View File

@@ -0,0 +1,10 @@
{
"tabWidth": 2,
"useTabs": false,
"semi": true,
"singleQuote": false,
"bracketSpacing": true,
"arrowParens": "avoid",
"vueIndentScriptAndStyle": false,
"trailingComma": "none"
}

View File

@@ -7,6 +7,7 @@ script:
- yarn coverage
before_install:
- cd seasoned_api
- cp conf/development.json.example conf/development.json
before_script:
- yarn
- curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter

View File

@@ -1,25 +1,26 @@
{
"database": {
"host": "../shows.db"
},
"webserver": {
"port": 31459
},
"tmdb": {
"apiKey": ""
},
"plex": {
"ip": ""
},
"database": {
"host": "../shows.db"
},
"webserver": {
"port": 31459,
"origins": []
},
"tmdb": {
"apiKey": ""
},
"plex": {
"ip": ""
},
"tautulli": {
"apiKey": "",
"ip": "",
"port": ""
},
"raven": {
"DSN": ""
},
"authentication": {
"raven": {
"DSN": ""
},
"authentication": {
"secret": "secret"
}
}
}

View File

@@ -7,31 +7,33 @@
},
"main": "webserver/server.js",
"scripts": {
"start": "cross-env SEASONED_CONFIG=conf/development.json PROD=true NODE_PATH=. babel-node src/webserver/server.js",
"start": "cross-env SEASONED_CONFIG=conf/development.json NODE_ENV=production NODE_PATH=. babel-node src/webserver/server.js",
"test": "cross-env SEASONED_CONFIG=conf/test.json NODE_PATH=. mocha --require @babel/register --recursive test/unit test/system",
"coverage": "cross-env SEASONED_CONFIG=conf/test.json NODE_PATH=. nyc mocha --require @babel/register --recursive test && nyc report --reporter=text-lcov | coveralls",
"lint": "./node_modules/.bin/eslint src/",
"update": "cross-env SEASONED_CONFIG=conf/development.json NODE_PATH=. node src/plex/updateRequestsInPlex.js",
"update": "cross-env SEASONED_CONFIG=conf/development.json NODE_PATH=. node scripts/updateRequestsInPlex.js",
"docs": "yarn apiDocs; yarn classDocs",
"apiDocs": "",
"classDocs": "./script/generate-class-docs.sh"
},
"dependencies": {
"axios": "^0.18.0",
"bcrypt": "^3.0.6",
"bcrypt": "^5.0.1",
"body-parser": "~1.18.2",
"cookie-parser": "^1.4.6",
"cross-env": "~5.1.4",
"express": "~4.16.0",
"form-data": "^2.5.1",
"jsonwebtoken": "^8.2.0",
"jsonwebtoken": "^8.5.1",
"km-moviedb": "^0.2.12",
"node-cache": "^4.1.1",
"node-fetch": "^2.6.0",
"python-shell": "^0.5.0",
"raven": "^2.4.2",
"redis": "^3.0.2",
"request": "^2.87.0",
"request-promise": "^4.2",
"sqlite3": "^4.0.0"
"sqlite3": "^5.0.1"
},
"devDependencies": {
"@babel/core": "^7.5.5",

View File

@@ -0,0 +1,44 @@
const Plex = require("src/plex/plex");
const configuration = require("src/config/configuration").getInstance();
const plex = new Plex(configuration.get("plex", "ip"));
const establishedDatabase = require("src/database/database");
const queries = {
getRequestsNotYetInPlex: `SELECT * FROM requests WHERE status = 'requested' OR status = 'downloading'`,
saveNewStatus: `UPDATE requests SET status = ? WHERE id IS ? and type IS ?`
};
const getByStatus = () =>
establishedDatabase.all(queries.getRequestsNotYetInPlex);
const checkIfRequestExistInPlex = async request => {
request.existsInPlex = await plex.existsInPlex(request);
return request;
};
const commitNewStatus = (status, id, type, title) => {
console.log(type, title, "updated to:", status);
return establishedDatabase.run(queries.saveNewStatus, [status, id, type]);
};
const getNewRequestMatchesInPlex = async () => {
const requests = await getByStatus();
return Promise.all(requests.map(checkIfRequestExistInPlex))
.catch(error =>
console.log("error from checking plex for existance:", error)
)
.then(matchedRequests =>
matchedRequests.filter(request => request.existsInPlex)
);
};
const updateMatchInDb = (match, status) => {
return commitNewStatus(status, match.id, match.type, match.title);
};
getNewRequestMatchesInPlex()
.then(newMatches =>
Promise.all(newMatches.map(match => updateMatchInDb(match, "downloaded")))
)
.then(() => process.exit(0));

52
seasoned_api/src/cache/redis.js vendored Normal file
View File

@@ -0,0 +1,52 @@
const redis = require("redis")
const client = redis.createClient()
class Cache {
/**
* Retrieve an unexpired cache entry by key.
* @param {String} key of the cache entry
* @returns {Promise}
*/
get(key) {
return new Promise((resolve, reject) => {
client.get(key, (error, reply) => {
if (reply == null) {
return reject();
}
resolve(JSON.parse(reply));
});
});
}
/**
* Insert cache entry with key and value.
* @param {String} key of the cache entry
* @param {String} value of the cache entry
* @param {Number} timeToLive the number of seconds before entry expires
* @returns {Object}
*/
set(key, value, timeToLive = 10800) {
if (value == null || key == null) return null;
const json = JSON.stringify(value);
client.set(key, json, (error, reply) => {
if (reply == "OK") {
// successfully set value with key, now set TTL for key
client.expire(key, timeToLive, e => {
if (e)
console.error(
"Unexpected error while setting expiration for key:",
key,
". Error:",
error
);
});
}
});
return value;
}
}
module.exports = Cache;

View File

@@ -22,13 +22,13 @@ class Config {
get(section, option) {
if (this.fields[section] === undefined || this.fields[section][option] === undefined) {
throw new Error(`Filed "${section} => ${option}" does not exist.`);
throw new Error(`Field "${section} => ${option}" does not exist.`);
}
const field = new Field(this.fields[section][option]);
if (field.value === '') {
const envField = process.env[['SEASONED', section.toUpperCase(), option.toUpperCase()].join('_')];
const envField = process.env[[section.toUpperCase(), option.toUpperCase()].join('_')];
if (envField !== undefined && envField.length !== 0) { return envField; }
}

View File

@@ -31,7 +31,7 @@ CREATE TABLE IF NOT EXISTS search_history (
);
CREATE TABLE IF NOT EXISTS requests(
id TEXT,
id NUMBER,
title TEXT,
year NUMBER,
poster_path TEXT DEFAULT NULL,

View File

@@ -0,0 +1,35 @@
const request = require("request");
const configuration = require('src/config/configuration').getInstance();
const sendSMS = (message) => {
const apiKey = configuration.get('sms', 'apikey')
if (!apiKey) {
console.warning("api key for sms not set, cannot send sms.")
return null
}
const sender = configuration.get('sms', 'sender')
const recipient = configuration.get('sms', 'recipient')
return new Promise((resolve, reject) => {
request.post(
{
url: `https://gatewayapi.com/rest/mtsms?token=${apiKey}`,
json: true,
body: {
sender,
message,
recipients: [{ msisdn: `47${recipient}` }]
}
},
function(err, r, body) {
console.log(err ? err : body);
console.log("sms provider response:", body)
resolve()
}
);
})
}
module.exports = { sendSMS }

View File

@@ -1,84 +1,104 @@
const assert = require('assert');
const http = require('http');
const { URL } = require('url');
const PythonShell = require('python-shell');
const assert = require("assert");
const http = require("http");
const { URL } = require("url");
const PythonShell = require("python-shell");
const establishedDatabase = require('src/database/database');
const establishedDatabase = require("src/database/database");
const RedisCache = require("src/cache/redis");
const cache = new RedisCache();
function getMagnetFromURL(url) {
return new Promise((resolve, reject) => {
const options = new URL(url);
if (options.protocol.includes('magnet'))
resolve(url)
return new Promise((resolve, reject) => {
const options = new URL(url);
if (options.protocol.includes("magnet")) resolve(url);
http.get(options, (res) => {
if (res.statusCode == 301 || res.statusCode == 302) {
resolve(res.headers.location)
}
});
});
http.get(options, res => {
if (res.statusCode == 301 || res.statusCode == 302) {
resolve(res.headers.location);
}
});
});
}
async function find(searchterm, callback) {
const options = {
pythonPath: '../torrent_search/env/bin/python3',
scriptPath: '../torrent_search',
args: [searchterm, '-s', 'jackett', '-f', '--print']
}
const options = {
pythonPath: "../torrent_search/env/bin/python3",
scriptPath: "../torrent_search",
args: [searchterm, "-s", "jackett", "--print"]
};
PythonShell.run('torrentSearch/search.py', options, callback);
// PythonShell does not support return
PythonShell.run("torrentSearch/search.py", options, callback);
// PythonShell does not support return
}
async function callPythonAddMagnet(url, callback) {
getMagnetFromURL(url)
.then((magnet) => {
getMagnetFromURL(url)
.then(magnet => {
const options = {
pythonPath: '../delugeClient/env/bin/python3',
scriptPath: '../delugeClient',
args: ['add', magnet]
pythonPath: "../delugeClient/env/bin/python3",
scriptPath: "../delugeClient",
args: ["add", magnet]
};
PythonShell.run('deluge_cli.py', options, callback);
})
.catch((err) => {
PythonShell.run("deluge_cli.py", options, callback);
})
.catch(err => {
console.log(err);
throw new Error(err);
})
});
}
async function SearchPiratebay(query) {
return await new Promise((resolve, reject) => find(query, (err, results) => {
if (err) {
console.log('THERE WAS A FUCKING ERROR!\n', err);
reject(Error('There was a error when searching for torrents'));
}
if (results) {
resolve(JSON.parse(results, null, '\t'));
}
}));
if (query && query.includes("+")) {
query = query.replace("+", "%20");
}
const cacheKey = `pirate/${query}`;
return new Promise((resolve, reject) =>
cache
.get(cacheKey)
.then(resolve)
.catch(() =>
find(query, (err, results) => {
if (err) {
console.log("THERE WAS A FUCKING ERROR!\n", err);
reject(Error("There was a error when searching for torrents"));
}
if (results) {
const jsonData = JSON.parse(results[1], null, "\t");
cache.set(cacheKey, jsonData);
resolve(jsonData);
}
})
)
);
}
async function AddMagnet(magnet, name, tmdb_id) {
return await new Promise((resolve, reject) => callPythonAddMagnet(magnet, (err, results) => {
return await new Promise((resolve, reject) =>
callPythonAddMagnet(magnet, (err, results) => {
if (err) {
/* eslint-disable no-console */
console.log(err);
reject(Error('Enable to add torrent', err))
/* eslint-disable no-console */
console.log(err);
reject(Error("Enable to add torrent", err));
}
/* eslint-disable no-console */
console.log('result/error:', err, results);
console.log("result/error:", err, results);
database = establishedDatabase;
insert_query = "INSERT INTO requested_torrent(magnet,torrent_name,tmdb_id) \
insert_query =
"INSERT INTO requested_torrent(magnet,torrent_name,tmdb_id) \
VALUES (?,?,?)";
let response = database.run(insert_query, [magnet, name, tmdb_id]);
console.log('Response from requsted_torrent insert: ' + response);
console.log("Response from requsted_torrent insert: " + response);
resolve({ success: true });
}));
})
);
}
module.exports = { SearchPiratebay, AddMagnet };

View File

@@ -1,89 +1,240 @@
const fetch = require('node-fetch')
const convertPlexToMovie = require('src/plex/convertPlexToMovie')
const convertPlexToShow = require('src/plex/convertPlexToShow')
const convertPlexToEpisode = require('src/plex/convertPlexToEpisode')
const fetch = require("node-fetch");
const convertPlexToMovie = require("src/plex/convertPlexToMovie");
const convertPlexToShow = require("src/plex/convertPlexToShow");
const convertPlexToEpisode = require("src/plex/convertPlexToEpisode");
const { Movie, Show, Person } = require("src/tmdb/types");
const { Movie, Show, Person } = require('src/tmdb/types');
const RedisCache = require("src/cache/redis");
const redisCache = new RedisCache();
// const { Movie, }
// TODO? import class definitions to compare types ?
// what would typescript do?
const sanitize = string => string.toLowerCase().replace(/[^\w]/gi, "");
function fixedEncodeURIComponent(str) {
return encodeURIComponent(str).replace(/[!'()*]/g, function (c) {
return "%" + c.charCodeAt(0).toString(16).toUpperCase();
});
}
const matchingTitleAndYear = (plex, tmdb) => {
let matchingTitle, matchingYear;
if (plex["title"] != null && tmdb["title"] != null) {
const plexTitle = sanitize(plex.title);
const tmdbTitle = sanitize(tmdb.title);
matchingTitle = plexTitle == tmdbTitle;
matchingTitle = matchingTitle
? matchingTitle
: plexTitle.startsWith(tmdbTitle);
} else matchingTitle = false;
if (plex["year"] != null && tmdb["year"] != null)
matchingYear = plex.year == tmdb.year;
else matchingYear = false;
return matchingTitle && matchingYear;
};
const successfullResponse = response => {
if (response && response["MediaContainer"]) return response;
if (
response == null ||
response["status"] == null ||
response["statusText"] == null
) {
throw Error("Unable to decode response");
}
const { status, statusText } = response;
if (status === 200) {
return response.json();
} else {
throw { message: statusText, status: status };
}
};
class Plex {
constructor(ip, port=32400) {
this.plexIP = ip
this.plexPort = port
constructor(ip, port = 32400, cache = null) {
this.plexIP = ip;
this.plexPort = port;
this.cache = cache || redisCache;
this.cacheTags = {
machineInfo: "plex/mi",
search: "plex/s"
};
}
fetchMachineIdentifier() {
const cacheKey = `${this.cacheTags.machineInfo}`;
const url = `http://${this.plexIP}:${this.plexPort}/`;
const options = {
timeout: 20000,
headers: { Accept: "application/json" }
};
return new Promise((resolve, reject) =>
this.cache
.get(cacheKey)
.then(machineInfo => resolve(machineInfo["machineIdentifier"]))
.catch(() => fetch(url, options))
.then(response => response.json())
.then(machineInfo =>
this.cache.set(cacheKey, machineInfo["MediaContainer"], 2628000)
)
.then(machineInfo => resolve(machineInfo["machineIdentifier"]))
.catch(error => {
if (error != undefined && error.type === "request-timeout") {
reject({
message: "Plex did not respond",
status: 408,
success: false
});
}
reject(error);
})
);
}
matchTmdbAndPlexMedia(plex, tmdb) {
if (plex === undefined || tmdb === undefined)
return false
let match;
const sanitize = (string) => string.toLowerCase()
if (plex == null || tmdb == null) return false;
const matchTitle = sanitize(plex.title) === sanitize(tmdb.title)
const matchYear = plex.year === tmdb.year
return matchTitle && matchYear
}
existsInPlex(tmdbMovie) {
return this.search(tmdbMovie.title)
.then(plexMovies => plexMovies.some(plex => this.matchTmdbAndPlexMedia(plex, tmdbMovie)))
}
successfullResponse(response) {
const { status, statusText } = response
if (status === 200) {
return response.json()
if (plex instanceof Array) {
let possibleMatches = plex.map(plexItem =>
matchingTitleAndYear(plexItem, tmdb)
);
match = possibleMatches.includes(true);
} else {
throw { message: statusText, status: status }
match = matchingTitleAndYear(plex, tmdb);
}
return match;
}
async existsInPlex(tmdb) {
const plexMatch = await this.findPlexItemByTitleAndYear(
tmdb.title,
tmdb.year
);
return plexMatch ? true : false;
}
findPlexItemByTitleAndYear(title, year) {
const query = { title, year };
return this.search(title).then(plexResults => {
const matchesInPlex = plexResults.map(plex =>
this.matchTmdbAndPlexMedia(plex, query)
);
const matchesIndex = matchesInPlex.findIndex(el => el === true);
return matchesInPlex != -1 ? plexResults[matchesIndex] : null;
});
}
getDirectLinkByTitleAndYear(title, year) {
const machineIdentifierPromise = this.fetchMachineIdentifier();
const matchingObjectInPlexPromise = this.findPlexItemByTitleAndYear(
title,
year
);
return Promise.all([
machineIdentifierPromise,
matchingObjectInPlexPromise
]).then(([machineIdentifier, matchingObjectInPlex]) => {
if (
matchingObjectInPlex == false ||
matchingObjectInPlex == null ||
matchingObjectInPlex["key"] == null ||
machineIdentifier == null
)
return false;
const keyUriComponent = fixedEncodeURIComponent(matchingObjectInPlex.key);
return `https://app.plex.tv/desktop#!/server/${machineIdentifier}/details?key=${keyUriComponent}`;
});
}
search(query) {
const url = `http://${this.plexIP}:${this.plexPort}/hubs/search?query=${query}`
const cacheKey = `${this.cacheTags.search}:${query}`;
const url = `http://${this.plexIP}:${
this.plexPort
}/hubs/search?query=${fixedEncodeURIComponent(query)}`;
const options = {
timeout: 2000,
headers: { 'Accept': 'application/json' }
}
timeout: 20000,
headers: { Accept: "application/json" }
};
return fetch(url, options)
.then(this.successfullResponse)
.then(this.mapResults)
.catch(error => {
if (error.type === 'request-timeout') {
throw { message: 'Plex did not respond', status: 408, success: false }
}
return new Promise((resolve, reject) =>
this.cache
.get(cacheKey)
.catch(() => fetch(url, options)) // else fetch fresh data
.then(successfullResponse)
.then(results => this.cache.set(cacheKey, results, 21600)) // 6 hours
.then(this.mapResults)
.then(resolve)
.catch(error => {
if (error != undefined && error.type === "request-timeout") {
reject({
message: "Plex did not respond",
status: 408,
success: false
});
}
throw error
reject(error);
})
);
}
// this is not guarenteed to work, but if we see a movie or
// show has been imported, this function can be helpfull to call
// in order to try bust the cache preventing movieInfo and
// showInfo from seeing updates through existsInPlex.
bustSearchCacheWithTitle(title) {
const query = title;
const cacheKey = `${this.cacheTags.search}/${query}*`;
this.cache.del(
cacheKey,
(error,
response => {
if (response == 1) return true;
// TODO improve cache key matching by lowercasing it on the backend.
// what do we actually need to check for if the key was deleted or not
// it might be an error or another response code.
console.log("Unable to delete, key might not exists");
})
);
}
mapResults(response) {
if (response === undefined || response.MediaContainer === undefined) {
console.log('response was not valid to map', response)
return []
if (
response == null ||
response.MediaContainer == null ||
response.MediaContainer.Hub == null
) {
return [];
}
return response.MediaContainer.Hub
.filter(category => category.size > 0)
return response.MediaContainer.Hub.filter(category => category.size > 0)
.map(category => {
if (category.type === 'movie') {
return category.Metadata.map(movie => {
const ovie = Movie.convertFromPlexResponse(movie)
return ovie.createJsonResponse()
})
} else if (category.type === 'show') {
return category.Metadata.map(convertPlexToShow)
} else if (category.type === 'episode') {
return category.Metadata.map(convertPlexToEpisode)
if (category.type === "movie") {
return category.Metadata;
} else if (category.type === "show") {
return category.Metadata.map(convertPlexToShow);
} else if (category.type === "episode") {
return category.Metadata.map(convertPlexToEpisode);
}
})
.filter(result => result !== undefined)
.flat()
.filter(result => result !== undefined);
}
}

View File

@@ -1,101 +1,130 @@
const PlexRepository = require('src/plex/plexRepository');
const Cache = require('src/tmdb/cache');
const configuration = require('src/config/configuration').getInstance();
const TMDB = require('src/tmdb/tmdb');
const establishedDatabase = require('src/database/database');
const PlexRepository = require("src/plex/plexRepository");
const configuration = require("src/config/configuration").getInstance();
const TMDB = require("src/tmdb/tmdb");
const establishedDatabase = require("src/database/database");
const plexRepository = new PlexRepository(configuration.get('plex', 'ip'));
const cache = new Cache();
const tmdb = new TMDB(cache, configuration.get('tmdb', 'apiKey'));
const plexRepository = new PlexRepository(configuration.get("plex", "ip"));
const tmdb = new TMDB(configuration.get("tmdb", "apiKey"));
class RequestRepository {
constructor(database) {
this.database = database || establishedDatabase;
this.queries = {
insertRequest: `INSERT INTO requests(id,title,year,poster_path,background_path,requested_by,ip,user_agent,type)
constructor(database) {
this.database = database || establishedDatabase;
this.queries = {
insertRequest: `INSERT INTO requests(id,title,year,poster_path,background_path,requested_by,ip,user_agent,type)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
fetchRequestedItems: 'SELECT * FROM requests ORDER BY date DESC LIMIT 25 OFFSET ?*25-25',
fetchRequestedItemsByStatus: 'SELECT * FROM requests WHERE status IS ? AND type LIKE ? ORDER BY date DESC LIMIT 25 OFFSET ?*25-25',
updateRequestedById: 'UPDATE requests SET status = ? WHERE id is ? AND type is ?',
checkIfIdRequested: 'SELECT * FROM requests WHERE id IS ? AND type IS ?',
userRequests: 'SELECT * FROM requests WHERE requested_by IS ? ORDER BY date DESC',
};
this.cacheTags = {
search: 'se',
lookup: 'i',
};
}
fetchRequestedItems:
"SELECT * FROM requests ORDER BY date DESC LIMIT 25 OFFSET ?*25-25",
fetchRequestedItemsByStatus:
"SELECT * FROM requests WHERE status IS ? AND type LIKE ? ORDER BY date DESC LIMIT 25 OFFSET ?*25-25",
updateRequestedById:
"UPDATE requests SET status = ? WHERE id is ? AND type is ?",
checkIfIdRequested: "SELECT * FROM requests WHERE id IS ? AND type IS ?",
userRequests:
"SELECT * FROM requests WHERE requested_by IS ? ORDER BY date DESC"
};
this.cacheTags = {
search: "se",
lookup: "i"
};
}
search(query, type, page) {
return Promise.resolve()
.then(() => tmdb.search(query, type, page))
.catch(error => Error(`error in the house${error}`));
}
search(query, type, page) {
return Promise.resolve()
.then(() => tmdb.search(query, type, page))
.catch(error => Error(`error in the house${error}`));
}
lookup(identifier, type = 'movie') {
return Promise.resolve()
.then(() => tmdb.lookup(identifier, type))
.then(tmdbMovie => this.checkID(tmdbMovie))
.then(tmdbMovie => plexRepository.inPlex(tmdbMovie))
.catch((error) => {
throw new Error(error);
});
}
lookup(identifier, type = "movie") {
return Promise.resolve()
.then(() => tmdb.lookup(identifier, type))
.then(tmdbMovie => this.checkID(tmdbMovie))
.then(tmdbMovie => plexRepository.inPlex(tmdbMovie))
.catch(error => {
throw new Error(error);
});
}
checkID(tmdbMovie) {
return Promise.resolve()
.then(() => this.database.get(this.queries.checkIfIdRequested, [tmdbMovie.id, tmdbMovie.type]))
.then((result, error) => {
if (error) { throw new Error(error); }
tmdbMovie.requested = result ? true : false;
return tmdbMovie;
});
}
checkID(tmdbMovie) {
return Promise.resolve()
.then(() =>
this.database.get(this.queries.checkIfIdRequested, [
tmdbMovie.id,
tmdbMovie.type
])
)
.then((result, error) => {
if (error) {
throw new Error(error);
}
tmdbMovie.requested = result ? true : false;
return tmdbMovie;
});
}
/**
/**
* Send request for given media id.
* @param {identifier, type} the id of the media object and type of media must be defined
* @returns {Promise} If nothing has gone wrong.
*/
sendRequest(identifier, type, ip, user_agent, user) {
return Promise.resolve()
.then(() => tmdb.lookup(identifier, type))
.then((movie) => {
const username = user === undefined ? undefined : user.username;
// Add request to database
return this.database.run(this.queries.insertRequest, [movie.id, movie.title, movie.year, movie.poster_path, movie.background_path, username, ip, user_agent, movie.type]);
sendRequest(identifier, type, ip, user_agent, user) {
return Promise.resolve()
.then(() => tmdb.lookup(identifier, type))
.then(movie => {
const username = user === undefined ? undefined : user.username;
// Add request to database
return this.database.run(this.queries.insertRequest, [
movie.id,
movie.title,
movie.year,
movie.poster_path,
movie.background_path,
username,
ip,
user_agent,
movie.type
]);
});
}
}
fetchRequested(status, page = '1', type = '%') {
return Promise.resolve()
.then(() => {
if (status === 'requested' || status === 'downloading' || status === 'downloaded')
return this.database.all(this.queries.fetchRequestedItemsByStatus, [status, type, page]);
else
return this.database.all(this.queries.fetchRequestedItems, page);
})
}
fetchRequested(status, page = "1", type = "%") {
return Promise.resolve().then(() => {
if (
status === "requested" ||
status === "downloading" ||
status === "downloaded"
)
return this.database.all(this.queries.fetchRequestedItemsByStatus, [
status,
type,
page
]);
else return this.database.all(this.queries.fetchRequestedItems, page);
});
}
userRequests(user) {
return Promise.resolve()
.then(() => this.database.all(this.queries.userRequests, user.username))
.catch((error) => {
if (String(error).includes('no such column')) {
throw new Error('Username not found');
}
throw new Error('Unable to fetch your requests');
})
.then((result) => {
// TODO do a correct mapping before sending, not just a dump of the database
result.map(item => item.poster = item.poster_path)
return result
});
}
userRequests(username) {
return Promise.resolve()
.then(() => this.database.all(this.queries.userRequests, username))
.catch(error => {
if (String(error).includes("no such column")) {
throw new Error("Username not found");
}
throw new Error("Unable to fetch your requests");
})
.then(result => {
// TODO do a correct mapping before sending, not just a dump of the database
result.map(item => (item.poster = item.poster_path));
return result;
});
}
updateRequestedById(id, type, status) {
return this.database.run(this.queries.updateRequestedById, [status, id, type]);
}
updateRequestedById(id, type, status) {
return this.database.run(this.queries.updateRequestedById, [
status,
id,
type
]);
}
}
module.exports = RequestRepository;

View File

@@ -1,39 +0,0 @@
const Plex = require('src/plex/plex')
const configuration = require('src/config/configuration').getInstance();
const plex = new Plex(configuration.get('plex', 'ip'))
const establishedDatabase = require('src/database/database');
class UpdateRequestsInPlex {
constructor() {
this.database = establishedDatabase;
this.queries = {
getMovies: `SELECT * FROM requests WHERE status = 'requested' OR status = 'downloading'`,
// getMovies: "select * from requests where status is 'reset'",
saveNewStatus: `UPDATE requests SET status = ? WHERE id IS ? and type IS ?`,
}
}
getByStatus() {
return this.database.all(this.queries.getMovies);
}
scrub() {
return this.getByStatus()
.then((requests) => Promise.all(requests.map(movie => plex.existsInPlex(movie))))
}
commitNewStatus(status, id, type, title) {
console.log(type, title, 'updated to:', status)
this.database.run(this.queries.saveNewStatus, [status, id, type])
}
updateStatus(status) {
this.getByStatus()
.then(requests => Promise.all(requests.map(request => plex.existsInPlex(request))))
.then(matchedRequests => matchedRequests.filter(request => request.existsInPlex))
.then(newMatches => newMatches.map(match => this.commitNewStatus(status, match.id, match.type, match.title)))
}
}
var requestsUpdater = new UpdateRequestsInPlex();
requestsUpdater.updateStatus('downloaded')
module.exports = UpdateRequestsInPlex

View File

@@ -1,9 +1,7 @@
const assert = require('assert')
const configuration = require('src/config/configuration').getInstance();
const Cache = require('src/tmdb/cache');
const TMDB = require('src/tmdb/tmdb');
const cache = new Cache();
const tmdb = new TMDB(cache, configuration.get('tmdb', 'apiKey'));
const tmdb = new TMDB(configuration.get('tmdb', 'apiKey'));
const establishedDatabase = require('src/database/database');
const utils = require('./utils');

View File

@@ -1,4 +1,4 @@
const fetch = require('node-fetch');
const fetch = require("node-fetch");
class Tautulli {
constructor(apiKey, ip, port) {
@@ -8,50 +8,66 @@ class Tautulli {
}
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)
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
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)
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)
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)
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)
const url = this.buildUrlWithCmdAndUserid("get_history", plex_userid);
console.log('fetching url', url.href)
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));
}
}

View File

@@ -1,31 +1,74 @@
const moviedb = require('km-moviedb');
const moviedb = require("km-moviedb");
const RedisCache = require("src/cache/redis");
const redisCache = new RedisCache();
const { Movie, Show, Person, Credits, ReleaseDates } = require('src/tmdb/types');
// const { tmdbInfo } = require('src/tmdb/types')
const {
Movie,
Show,
Person,
Credits,
ReleaseDates
} = require("src/tmdb/types");
const tmdbErrorResponse = (error, typeString = undefined) => {
if (error.status === 404) {
let message = error.response.body.status_message;
throw {
status: 404,
message: message.slice(0, -1) + " in tmdb."
};
} else if (error.status === 401) {
throw {
status: 401,
message: error.response.body.status_message
};
}
throw {
status: 500,
message: `An unexpected error occured while fetching ${typeString} from tmdb`
};
};
class TMDB {
constructor(cache, apiKey, tmdbLibrary) {
this.cache = cache;
constructor(apiKey, cache, tmdbLibrary) {
this.tmdbLibrary = tmdbLibrary || moviedb(apiKey);
this.cache = cache || redisCache;
this.cacheTags = {
multiSearch: 'mus',
movieSearch: 'mos',
showSearch: 'ss',
personSearch: 'ps',
movieInfo: 'mi',
movieCredits: 'mc',
movieReleaseDates: 'mrd',
showInfo: 'si',
showCredits: 'sc',
personInfo: 'pi',
miscNowPlayingMovies: 'npm',
miscPopularMovies: 'pm',
miscTopRatedMovies: 'tpm',
miscUpcomingMovies: 'um',
tvOnTheAir: 'toa',
miscPopularTvs: 'pt',
miscTopRatedTvs: 'trt',
multiSearch: "mus",
movieSearch: "mos",
showSearch: "ss",
personSearch: "ps",
movieInfo: "mi",
movieCredits: "mc",
movieReleaseDates: "mrd",
movieImages: "mimg",
showInfo: "si",
showCredits: "sc",
personInfo: "pi",
personCredits: "pc",
miscNowPlayingMovies: "npm",
miscPopularMovies: "pm",
miscTopRatedMovies: "tpm",
miscUpcomingMovies: "um",
tvOnTheAir: "toa",
miscPopularTvs: "pt",
miscTopRatedTvs: "trt"
};
this.defaultTTL = 86400;
}
getFromCacheOrFetchFromTmdb(cacheKey, tmdbMethod, query) {
return new Promise((resolve, reject) =>
this.cache
.get(cacheKey)
.then(resolve)
.catch(() => this.tmdb(tmdbMethod, query))
.then(resolve)
.catch(error => reject(tmdbErrorResponse(error, tmdbMethod)))
);
}
/**
@@ -37,13 +80,11 @@ class TMDB {
*/
movieInfo(identifier) {
const query = { id: identifier };
const cacheKey = `${this.cacheTags.movieInfo}:${identifier}`;
const cacheKey = `tmdb/${this.cacheTags.movieInfo}:${identifier}`;
return this.cache.get(cacheKey)
.catch(() => this.tmdb('movieInfo', query))
.catch(tmdbError => tmdbErrorResponse(tmdbError, 'movie info'))
.then(movie => this.cache.set(cacheKey, movie, 1))
.then(movie => Movie.convertFromTmdbResponse(movie))
return this.getFromCacheOrFetchFromTmdb(cacheKey, "movieInfo", query)
.then(movie => this.cache.set(cacheKey, movie, this.defaultTTL))
.then(movie => Movie.convertFromTmdbResponse(movie));
}
/**
@@ -52,14 +93,12 @@ class TMDB {
* @returns {Promise} movie cast object
*/
movieCredits(identifier) {
const query = { id: identifier }
const cacheKey = `${this.cacheTags.movieCredits}:${identifier}`
const query = { id: identifier };
const cacheKey = `tmdb/${this.cacheTags.movieCredits}:${identifier}`;
return this.cache.get(cacheKey)
.catch(() => this.tmdb('movieCredits', query))
.catch(tmdbError => tmdbErrorResponse(tmdbError, 'movie credits'))
.then(credits => this.cache.set(cacheKey, credits, 1))
.then(credits => Credits.convertFromTmdbResponse(credits))
return this.getFromCacheOrFetchFromTmdb(cacheKey, "movieCredits", query)
.then(credits => this.cache.set(cacheKey, credits, this.defaultTTL))
.then(credits => Credits.convertFromTmdbResponse(credits));
}
/**
@@ -69,12 +108,10 @@ class TMDB {
*/
movieReleaseDates(identifier) {
const query = { id: identifier }
const cacheKey = `${this.cacheTags.movieReleaseDates}:${identifier}`
const cacheKey = `tmdb/${this.cacheTags.movieReleaseDates}:${identifier}`
return this.cache.get(cacheKey)
.catch(() => this.tmdb('movieReleaseDates', query))
.catch(tmdbError => tmdbErrorResponse(tmdbError, 'movie release dates'))
.then(releaseDates => this.cache.set(cacheKey, releaseDates, 1))
return this.getFromCacheOrFetchFromTmdb(cacheKey, 'movieReleaseDates', query)
.then(releaseDates => this.cache.set(cacheKey, releaseDates, this.defaultTTL))
.then(releaseDates => ReleaseDates.convertFromTmdbResponse(releaseDates))
}
@@ -86,24 +123,20 @@ class TMDB {
*/
showInfo(identifier) {
const query = { id: identifier };
const cacheKey = `${this.cacheTags.showInfo}:${identifier}`;
const cacheKey = `tmdb/${this.cacheTags.showInfo}:${identifier}`;
return this.cache.get(cacheKey)
.catch(() => this.tmdb('tvInfo', query))
.catch(tmdbError => tmdbErrorResponse(tmdbError, 'tv info'))
.then(show => this.cache.set(cacheKey, show, 1))
.then(show => Show.convertFromTmdbResponse(show))
return this.getFromCacheOrFetchFromTmdb(cacheKey, "tvInfo", query)
.then(show => this.cache.set(cacheKey, show, this.defaultTTL))
.then(show => Show.convertFromTmdbResponse(show));
}
showCredits(identifier) {
const query = { id: identifier }
const cacheKey = `${this.cacheTags.showCredits}:${identifier}`
const query = { id: identifier };
const cacheKey = `tmdb/${this.cacheTags.showCredits}:${identifier}`;
return this.cache.get(cacheKey)
.catch(() => this.tmdb('tvCredits', query))
.catch(tmdbError => tmdbErrorResponse(tmdbError, 'show credits'))
.then(credits => this.cache.set(cacheKey, credits, 1))
.then(credits => Credits.convertFromTmdbResponse(credits))
return this.getFromCacheOrFetchFromTmdb(cacheKey, "tvCredits", query)
.then(credits => this.cache.set(cacheKey, credits, this.defaultTTL))
.then(credits => Credits.convertFromTmdbResponse(credits));
}
/**
@@ -114,22 +147,32 @@ class TMDB {
*/
personInfo(identifier) {
const query = { id: identifier };
const cacheKey = `${this.cacheTags.personInfo}:${identifier}`;
const cacheKey = `tmdb/${this.cacheTags.personInfo}:${identifier}`;
return this.cache.get(cacheKey)
.catch(() => this.tmdb('personInfo', query))
.catch(tmdbError => tmdbErrorResponse(tmdbError, 'person info'))
.then(person => this.cache.set(cacheKey, person, 1))
.then(person => Person.convertFromTmdbResponse(person))
return this.getFromCacheOrFetchFromTmdb(cacheKey, "personInfo", query)
.then(person => this.cache.set(cacheKey, person, this.defaultTTL))
.then(person => Person.convertFromTmdbResponse(person));
}
multiSearch(search_query, page=1) {
const query = { query: search_query, page: page };
const cacheKey = `${this.cacheTags.multiSearch}:${page}:${search_query}`;
return this.cache.get(cacheKey)
.catch(() => this.tmdb('searchMulti', query))
.catch(tmdbError => tmdbErrorResponse(tmdbError, 'search results'))
.then(response => this.cache.set(cacheKey, response, 1))
personCredits(identifier) {
const query = { id: identifier };
const cacheKey = `tmdb/${this.cacheTags.personCredits}:${identifier}`;
return this.getFromCacheOrFetchFromTmdb(
cacheKey,
"personCombinedCredits",
query
)
.then(credits => this.cache.set(cacheKey, credits, this.defaultTTL))
.then(credits => Credits.convertFromTmdbResponse(credits));
}
multiSearch(search_query, page = 1, include_adult = true) {
const query = { query: search_query, page, include_adult };
const cacheKey = `tmdb/${this.cacheTags.multiSearch}:${page}:${search_query}:${include_adult}`;
return this.getFromCacheOrFetchFromTmdb(cacheKey, "searchMulti", query)
.then(response => this.cache.set(cacheKey, response, this.defaultTTL))
.then(response => this.mapResults(response));
}
@@ -139,15 +182,13 @@ class TMDB {
* @param {Number} page representing pagination of results
* @returns {Promise} dict with query results, current page and total_pages
*/
movieSearch(query, page=1) {
const tmdbquery = { query: query, page: page };
const cacheKey = `${this.cacheTags.movieSearch}:${page}:${query}`;
movieSearch(search_query, page = 1, include_adult = true) {
const tmdbquery = { query: search_query, page, include_adult };
const cacheKey = `tmdb/${this.cacheTags.movieSearch}:${page}:${search_query}:${include_adult}`;
return this.cache.get(cacheKey)
.catch(() => this.tmdb('searchMovie', tmdbquery))
.catch(tmdbError => tmdbErrorResponse(tmdbError, 'movie search results'))
.then(response => this.cache.set(cacheKey, response, 1))
.then(response => this.mapResults(response, 'movie'))
return this.getFromCacheOrFetchFromTmdb(cacheKey, "searchMovie", tmdbquery)
.then(response => this.cache.set(cacheKey, response, this.defaultTTL))
.then(response => this.mapResults(response, "movie"));
}
/**
@@ -156,15 +197,13 @@ class TMDB {
* @param {Number} page representing pagination of results
* @returns {Promise} dict with query results, current page and total_pages
*/
showSearch(query, page=1) {
const tmdbquery = { query: query, page: page };
const cacheKey = `${this.cacheTags.showSearch}:${page}:${query}`;
showSearch(search_query, page = 1, include_adult = true) {
const tmdbquery = { query: search_query, page, include_adult };
const cacheKey = `tmdb/${this.cacheTags.showSearch}:${page}:${search_query}:${include_adult}`;
return this.cache.get(cacheKey)
.catch(() => this.tmdb('searchTv', tmdbquery))
.catch(tmdbError => tmdbErrorResponse(tmdbError, 'tv search results'))
.then(response => this.cache.set(cacheKey, response, 1))
.then(response => this.mapResults(response, 'show'))
return this.getFromCacheOrFetchFromTmdb(cacheKey, "searchTv", tmdbquery)
.then(response => this.cache.set(cacheKey, response, this.defaultTTL))
.then(response => this.mapResults(response, "show"));
}
/**
@@ -173,37 +212,31 @@ class TMDB {
* @param {Number} page representing pagination of results
* @returns {Promise} dict with query results, current page and total_pages
*/
personSearch(query, page=1) {
personSearch(search_query, page = 1, include_adult = true) {
const tmdbquery = { query: search_query, page, include_adult };
const cacheKey = `tmdb/${this.cacheTags.personSearch}:${page}:${search_query}:${include_adult}`;
const tmdbquery = { query: query, page: page, include_adult: true };
const cacheKey = `${this.cacheTags.personSearch}:${page}:${query}`;
return this.cache.get(cacheKey)
.catch(() => this.tmdb('searchPerson', tmdbquery))
.catch(tmdbError => tmdbErrorResponse(tmdbError, 'person search results'))
.then(response => this.cache.set(cacheKey, response, 1))
.then(response => this.mapResults(response, 'person'))
return this.getFromCacheOrFetchFromTmdb(cacheKey, "searchPerson", tmdbquery)
.then(response => this.cache.set(cacheKey, response, this.defaultTTL))
.then(response => this.mapResults(response, "person"));
}
movieList(listname, page = 1) {
const query = { page: page };
const cacheKey = `${this.cacheTags[listname]}:${page}`;
return this.cache.get(cacheKey)
.catch(() => this.tmdb(listname, query))
.catch(tmdbError => this.tmdbErrorResponse(tmdbError, 'movie list ' + listname))
.then(response => this.cache.set(cacheKey, response, 1))
.then(response => this.mapResults(response, 'movie'))
const cacheKey = `tmdb/${this.cacheTags[listname]}:${page}`;
return this.getFromCacheOrFetchFromTmdb(cacheKey, listname, query)
.then(response => this.cache.set(cacheKey, response, this.defaultTTL))
.then(response => this.mapResults(response, "movie"));
}
showList(listname, page = 1) {
const query = { page: page };
const cacheKey = `${this.cacheTags[listname]}:${page}`;
const cacheKey = `tmdb/${this.cacheTags[listname]}:${page}`;
return this.cache.get(cacheKey)
.catch(() => this.tmdb(listname, query))
.catch(tmdbError => this.tmdbErrorResponse(tmdbError, 'show list ' + listname))
.then(response => this.cache.set(cacheKey, response, 1))
.then(response => this.mapResults(response, 'show'))
return this.getFromCacheOrFetchFromTmdb(cacheKey, listName, query)
.then(response => this.cache.set(cacheKey, response, this.defaultTTL))
.then(response => this.mapResults(response, "show"));
}
/**
@@ -212,29 +245,26 @@ class TMDB {
* @param {String} The type declared in listSearch.
* @returns {Promise} dict with tmdb results, mapped as movie/show objects.
*/
mapResults(response, type=undefined) {
// console.log(response.results)
// response.results.map(te => console.table(te))
mapResults(response, type = undefined) {
let results = response.results.map(result => {
if (type === 'movie' || result.media_type === 'movie') {
const movie = Movie.convertFromTmdbResponse(result)
return movie.createJsonResponse()
} else if (type === 'show' || result.media_type === 'tv') {
const show = Show.convertFromTmdbResponse(result)
return show.createJsonResponse()
} else if (type === 'person' || result.media_type === 'person') {
const person = Person.convertFromTmdbResponse(result)
return person.createJsonResponse()
if (type === "movie" || result.media_type === "movie") {
const movie = Movie.convertFromTmdbResponse(result);
return movie.createJsonResponse();
} else if (type === "show" || result.media_type === "tv") {
const show = Show.convertFromTmdbResponse(result);
return show.createJsonResponse();
} else if (type === "person" || result.media_type === "person") {
const person = Person.convertFromTmdbResponse(result);
return person.createJsonResponse();
}
})
});
return {
results: results,
page: response.page,
total_results: response.total_results,
total_pages: response.total_pages
}
};
}
/**
@@ -243,43 +273,21 @@ class TMDB {
* @param {Object} argument argument to function being called
* @returns {Promise} succeeds if callback succeeds
*/
tmdb(method, argument) {
return new Promise((resolve, reject) => {
const callback = (error, reponse) => {
if (error) {
return reject(error);
}
resolve(reponse);
};
tmdb(method, argument) {
return new Promise((resolve, reject) => {
const callback = (error, reponse) => {
if (error) {
return reject(error);
}
resolve(reponse);
};
if (!argument) {
this.tmdbLibrary[method](callback);
} else {
this.tmdbLibrary[method](argument, callback);
}
});
}
}
function tmdbErrorResponse(error, typeString=undefined) {
if (error.status === 404) {
let message = error.response.body.status_message;
throw {
status: 404,
message: message.slice(0, -1) + " in tmdb."
}
} else if (error.status === 401) {
throw {
status: 401,
message: error.response.body.status_message
}
}
throw {
status: 500,
message: `An unexpected error occured while fetching ${typeString} from tmdb`
if (!argument) {
this.tmdbLibrary[method](callback);
} else {
this.tmdbLibrary[method](argument, callback);
}
});
}
}

View File

@@ -1,7 +1,7 @@
import Movie from './types/movie.js'
import Show from './types/show.js'
import Person from './types/person.js'
import Credits from './types/credits.js'
import ReleaseDates from './types/releaseDates.js'
const Movie = require('./types/movie.js')
const Show = require('./types/show.js')
const Person = require('./types/person.js')
const Credits = require('./types/credits.js')
const ReleaseDates = require('./types/releaseDates.js')
module.exports = { Movie, Show, Person, Credits, ReleaseDates }

View File

@@ -1,20 +1,55 @@
class Credits {
constructor(id, cast=[], crew=[]) {
import Movie from "./movie";
import Show from "./show";
class Credits {
constructor(id, cast = [], crew = []) {
this.id = id;
this.cast = cast;
this.crew = crew;
this.type = 'credits';
this.type = "credits";
}
static convertFromTmdbResponse(response) {
const { id, cast, crew } = response;
const allCast = cast.map(cast =>
new CastMember(cast.character, cast.gender, cast.id, cast.name, cast.profile_path))
const allCrew = crew.map(crew =>
new CrewMember(crew.department, crew.gender, crew.id, crew.job, crew.name, crew.profile_path))
const allCast = cast.map(cast => {
if (cast["media_type"]) {
if (cast.media_type === "movie") {
return CreditedMovie.convertFromTmdbResponse(cast);
} else if (cast.media_type === "tv") {
return CreditedShow.convertFromTmdbResponse(cast);
}
}
return new Credits(id, allCast, allCrew)
return new CastMember(
cast.character,
cast.gender,
cast.id,
cast.name,
cast.profile_path
);
});
const allCrew = crew.map(crew => {
if (cast["media_type"]) {
if (cast.media_type === "movie") {
return CreditedMovie.convertFromTmdbResponse(cast);
} else if (cast.media_type === "tv") {
return CreditedShow.convertFromTmdbResponse(cast);
}
}
return new CrewMember(
crew.department,
crew.gender,
crew.id,
crew.job,
crew.name,
crew.profile_path
);
});
return new Credits(id, allCast, allCrew);
}
createJsonResponse() {
@@ -22,7 +57,7 @@ class Credits {
id: this.id,
cast: this.cast.map(cast => cast.createJsonResponse()),
crew: this.crew.map(crew => crew.createJsonResponse())
}
};
}
}
@@ -33,7 +68,7 @@ class CastMember {
this.id = id;
this.name = name;
this.profile_path = profile_path;
this.type = 'cast member';
this.type = "person";
}
createJsonResponse() {
@@ -44,7 +79,7 @@ class CastMember {
name: this.name,
profile_path: this.profile_path,
type: this.type
}
};
}
}
@@ -56,7 +91,7 @@ class CrewMember {
this.job = job;
this.name = name;
this.profile_path = profile_path;
this.type = 'crew member';
this.type = "person";
}
createJsonResponse() {
@@ -68,8 +103,11 @@ class CrewMember {
name: this.name,
profile_path: this.profile_path,
type: this.type
}
};
}
}
class CreditedMovie extends Movie {}
class CreditedShow extends Show {}
module.exports = Credits;

View File

@@ -1,23 +1,54 @@
class Person {
constructor(id, name, poster=undefined, birthday=undefined, deathday=undefined,
adult=undefined, knownForDepartment=undefined) {
class Person {
constructor(
id,
name,
poster = undefined,
birthday = undefined,
deathday = undefined,
adult = undefined,
placeOfBirth = undefined,
biography = undefined,
knownForDepartment = undefined
) {
this.id = id;
this.name = name;
this.poster = poster;
this.birthday = birthday;
this.deathday = deathday;
this.adult = adult;
this.placeOfBirth = placeOfBirth;
this.biography = biography;
this.knownForDepartment = knownForDepartment;
this.type = 'person';
this.type = "person";
}
static convertFromTmdbResponse(response) {
const { id, name, poster, birthday, deathday, adult, known_for_department } = response;
const {
id,
name,
profile_path,
birthday,
deathday,
adult,
place_of_birth,
biography,
known_for_department
} = response;
const birthDay = new Date(birthday)
const deathDay = deathday ? new Date(deathday) : null
const birthDay = new Date(birthday);
const deathDay = deathday ? new Date(deathday) : null;
return new Person(id, name, poster, birthDay, deathDay, adult, known_for_department)
return new Person(
id,
name,
profile_path,
birthDay,
deathDay,
adult,
place_of_birth,
biography,
known_for_department
);
}
createJsonResponse() {
@@ -27,10 +58,12 @@ class Person {
poster: this.poster,
birthday: this.birthday,
deathday: this.deathday,
place_of_birth: this.placeOfBirth,
biography: this.biography,
known_for_department: this.knownForDepartment,
adult: this.adult,
type: this.type
}
};
}
}

View File

@@ -1,26 +1,25 @@
const User = require('src/user/user');
const jwt = require('jsonwebtoken');
const User = require("src/user/user");
const jwt = require("jsonwebtoken");
class Token {
constructor(user, admin=false) {
constructor(user, admin = false, settings = null) {
this.user = user;
this.admin = admin;
this.settings = settings;
}
/**
* Generate a new token.
* @param {String} secret a cipher of the token
* @returns {String}
*/
* Generate a new token.
* @param {String} secret a cipher of the token
* @returns {String}
*/
toString(secret) {
const username = this.user.username;
const admin = this.admin;
let data = { username }
const { user, admin, settings } = this;
if (admin)
data = { ...data, admin }
let data = { username: user.username, settings };
if (admin) data["admin"] = admin;
return jwt.sign(data, secret, { expiresIn: '90d' });
return jwt.sign(data, secret, { expiresIn: "90d" });
}
/**
@@ -30,15 +29,12 @@ class Token {
* @returns {Token}
*/
static fromString(jwtToken, secret) {
let username = null;
const token = jwt.verify(jwtToken, secret, { clockTolerance: 10000 });
if (token.username == null) throw new Error("Malformed token");
const token = jwt.verify(jwtToken, secret, { clockTolerance: 10000 })
if (token.username === undefined || token.username === null)
throw new Error('Malformed token')
username = token.username
const user = new User(username)
return new Token(user)
const { username, admin, settings } = token;
const user = new User(username);
return new Token(user, admin, settings);
}
}

View File

@@ -1,219 +1,256 @@
const assert = require('assert');
const establishedDatabase = require('src/database/database');
const assert = require("assert");
const establishedDatabase = require("src/database/database");
class UserRepository {
constructor(database) {
this.database = database || establishedDatabase;
this.queries = {
read: 'select * from user where lower(user_name) = lower(?)',
create: 'insert into user (user_name) values (?)',
change: 'update user set password = ? where user_name = ?',
retrieveHash: 'select * from user where user_name = ?',
getAdminStateByUser: 'select admin from user where user_name = ?',
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 = ?'
read: "select * from user where lower(user_name) = lower(?)",
create: "insert into user (user_name) values (?)",
change: "update user set password = ? where user_name = ?",
retrieveHash: "select * from user where user_name = ?",
getAdminStateByUser: "select admin from user where user_name = ?",
link: "update settings set plex_userid = ? where user_name = ?",
unlink: "update settings set plex_userid = null where user_name = ?",
createSettings: "insert into settings (user_name) values (?)",
updateSettings:
"update settings set user_name = ?, dark_mode = ?, emoji = ?",
getSettings: "select * from settings where user_name = ?"
};
}
/**
* Create a user in a database.
* @param {User} user the user you want to create
* @returns {Promise}
*/
* Create a user in a database.
* @param {User} user the user you want to create
* @returns {Promise}
*/
create(user) {
return this.database.get(this.queries.read, user.username)
return this.database
.get(this.queries.read, user.username)
.then(() => this.database.run(this.queries.create, user.username))
.catch((error) => {
if (error.name === 'AssertionError' || error.message.endsWith('user_name')) {
throw new Error('That username is already registered');
.catch(error => {
if (
error.name === "AssertionError" ||
error.message.endsWith("user_name")
) {
throw new Error("That username is already registered");
}
throw Error(error)
throw Error(error);
});
}
/**
* Retrieve a password from a database.
* @param {User} user the user you want to retrieve the password
* @returns {Promise}
*/
* Retrieve a password from a database.
* @param {User} user the user you want to retrieve the password
* @returns {Promise}
*/
retrieveHash(user) {
return this.database.get(this.queries.retrieveHash, user.username)
return this.database
.get(this.queries.retrieveHash, user.username)
.then(row => {
assert(row, 'The user does not exist.');
assert(row, "The user does not exist.");
return row.password;
})
.catch(err => { console.log(error); throw new Error('Unable to find your user.'); })
.catch(err => {
console.log(error);
throw new Error("Unable to find your user.");
});
}
/**
* Change a user's password in a database.
* @param {User} user the user you want to create
* @param {String} password the new password you want to change
* @returns {Promise}
*/
* Change a user's password in a database.
* @param {User} user the user you want to create
* @param {String} password the new password you want to change
* @returns {Promise}
*/
changePassword(user, password) {
return this.database.run(this.queries.change, [password, user.username])
return this.database.run(this.queries.change, [password, user.username]);
}
/**
* Link plex userid with seasoned user
* @param {String} username the user you want to lunk plex userid with
* @param {Number} plexUserID plex unique id
* @returns {Promsie}
*/
* Link plex userid with seasoned user
* @param {String} username the user you want to lunk plex userid with
* @param {Number} plexUserID plex unique id
* @returns {Promsie}
*/
linkPlexUserId(username, plexUserID) {
return new Promise((resolve, reject) => {
this.database.run(this.queries.link, [plexUserID, username])
this.database
.run(this.queries.link, [plexUserID, username])
.then(row => resolve(row))
.catch(error => {
// TODO log this unknown db error
console.log('db error', error)
console.error("db error", error);
reject({
status: 500,
message: 'An unexpected error occured while linking plex and seasoned accounts',
source: 'seasoned database'
})
})
})
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}
*/
* 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)
this.database
.run(this.queries.unlink, username)
.then(row => resolve(row))
.catch(error => {
// TODO log this unknown db error
console.log('db error', error)
console.log("db error", error);
reject({
status: 500,
message: 'An unexpected error occured while unlinking plex and seasoned accounts',
source: 'seasoned database'
})
})
})
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}
*/
/**
* 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);
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}
*/
/**
* 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) => {
this.database
.get(this.queries.getSettings, username)
.then(async row => {
if (row == null) {
console.log(`settings do not exist for user: ${username}. Creating settings entry.`)
console.debug(
`settings do not exist for user: ${username}. Creating settings entry.`
);
const userExistsWithUsername = await this.database.get('select * from user where user_name is ?', username)
const userExistsWithUsername = await this.database.get(
"select * from user where user_name is ?",
username
);
if (userExistsWithUsername !== undefined) {
try {
resolve(this.dbCreateSettings(username))
resolve(this.dbCreateSettings(username));
} catch (error) {
reject(error)
reject(error);
}
} else {
reject({
status: 404,
message: 'User not found, no settings to get'
})
message: "User not found, no settings to get"
});
}
}
resolve(row)
resolve(row);
})
.catch(error => {
console.error('Unexpected error occured while fetching settings for your account. Error:', error)
console.error(
"Unexpected error occured while fetching settings for your account. Error:",
error
);
reject({
status: 500,
message: 'An unexpected error occured while fetching settings for your account',
source: 'seasoned database'
})
})
})
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
/**
* 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 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'
}
})
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}
*/
/**
* Helper function for creating settings in the database
* @param {String} username
* @returns {Promsie}
*/
dbCreateSettings(username) {
return this.database.run(this.queries.createSettings, username)
return this.database
.run(this.queries.createSettings, username)
.then(() => this.database.get(this.queries.getSettings, username))
.catch(error => rejectUnexpectedDatabaseError('Unexpected error occured while creating settings', 503, error))
.catch(error =>
rejectUnexpectedDatabaseError(
"Unexpected error occured while creating settings",
503,
error
)
);
}
/**
* Helper function for updating settings in the database
* @param {String} username
* @returns {Promsie}
*/
/**
* 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)))
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 rejectUnexpectedDatabaseError = (
message,
status,
error,
reject = null
) => {
console.error(error);
const body = {
status,
message,
source: 'seasoned database'
}
source: "seasoned database"
};
if (reject == null) {
return new Promise((resolve, reject) => reject(body))
return new Promise((resolve, reject) => reject(body));
}
reject(body)
}
reject(body);
};
module.exports = UserRepository;

View File

@@ -1,10 +1,10 @@
const bcrypt = require('bcrypt');
const UserRepository = require('src/user/userRepository');
const bcrypt = require("bcrypt");
const UserRepository = require("src/user/userRepository");
class UserSecurity {
constructor(database) {
this.userRepository = new UserRepository(database);
}
this.userRepository = new UserRepository(database);
}
/**
* Create a new user in PlanFlix.
@@ -13,15 +13,15 @@ class UserSecurity {
* @returns {Promise}
*/
createNewUser(user, clearPassword) {
if (user.username.trim() === '') {
throw new Error('The username is empty.');
} else if (clearPassword.trim() === '') {
throw new Error('The password is empty.');
if (user.username.trim() === "") {
throw new Error("The username is empty.");
} else if (clearPassword.trim() === "") {
throw new Error("The password is empty.");
} else {
return Promise.resolve()
.then(() => this.userRepository.create(user))
return this.userRepository
.create(user)
.then(() => UserSecurity.hashPassword(clearPassword))
.then(hash => this.userRepository.changePassword(user, hash))
.then(hash => this.userRepository.changePassword(user, hash));
}
}
@@ -32,24 +32,25 @@ class UserSecurity {
* @returns {Promise}
*/
login(user, clearPassword) {
return Promise.resolve()
.then(() => this.userRepository.retrieveHash(user))
return this.userRepository
.retrieveHash(user)
.then(hash => UserSecurity.compareHashes(hash, clearPassword))
.catch(() => { throw new Error('Incorrect username or password.'); });
.catch(() => {
throw new Error("Incorrect username or password.");
});
}
/**
* Compare between a password and a hash password from database.
* @param {String} hash the hash password from database
* @param {String} clearPassword the user's password
* @returns {Promise}
*/
/**
* Compare between a password and a hash password from database.
* @param {String} hash the hash password from database
* @param {String} clearPassword the user's password
* @returns {Promise}
*/
static compareHashes(hash, clearPassword) {
return new Promise((resolve, reject) => {
bcrypt.compare(clearPassword, hash, (error, match) => {
if (match)
resolve()
reject()
if (match) resolve(true);
reject(false);
});
});
}
@@ -60,9 +61,11 @@ class UserSecurity {
* @returns {Promise}
*/
static hashPassword(clearPassword) {
return new Promise((resolve) => {
return new Promise(resolve => {
const saltRounds = 10;
bcrypt.hash(clearPassword, saltRounds, (error, hash) => {
if (error) reject(error);
resolve(hash);
});
});

View File

@@ -1,146 +1,239 @@
const express = require('express');
const Raven = require('raven');
const bodyParser = require('body-parser');
const tokenToUser = require('./middleware/tokenToUser');
const mustBeAuthenticated = require('./middleware/mustBeAuthenticated');
const mustBeAdmin = require('./middleware/mustBeAdmin');
const mustHaveAccountLinkedToPlex = require('./middleware/mustHaveAccountLinkedToPlex');
const configuration = require('src/config/configuration').getInstance();
const express = require("express");
const Raven = require("raven");
const cookieParser = require("cookie-parser");
const bodyParser = require("body-parser");
const listController = require('./controllers/list/listController');
const tautulli = require('./controllers/user/viewHistory.js');
const SettingsController = require('./controllers/user/settings');
const AuthenticatePlexAccountController = require('./controllers/user/AuthenticatePlexAccount');
const configuration = require("src/config/configuration").getInstance();
const reqTokenToUser = require("./middleware/reqTokenToUser");
const mustBeAuthenticated = require("./middleware/mustBeAuthenticated");
const mustBeAdmin = require("./middleware/mustBeAdmin");
const mustHaveAccountLinkedToPlex = require("./middleware/mustHaveAccountLinkedToPlex");
const listController = require("./controllers/list/listController");
const tautulli = require("./controllers/user/viewHistory.js");
const SettingsController = require("./controllers/user/settings");
const AuthenticatePlexAccountController = require("./controllers/user/authenticatePlexAccount");
// TODO: Have our raven router check if there is a value, if not don't enable raven.
Raven.config(configuration.get('raven', 'DSN')).install();
Raven.config(configuration.get("raven", "DSN")).install();
const app = express(); // define our app using express
app.use(Raven.requestHandler());
app.use(bodyParser.json());
app.use(cookieParser());
const router = express.Router();
const allowedOrigins = ['https://kevinmidboe.com', 'http://localhost:8080'];
const allowedOrigins = configuration.get("webserver", "origins");
// TODO: All JSON handling in a single router
// router.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
/* Decode the Authorization header if provided */
router.use(tokenToUser);
/* Check header and cookie for authentication and set req.loggedInUser */
router.use(reqTokenToUser);
// TODO: Should have a separate middleware/router for handling headers.
router.use((req, res, next) => {
// TODO add logging of all incoming
const origin = req.headers.origin;
if (allowedOrigins.indexOf(origin) > -1) {
res.setHeader('Access-Control-Allow-Origin', origin);
}
res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization, loggedinuser');
res.header('Access-Control-Allow-Methods', 'POST, GET, PUT');
// TODO add logging of all incoming
// const origin = req.headers.origin;
// if (allowedOrigins.indexOf(origin) > -1) {
// res.setHeader("Access-Control-Allow-Origin", origin);
// }
next();
res.header(
"Access-Control-Allow-Headers",
"Content-Type, Authorization, loggedinuser, set-cookie"
);
res.header("Access-Control-Allow-Credentials", "true");
res.header("Access-Control-Allow-Methods", "POST, GET, PUT, OPTIONS");
next();
});
router.get('/', function mainHandler(req, res) {
throw new Error('Broke!');
router.get("/", (req, res) => {
res.send("welcome to seasoned api");
});
app.use(Raven.errorHandler());
app.use(function onError(err, req, res, next) {
res.statusCode = 500;
res.end(res.sentry + '\n');
app.use((err, req, res, next) => {
res.statusCode = 500;
res.end(res.sentry + "\n");
});
/**
* User
*/
router.post('/v1/user', require('./controllers/user/register.js'));
router.post('/v1/user/login', require('./controllers/user/login.js'));
router.get('/v1/user/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.post("/v1/user", require("./controllers/user/register.js"));
router.post("/v1/user/login", require("./controllers/user/login.js"));
router.post("/v1/user/logout", require("./controllers/user/logout.js"));
router.get(
"/v1/user/settings",
mustBeAuthenticated,
SettingsController.getSettingsController
);
router.put(
"/v1/user/settings",
mustBeAuthenticated,
SettingsController.updateSettingsController
);
router.get(
"/v1/user/search_history",
mustBeAuthenticated,
require("./controllers/user/searchHistory.js")
);
router.get(
"/v1/user/requests",
mustBeAuthenticated,
require("./controllers/user/requests.js")
);
router.post(
"/v1/user/link_plex",
mustBeAuthenticated,
AuthenticatePlexAccountController.link
);
router.post(
"/v1/user/unlink_plex",
mustBeAuthenticated,
AuthenticatePlexAccountController.unlink
);
router.get('/v1/user/view_history', mustHaveAccountLinkedToPlex, tautulli.userViewHistoryController);
router.get('/v1/user/watch_time', mustHaveAccountLinkedToPlex, tautulli.watchTimeStatsController);
router.get('/v1/user/plays_by_day', mustHaveAccountLinkedToPlex, tautulli.getPlaysByDaysController);
router.get('/v1/user/plays_by_dayofweek', mustHaveAccountLinkedToPlex, tautulli.getPlaysByDayOfWeekController);
router.get(
"/v1/user/view_history",
mustHaveAccountLinkedToPlex,
tautulli.userViewHistoryController
);
router.get(
"/v1/user/watch_time",
mustHaveAccountLinkedToPlex,
tautulli.watchTimeStatsController
);
router.get(
"/v1/user/plays_by_day",
mustHaveAccountLinkedToPlex,
tautulli.getPlaysByDaysController
);
router.get(
"/v1/user/plays_by_dayofweek",
mustHaveAccountLinkedToPlex,
tautulli.getPlaysByDayOfWeekController
);
/**
* Seasoned
*/
router.get('/v1/seasoned/all', require('./controllers/seasoned/readStrays.js'));
router.get('/v1/seasoned/:strayId', require('./controllers/seasoned/strayById.js'));
router.post('/v1/seasoned/verify/:strayId', require('./controllers/seasoned/verifyStray.js'));
router.get("/v1/seasoned/all", require("./controllers/seasoned/readStrays.js"));
router.get(
"/v1/seasoned/:strayId",
require("./controllers/seasoned/strayById.js")
);
router.post(
"/v1/seasoned/verify/:strayId",
require("./controllers/seasoned/verifyStray.js")
);
router.get('/v2/search/', require('./controllers/search/multiSearch.js'));
router.get('/v2/search/movie', require('./controllers/search/movieSearch.js'));
router.get('/v2/search/show', require('./controllers/search/showSearch.js'));
router.get('/v2/search/person', require('./controllers/search/personSearch.js'));
router.get("/v2/search/", require("./controllers/search/multiSearch.js"));
router.get("/v2/search/movie", require("./controllers/search/movieSearch.js"));
router.get("/v2/search/show", require("./controllers/search/showSearch.js"));
router.get(
"/v2/search/person",
require("./controllers/search/personSearch.js")
);
router.get('/v2/movie/now_playing', listController.nowPlayingMovies);
router.get('/v2/movie/popular', listController.popularMovies);
router.get('/v2/movie/top_rated', listController.topRatedMovies);
router.get('/v2/movie/upcoming', listController.upcomingMovies);
router.get("/v2/movie/now_playing", listController.nowPlayingMovies);
router.get("/v2/movie/popular", listController.popularMovies);
router.get("/v2/movie/top_rated", listController.topRatedMovies);
router.get("/v2/movie/upcoming", listController.upcomingMovies);
router.get("/v2/movie/:id/credits", require("./controllers/movie/credits.js"));
router.get(
"/v2/movie/:id/release_dates",
require("./controllers/movie/releaseDates.js")
);
router.get("/v2/movie/:id", require("./controllers/movie/info.js"));
router.get('/v2/show/now_playing', listController.nowPlayingShows);
router.get('/v2/show/popular', listController.popularShows);
router.get('/v2/show/top_rated', listController.topRatedShows);
router.get("/v2/show/now_playing", listController.nowPlayingShows);
router.get("/v2/show/popular", listController.popularShows);
router.get("/v2/show/top_rated", listController.topRatedShows);
router.get("/v2/show/:id/credits", require("./controllers/show/credits.js"));
router.get("/v2/show/:id", require("./controllers/show/info.js"));
router.get('/v2/movie/:id/credits', require('./controllers/movie/credits.js'));
router.get('/v2/movie/:id/release_dates', require('./controllers/movie/releaseDates.js'));
router.get('/v2/show/:id/credits', require('./controllers/show/credits.js'));
router.get('/v2/movie/:id', require('./controllers/movie/info.js'));
router.get('/v2/show/:id', require('./controllers/show/info.js'));
router.get('/v2/person/:id', require('./controllers/person/info.js'));
router.get(
"/v2/person/:id/credits",
require("./controllers/person/credits.js")
);
router.get("/v2/person/:id", require("./controllers/person/info.js"));
/**
* Plex
*/
router.get('/v2/plex/search', require('./controllers/plex/search'));
router.get("/v2/plex/search", require("./controllers/plex/search"));
/**
* List
*/
router.get('/v1/plex/search', require('./controllers/plex/searchMedia.js'));
router.get('/v1/plex/playing', require('./controllers/plex/plexPlaying.js'));
router.get('/v1/plex/request', require('./controllers/plex/searchRequest.js'));
router.get('/v1/plex/request/:mediaId', require('./controllers/plex/readRequest.js'));
router.post('/v1/plex/request/:mediaId', require('./controllers/plex/submitRequest.js'));
router.post('/v1/plex/hook', require('./controllers/plex/hookDump.js'));
router.get("/v1/plex/search", require("./controllers/plex/searchMedia.js"));
router.get("/v1/plex/playing", require("./controllers/plex/plexPlaying.js"));
router.get("/v1/plex/request", require("./controllers/plex/searchRequest.js"));
router.get(
"/v1/plex/request/:mediaId",
require("./controllers/plex/readRequest.js")
);
router.post(
"/v1/plex/request/:mediaId",
require("./controllers/plex/submitRequest.js")
);
router.post("/v1/plex/hook", require("./controllers/plex/hookDump.js"));
router.get(
"/v1/plex/watch-link",
mustBeAuthenticated,
require("./controllers/plex/watchDirectLink.js")
);
/**
* Requests
*/
router.get('/v2/request', require('./controllers/request/fetchAllRequests.js'));
router.get('/v2/request/:id', require('./controllers/request/getRequest.js'));
router.post('/v2/request', require('./controllers/request/requestTmdbId.js'));
router.get('/v1/plex/requests/all', require('./controllers/plex/fetchRequested.js'));
router.put('/v1/plex/request/:requestId', mustBeAuthenticated, require('./controllers/plex/updateRequested.js'));
router.get("/v2/request", require("./controllers/request/fetchAllRequests.js"));
router.get("/v2/request/:id", require("./controllers/request/getRequest.js"));
router.post("/v2/request", require("./controllers/request/requestTmdbId.js"));
router.get(
"/v1/plex/requests/all",
require("./controllers/plex/fetchRequested.js")
);
router.put(
"/v1/plex/request/:requestId",
mustBeAuthenticated,
require("./controllers/plex/updateRequested.js")
);
/**
* Pirate
*/
router.get('/v1/pirate/search', mustBeAuthenticated, require('./controllers/pirate/searchTheBay.js'));
router.post('/v1/pirate/add', mustBeAuthenticated, require('./controllers/pirate/addMagnet.js'));
router.get(
"/v1/pirate/search",
mustBeAuthenticated,
require("./controllers/pirate/searchTheBay.js")
);
router.post(
"/v1/pirate/add",
mustBeAuthenticated,
require("./controllers/pirate/addMagnet.js")
);
/**
* git
*/
router.post('/v1/git/dump', require('./controllers/git/dumpHook.js'));
router.post("/v1/git/dump", require("./controllers/git/dumpHook.js"));
/**
* misc
*/
router.get('/v1/emoji', require('./controllers/misc/emoji.js'));
router.get("/v1/emoji", require("./controllers/misc/emoji.js"));
// REGISTER OUR ROUTES -------------------------------
// all of our routes will be prefixed with /api
app.use('/api', router);
app.use("/api", router);
module.exports = app;

View File

@@ -1,8 +1,6 @@
const configuration = require('src/config/configuration').getInstance();
const Cache = require('src/tmdb/cache');
const TMDB = require('src/tmdb/tmdb');
const cache = new Cache();
const tmdb = new TMDB(cache, configuration.get('tmdb', 'apiKey'));
const tmdb = new TMDB(configuration.get('tmdb', 'apiKey'));
// there should be a translate function from query params to
// tmdb list that is valid. Should it be a helper function or does it

View File

@@ -1,9 +1,7 @@
const configuration = require('src/config/configuration').getInstance();
const Cache = require('src/tmdb/cache');
const TMDB = require('src/tmdb/tmdb');
const cache = new Cache();
const tmdb = new TMDB(cache, configuration.get('tmdb', 'apiKey'));
const tmdb = new TMDB(configuration.get('tmdb', 'apiKey'));
const movieCreditsController = (req, res) => {
const movieId = req.params.id;
@@ -23,4 +21,4 @@ const movieCreditsController = (req, res) => {
})
}
module.exports = movieCreditsController;
module.exports = movieCreditsController;

View File

@@ -1,19 +1,19 @@
const configuration = require('src/config/configuration').getInstance();
const Cache = require('src/tmdb/cache');
const TMDB = require('src/tmdb/tmdb');
const Plex = require('src/plex/plex');
const cache = new Cache();
const tmdb = new TMDB(cache, configuration.get('tmdb', 'apiKey'));
const plex = new Plex(configuration.get('plex', 'ip'));
const configuration = require("src/config/configuration").getInstance();
const TMDB = require("src/tmdb/tmdb");
const Plex = require("src/plex/plex");
const tmdb = new TMDB(configuration.get("tmdb", "apiKey"));
const plex = new Plex(configuration.get("plex", "ip"));
function handleError(error, res) {
const { status, message } = error;
if (status && message) {
res.status(status).send({ success: false, message })
res.status(status).send({ success: false, message });
} else {
console.log('caught movieinfo controller error', error)
res.status(500).send({ message: 'An unexpected error occured while requesting movie info'})
console.log("caught movieinfo controller error", error);
res.status(500).send({
message: "An unexpected error occured while requesting movie info"
});
}
}
@@ -27,31 +27,44 @@ async function movieInfoController(req, res) {
const movieId = req.params.id;
let { credits, release_dates, check_existance } = req.query;
credits && credits.toLowerCase() === 'true' ? credits = true : credits = false
release_dates && release_dates.toLowerCase() === 'true' ? release_dates = true : release_dates = false
check_existance && check_existance.toLowerCase() === 'true' ? check_existance = true : check_existance = false
credits && credits.toLowerCase() === "true"
? (credits = true)
: (credits = false);
release_dates && release_dates.toLowerCase() === "true"
? (release_dates = true)
: (release_dates = false);
check_existance && check_existance.toLowerCase() === "true"
? (check_existance = true)
: (check_existance = false);
let tmdbQueue = [tmdb.movieInfo(movieId)]
if (credits)
tmdbQueue.push(tmdb.movieCredits(movieId))
if (release_dates)
tmdbQueue.push(tmdb.movieReleaseDates(movieId))
let tmdbQueue = [tmdb.movieInfo(movieId)];
if (credits) tmdbQueue.push(tmdb.movieCredits(movieId));
if (release_dates) tmdbQueue.push(tmdb.movieReleaseDates(movieId));
try {
const [ Movie, Credits, ReleaseDates ] = await Promise.all(tmdbQueue)
const [Movie, Credits, ReleaseDates] = await Promise.all(tmdbQueue);
const movie = Movie.createJsonResponse()
if (Credits)
movie.credits = Credits.createJsonResponse()
const movie = Movie.createJsonResponse();
if (Credits) movie.credits = Credits.createJsonResponse();
if (ReleaseDates)
movie.release_dates = ReleaseDates.createJsonResponse().results
movie.release_dates = ReleaseDates.createJsonResponse().results;
if (check_existance)
movie.exists_in_plex = await plex.existsInPlex(movie)
res.send(movie)
} catch(error) {
handleError(error, res)
if (check_existance) {
try {
movie.exists_in_plex = await plex.existsInPlex(movie);
} catch (error) {
if (error.status === 401) {
console.log("Unathorized request, check plex server LAN settings");
} else {
console.log("Unkown error from plex!");
}
console.log(error);
}
}
res.send(movie);
} catch (error) {
handleError(error, res);
}
}

View File

@@ -1,9 +1,7 @@
const configuration = require('src/config/configuration').getInstance();
const Cache = require('src/tmdb/cache');
const TMDB = require('src/tmdb/tmdb');
const cache = new Cache();
const tmdb = new TMDB(cache, configuration.get('tmdb', 'apiKey'));
const tmdb = new TMDB(configuration.get('tmdb', 'apiKey'));
const movieReleaseDatesController = (req, res) => {
const movieId = req.params.id;
@@ -23,4 +21,4 @@ const movieReleaseDatesController = (req, res) => {
})
}
module.exports = movieReleaseDatesController;
module.exports = movieReleaseDatesController;

View File

@@ -0,0 +1,26 @@
const configuration = require("src/config/configuration").getInstance();
const TMDB = require("src/tmdb/tmdb");
const tmdb = new TMDB(configuration.get("tmdb", "apiKey"));
const personCreditsController = (req, res) => {
const personId = req.params.id;
return tmdb
.personCredits(personId)
.then(credits => res.send(credits))
.catch(error => {
const { status, message } = error;
if (status && message) {
res.status(status).send({ success: false, message });
} else {
// TODO log unhandled errors
console.log("caugth show credits controller error", error);
res.status(500).send({
message: "An unexpected error occured while requesting person credits"
});
}
});
};
module.exports = personCreditsController;

View File

@@ -1,25 +1,49 @@
const configuration = require('src/config/configuration').getInstance();
const Cache = require('src/tmdb/cache');
const TMDB = require('src/tmdb/tmdb');
const cache = new Cache();
const tmdb = new TMDB(cache, configuration.get('tmdb', 'apiKey'));
const configuration = require("src/config/configuration").getInstance();
const TMDB = require("src/tmdb/tmdb");
const tmdb = new TMDB(configuration.get("tmdb", "apiKey"));
function handleError(error, res) {
const { status, message } = error;
if (status && message) {
res.status(status).send({ success: false, message });
} else {
console.log("caught personinfo controller error", error);
res.status(500).send({
message: "An unexpected error occured while requesting person info."
});
}
}
/**
* Controller: Retrieve information for a person
* Controller: Retrieve information for a person
* @param {Request} req http request variable
* @param {Response} res
* @returns {Callback}
*/
function personInfoController(req, res) {
async function personInfoController(req, res) {
const personId = req.params.id;
let { credits } = req.query;
arguments;
credits && credits.toLowerCase() === "true"
? (credits = true)
: (credits = false);
tmdb.personInfo(personId)
.then(person => res.send(person.createJsonResponse()))
.catch(error => {
res.status(404).send({ success: false, message: error.message });
});
let tmdbQueue = [tmdb.personInfo(personId)];
if (credits) tmdbQueue.push(tmdb.personCredits(personId));
try {
const [Person, Credits] = await Promise.all(tmdbQueue);
const person = Person.createJsonResponse();
if (credits) person.credits = Credits.createJsonResponse();
return res.send(person);
} catch (error) {
handleError(error, res);
}
}
module.exports = personInfoController;

View File

@@ -1,11 +1,11 @@
/*
* @Author: KevinMidboe
* @Date: 2017-10-21 09:54:31
* @Last Modified by: KevinMidboe
* @Last Modified time: 2018-02-26 19:56:32
*/
* @Author: KevinMidboe
* @Date: 2017-10-21 09:54:31
* @Last Modified by: KevinMidboe
* @Last Modified time: 2018-02-26 19:56:32
*/
const PirateRepository = require('src/pirate/pirateRepository');
const PirateRepository = require("src/pirate/pirateRepository");
// const pirateRepository = new PirateRepository();
/**
@@ -15,15 +15,15 @@ const PirateRepository = require('src/pirate/pirateRepository');
* @returns {Callback}
*/
function updateRequested(req, res) {
const { query, page, type } = req.query;
const { query, page, type } = req.query;
PirateRepository.SearchPiratebay(query, page, type)
.then((result) => {
res.send({ success: true, results: result });
})
.catch(error => {
res.status(401).send({ success: false, message: error.message });
});
PirateRepository.SearchPiratebay(query, page, type)
.then(result => {
res.send({ success: true, results: result });
})
.catch(error => {
res.status(401).send({ success: false, message: error.message });
});
}
module.exports = updateRequested;

View File

@@ -1,26 +1,24 @@
const SearchHistory = require('src/searchHistory/searchHistory');
const Cache = require('src/tmdb/cache');
const RequestRepository = require('src/plex/requestRepository.js');
const SearchHistory = require("src/searchHistory/searchHistory");
const Cache = require("src/tmdb/cache");
const RequestRepository = require("src/plex/requestRepository.js");
const cache = new Cache();
const requestRepository = new RequestRepository(cache);
const searchHistory = new SearchHistory();
function searchRequestController(req, res) {
const user = req.loggedInUser;
const { query, page, type } = req.query;
const username = user === undefined ? undefined : user.username;
const { query, page, type } = req.query;
const username = req.loggedInUser ? req.loggedInUser.username : null;
Promise.resolve()
.then(() => searchHistory.create(username, query))
.then(() => requestRepository.search(query, page, type))
.then((searchResult) => {
res.send(searchResult);
})
.catch(error => {
res.status(500).send({ success: false, message: error.message });
});
Promise.resolve()
.then(() => searchHistory.create(username, query))
.then(() => requestRepository.search(query, page, type))
.then(searchResult => {
res.send(searchResult);
})
.catch(error => {
res.status(500).send({ success: false, message: error.message });
});
}
module.exports = searchRequestController;

View File

@@ -1,19 +1,16 @@
const configuration = require('src/config/configuration').getInstance()
const RequestRepository = require('src/request/request');
const Cache = require('src/tmdb/cache')
const TMDB = require('src/tmdb/tmdb')
const configuration = require("src/config/configuration").getInstance();
const RequestRepository = require("src/request/request");
const TMDB = require("src/tmdb/tmdb");
const tmdb = new TMDB(configuration.get("tmdb", "apiKey"));
const request = new RequestRepository();
const cache = new Cache()
const tmdb = new TMDB(cache, configuration.get('tmdb', 'apiKey'))
const request = new RequestRepository()
const tmdbMovieInfo = id => {
return tmdb.movieInfo(id);
};
const tmdbMovieInfo = (id) => {
return tmdb.movieInfo(id)
}
const tmdbShowInfo = (id) => {
return tmdb.showInfo(id)
}
const tmdbShowInfo = id => {
return tmdb.showInfo(id);
};
/**
* Controller: POST a media id to be donwloaded
@@ -24,28 +21,43 @@ const tmdbShowInfo = (id) => {
function submitRequestController(req, res) {
// This is the id that is the param of the url
const id = req.params.mediaId;
const type = req.query.type ? req.query.type.toLowerCase() : undefined
const ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress;
const user_agent = req.headers['user-agent'];
const user = req.loggedInUser;
let mediaFunction = undefined
const type = req.query.type ? req.query.type.toLowerCase() : undefined;
const ip = req.headers["x-forwarded-for"] || req.connection.remoteAddress;
const user_agent = req.headers["user-agent"];
const username = req.loggedInUser ? req.loggedInUser.username : null;
if (type === 'movie') {
console.log('movie')
mediaFunction = tmdbMovieInfo
} else if (type === 'show') {
console.log('show')
mediaFunction = tmdbShowInfo
let mediaFunction = undefined;
if (type === "movie") {
console.log("movie");
mediaFunction = tmdbMovieInfo;
} else if (type === "show") {
console.log("show");
mediaFunction = tmdbShowInfo;
} else {
res.status(422).send({ success: false, message: 'Incorrect type. Allowed types: "movie" or "show"'})
res
.status(422)
.send({
success: false,
message: 'Incorrect type. Allowed types: "movie" or "show"'
});
}
if (mediaFunction === undefined) { res.status(200); return }
if (mediaFunction === undefined) {
res.status(200);
return;
}
mediaFunction(id)
.then(tmdbMedia => request.requestFromTmdb(tmdbMedia, ip, user_agent, user))
.then(() => res.send({ success: true, message: 'Media item successfully requested' }))
.catch(err => res.status(500).send({ success: false, message: err.message }))
.then(tmdbMedia =>
request.requestFromTmdb(tmdbMedia, ip, user_agent, username)
)
.then(() =>
res.send({ success: true, message: "Media item successfully requested" })
)
.catch(err =>
res.status(500).send({ success: false, message: err.message })
);
}
module.exports = submitRequestController;

View File

@@ -0,0 +1,27 @@
const configuration = require('src/config/configuration').getInstance();
const Plex = require('src/plex/plex');
const plex = new Plex(configuration.get('plex', 'ip'));
/**
* Controller: Search plex for movies, shows and episodes by query
* @param {Request} req http request variable
* @param {Response} res
* @returns {Callback}
*/
function watchDirectLink (req, res) {
const { title, year } = req.query;
plex.getDirectLinkByTitleAndYear(title, year)
.then(plexDirectLink => {
if (plexDirectLink == false)
res.status(404).send({ success: true, link: null })
else
res.status(200).send({ success: true, link: plexDirectLink })
})
.catch(error => {
res.status(500).send({ success: false, message: error.message });
});
}
module.exports = watchDirectLink;

View File

@@ -1,18 +1,17 @@
const configuration = require('src/config/configuration').getInstance();
const Cache = require('src/tmdb/cache');
const TMDB = require('src/tmdb/tmdb');
const RequestRepository = require('src/request/request');
const cache = new Cache();
const tmdb = new TMDB(cache, configuration.get('tmdb', 'apiKey'));
const configuration = require("src/config/configuration").getInstance();
const TMDB = require("src/tmdb/tmdb");
const RequestRepository = require("src/request/request");
const tmdb = new TMDB(configuration.get("tmdb", "apiKey"));
const request = new RequestRepository();
// const { sendSMS } = require("src/notifications/sms");
const tmdbMovieInfo = (id) => {
return tmdb.movieInfo(id)
}
const tmdbMovieInfo = id => {
return tmdb.movieInfo(id);
};
const tmdbShowInfo = (id) => {
return tmdb.showInfo(id)
}
const tmdbShowInfo = id => {
return tmdb.showInfo(id);
};
/**
* Controller: Request by id with type param
@@ -21,33 +20,48 @@ const tmdbShowInfo = (id) => {
* @returns {Callback}
*/
function requestTmdbIdController(req, res) {
const { id, type } = req.body
const { id, type } = req.body;
const ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress;
const user_agent = req.headers['user-agent'];
const user = req.loggedInUser;
const ip = req.headers["x-forwarded-for"] || req.connection.remoteAddress;
const user_agent = req.headers["user-agent"];
const username = req.loggedInUser ? req.loggedInUser.username : null;
let mediaFunction = undefined
let mediaFunction = undefined;
if (id === undefined || type === undefined) {
res.status(422).send({ success: false, message: "'Missing parameteres: 'id' and/or 'type'"})
res.status(422).send({
success: false,
message: "'Missing parameteres: 'id' and/or 'type'"
});
}
if (type === 'movie') {
mediaFunction = tmdbMovieInfo
} else if (type === 'show') {
mediaFunction = tmdbShowInfo
if (type === "movie") {
mediaFunction = tmdbMovieInfo;
} else if (type === "show") {
mediaFunction = tmdbShowInfo;
} else {
res.status(422).send({ success: false, message: 'Incorrect type. Allowed types: "movie" or "show"'})
res.status(422).send({
success: false,
message: 'Incorrect type. Allowed types: "movie" or "show"'
});
}
mediaFunction(id)
// .catch((error) => { console.error(error); res.status(404).send({ success: false, error: 'Id not found' }) })
.then(tmdbMedia => request.requestFromTmdb(tmdbMedia, ip, user_agent, user))
.then(() => res.send({success: true, message: 'Request has been submitted.'}))
.then(tmdbMedia => {
request.requestFromTmdb(tmdbMedia, ip, user_agent, username);
// TODO enable SMS
// const url = `https://request.movie?${tmdbMedia.type}=${tmdbMedia.id}`;
// const message = `${tmdbMedia.title} (${tmdbMedia.year}) requested!\n${url}`;
// sendSMS(message);
})
.then(() =>
res.send({ success: true, message: "Request has been submitted." })
)
.catch(error => {
res.send({ success: false, message: error.message });
})
});
}
module.exports = requestTmdbIdController;

View File

@@ -1,9 +1,7 @@
const configuration = require('src/config/configuration').getInstance();
const Cache = require('src/tmdb/cache');
const TMDB = require('src/tmdb/tmdb');
const SearchHistory = require('src/searchHistory/searchHistory');
const cache = new Cache();
const tmdb = new TMDB(cache, configuration.get('tmdb', 'apiKey'));
const configuration = require("src/config/configuration").getInstance();
const TMDB = require("src/tmdb/tmdb");
const SearchHistory = require("src/searchHistory/searchHistory");
const tmdb = new TMDB(configuration.get("tmdb", "apiKey"));
const searchHistory = new SearchHistory();
/**
@@ -13,28 +11,30 @@ const searchHistory = new SearchHistory();
* @returns {Callback}
*/
function movieSearchController(req, res) {
const user = req.loggedInUser;
const { query, page } = req.query;
const { query, page, adult } = req.query;
const username = req.loggedInUser ? req.loggedInUser.username : null;
const includeAdult = adult == "true" ? true : false;
if (user) {
return searchHistory.create(user, query);
if (username) {
searchHistory.create(username, query);
}
tmdb.movieSearch(query, page)
return tmdb
.movieSearch(query, page, includeAdult)
.then(movieSearchResults => res.send(movieSearchResults))
.catch(error => {
const { status, message } = error;
if (status && message) {
res.status(status).send({ success: false, message })
res.status(status).send({ success: false, message });
} else {
// TODO log unhandled errors
console.log('caugth movie search controller error', error)
console.log("caugth movie search controller error", error);
res.status(500).send({
message: `An unexpected error occured while searching movies with query: ${query}`
})
});
}
})
});
}
module.exports = movieSearchController;

View File

@@ -1,16 +1,14 @@
const configuration = require('src/config/configuration').getInstance();
const Cache = require('src/tmdb/cache');
const TMDB = require('src/tmdb/tmdb');
const SearchHistory = require('src/searchHistory/searchHistory');
const cache = new Cache();
const tmdb = new TMDB(cache, configuration.get('tmdb', 'apiKey'));
const configuration = require("src/config/configuration").getInstance();
const TMDB = require("src/tmdb/tmdb");
const SearchHistory = require("src/searchHistory/searchHistory");
const tmdb = new TMDB(configuration.get("tmdb", "apiKey"));
const searchHistory = new SearchHistory();
function checkAndCreateJsonResponse(result) {
if (typeof result['createJsonResponse'] === 'function') {
return result.createJsonResponse()
if (typeof result["createJsonResponse"] === "function") {
return result.createJsonResponse();
}
return result
return result;
}
/**
@@ -20,26 +18,31 @@ function checkAndCreateJsonResponse(result) {
* @returns {Callback}
*/
function multiSearchController(req, res) {
const user = req.loggedInUser;
const { query, page } = req.query;
const { query, page, adult } = req.query;
const username = req.loggedInUser ? req.loggedInUser.username : null;
if (user) {
searchHistory.create(user.username, query)
if (username) {
searchHistory.create(username, query);
}
return tmdb.multiSearch(query, page)
.then(multiSearchResults => res.send(multiSearchResults))
.catch(error => {
const { status, message } = error;
return tmdb
.multiSearch(query, page, adult)
.then(multiSearchResults => res.send(multiSearchResults))
.catch(error => {
const { status, message } = error;
if (status && message) {
res.status(status).send({ success: false, message })
} else {
// TODO log unhandled errors
console.log('caugth multi search controller error', error)
res.status(500).send({ message: `An unexpected error occured while searching with query: ${query}` })
}
})
if (status && message) {
res.status(status).send({ success: false, message });
} else {
// TODO log unhandled errors
console.log("caugth multi search controller error", error);
res
.status(500)
.send({
message: `An unexpected error occured while searching with query: ${query}`
});
}
});
}
module.exports = multiSearchController;

View File

@@ -1,9 +1,7 @@
const configuration = require('src/config/configuration').getInstance();
const Cache = require('src/tmdb/cache');
const TMDB = require('src/tmdb/tmdb');
const SearchHistory = require('src/searchHistory/searchHistory');
const cache = new Cache();
const tmdb = new TMDB(cache, configuration.get('tmdb', 'apiKey'));
const configuration = require("src/config/configuration").getInstance();
const TMDB = require("src/tmdb/tmdb");
const SearchHistory = require("src/searchHistory/searchHistory");
const tmdb = new TMDB(configuration.get("tmdb", "apiKey"));
const searchHistory = new SearchHistory();
/**
@@ -13,30 +11,30 @@ const searchHistory = new SearchHistory();
* @returns {Callback}
*/
function personSearchController(req, res) {
const user = req.loggedInUser;
const { query, page } = req.query;
const { query, page, adult } = req.query;
const username = req.loggedInUser ? req.loggedInUser.username : null;
const includeAdult = adult == "true" ? true : false;
if (user) {
return searchHistory.create(user, query);
if (username) {
searchHistory.create(username, query);
}
tmdb.personSearch(query, page)
.then((person) => {
res.send(person);
})
return tmdb
.personSearch(query, page, includeAdult)
.then(persons => res.send(persons))
.catch(error => {
const { status, message } = error;
if (status && message) {
res.status(status).send({ success: false, message })
res.status(status).send({ success: false, message });
} else {
// TODO log unhandled errors
console.log('caugth person search controller error', error)
console.log("caugth person search controller error", error);
res.status(500).send({
message: `An unexpected error occured while searching people with query: ${query}`
})
});
}
})
});
}
module.exports = personSearchController;

View File

@@ -1,9 +1,7 @@
const SearchHistory = require('src/searchHistory/searchHistory');
const configuration = require('src/config/configuration').getInstance();
const Cache = require('src/tmdb/cache');
const TMDB = require('src/tmdb/tmdb');
const cache = new Cache();
const tmdb = new TMDB(cache, configuration.get('tmdb', 'apiKey'));
const SearchHistory = require("src/searchHistory/searchHistory");
const configuration = require("src/config/configuration").getInstance();
const TMDB = require("src/tmdb/tmdb");
const tmdb = new TMDB(configuration.get("tmdb", "apiKey"));
const searchHistory = new SearchHistory();
/**
@@ -13,23 +11,22 @@ const searchHistory = new SearchHistory();
* @returns {Callback}
*/
function showSearchController(req, res) {
const user = req.loggedInUser;
const { query, page } = req.query;
const { query, page, adult } = req.query;
const username = req.loggedInUser ? req.loggedInUser.username : null;
const includeAdult = adult == "true" ? true : false;
Promise.resolve()
.then(() => {
if (user) {
return searchHistory.create(user, query);
}
return null
})
.then(() => tmdb.showSearch(query, page))
.then((shows) => {
res.send(shows);
})
.catch(error => {
res.status(500).send({ success: false, message: error.message });
});
if (username) {
searchHistory.create(username, query);
}
return tmdb
.showSearch(query, page, includeAdult)
.then(shows => {
res.send(shows);
})
.catch(error => {
res.status(500).send({ success: false, message: error.message });
});
}
module.exports = showSearchController;

View File

@@ -1,9 +1,6 @@
const configuration = require('src/config/configuration').getInstance();
const Cache = require('src/tmdb/cache');
const TMDB = require('src/tmdb/tmdb');
const cache = new Cache();
const tmdb = new TMDB(cache, configuration.get('tmdb', 'apiKey'));
const tmdb = new TMDB(configuration.get('tmdb', 'apiKey'));
const showCreditsController = (req, res) => {
const showId = req.params.id;
@@ -23,4 +20,4 @@ const showCreditsController = (req, res) => {
})
}
module.exports = showCreditsController;
module.exports = showCreditsController;

View File

@@ -1,9 +1,7 @@
const configuration = require('src/config/configuration').getInstance();
const Cache = require('src/tmdb/cache');
const TMDB = require('src/tmdb/tmdb');
const Plex = require('src/plex/plex');
const cache = new Cache();
const tmdb = new TMDB(cache, configuration.get('tmdb', 'apiKey'));
const tmdb = new TMDB(configuration.get('tmdb', 'apiKey'));
const plex = new Plex(configuration.get('plex', 'ip'));
function handleError(error, res) {

View File

@@ -1,24 +1,25 @@
const UserRepository = require('src/user/userRepository');
const UserRepository = require("src/user/userRepository");
const userRepository = new UserRepository();
const fetch = require('node-fetch');
const FormData = require('form-data');
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'
(message = "Unauthorized. Please check plex credentials."),
(source = "plex");
}
res.status(status).send({ success: false, message, source })
res.status(status).send({ success: false, message, source });
} else {
console.log('caught authenticate plex account controller error', error)
console.log("caught authenticate plex account controller error", error);
res.status(500).send({
message: 'An unexpected error occured while authenticating your account with plex',
message:
"An unexpected error occured while authenticating your account with plex",
source
})
});
}
}
@@ -28,33 +29,32 @@ function handleResponse(response) {
success: false,
status: response.status,
message: response.statusText
}
};
}
return response.json()
return response.json();
}
function plexAuthenticate(username, password) {
const url = 'https://plex.tv/api/v2/users/signin'
const url = "https://plex.tv/api/v2/users/signin";
const form = new FormData()
form.append('login', username)
form.append('password', password)
form.append('rememberMe', 'false')
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'
}
Accept: "application/json, text/plain, */*",
"Content-Type": form.getHeaders()["content-type"],
"X-Plex-Client-Identifier": "seasonedRequest"
};
const options = {
method: 'POST',
method: "POST",
headers,
body: form
}
};
return fetch(url, options)
.then(resp => handleResponse(resp))
return fetch(url, options).then(resp => handleResponse(resp));
}
function link(req, res) {
@@ -63,22 +63,28 @@ function link(req, res) {
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))
.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 user = req.loggedInUser;
const username = req.loggedInUser ? req.loggedInUser.username : null;
return userRepository.unlinkPlexUserId(user.username)
.then(response => res.send({
success: true,
message: "Successfully unlinked plex account from seasoned request."
}))
.catch(error => handleError(error, res))
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 = {

View File

@@ -1,36 +1,61 @@
const User = require('src/user/user');
const Token = require('src/user/token');
const UserSecurity = require('src/user/userSecurity');
const UserRepository = require('src/user/userRepository');
const configuration = require('src/config/configuration').getInstance();
const User = require("src/user/user");
const Token = require("src/user/token");
const UserSecurity = require("src/user/userSecurity");
const UserRepository = require("src/user/userRepository");
const configuration = require("src/config/configuration").getInstance();
const secret = configuration.get('authentication', 'secret');
const secret = configuration.get("authentication", "secret");
const userSecurity = new UserSecurity();
const userRepository = new UserRepository();
// TODO look to move some of the token generation out of the reach of the final "catch-all"
// catch including the, maybe sensitive, error message.
const isProduction = process.env.NODE_ENV === "production";
const cookieOptions = {
httpOnly: false,
secure: isProduction,
maxAge: 90 * 24 * 3600000, // 90 days
sameSite: isProduction ? "Strict" : "Lax"
};
/**
* Controller: Log in a user provided correct credentials.
* @param {Request} req http request variable
* @param {Response} res
* @returns {Callback}
*/
function loginController(req, res) {
const user = new User(req.body.username);
const password = req.body.password;
async function loginController(req, res) {
const user = new User(req.body.username);
const password = req.body.password;
userSecurity.login(user, password)
.then(() => userRepository.checkAdmin(user))
.then(checkAdmin => {
const isAdmin = checkAdmin === 1 ? true : false;
const token = new Token(user, isAdmin).toString(secret);
res.send({ success: true, token });
})
.catch(error => {
res.status(401).send({ success: false, message: error.message });
try {
const [loggedIn, isAdmin, settings] = await Promise.all([
userSecurity.login(user, password),
userRepository.checkAdmin(user),
userRepository.getSettings(user.username)
]);
if (!loggedIn) {
return res.status(503).send({
success: false,
message: "Unexpected error! Unable to create user."
});
}
const token = new Token(
user,
isAdmin === 1 ? true : false,
settings
).toString(secret);
return res.cookie("authorization", token, cookieOptions).status(200).send({
success: true,
message: "Welcome to request.movie!"
});
} catch (error) {
return res.status(401).send({ success: false, message: error.message });
}
}
module.exports = loginController;

View File

@@ -0,0 +1,16 @@
/**
* Controller: Log out a user (destroy authorization token)
* @param {Request} req http request variable
* @param {Response} res
* @returns {Callback}
*/
async function logoutController(req, res) {
res.clearCookie("authorization");
return res.status(200).send({
success: true,
message: "Logged out, see you later!"
});
}
module.exports = logoutController;

View File

@@ -1,13 +1,21 @@
const User = require('src/user/user');
const Token = require('src/user/token');
const UserSecurity = require('src/user/userSecurity');
const UserRepository = require('src/user/userRepository');
const configuration = require('src/config/configuration').getInstance();
const User = require("src/user/user");
const Token = require("src/user/token");
const UserSecurity = require("src/user/userSecurity");
const UserRepository = require("src/user/userRepository");
const configuration = require("src/config/configuration").getInstance();
const secret = configuration.get('authentication', 'secret');
const secret = configuration.get("authentication", "secret");
const userSecurity = new UserSecurity();
const userRepository = new UserRepository();
const isProduction = process.env.NODE_ENV === "production";
const cookieOptions = {
httpOnly: false,
secure: isProduction,
maxAge: 90 * 24 * 3600000, // 90 days
sameSite: isProduction ? "Strict" : "Lax"
};
/**
* Controller: Register a new user
* @param {Request} req http request variable
@@ -15,21 +23,25 @@ const userRepository = new UserRepository();
* @returns {Callback}
*/
function registerController(req, res) {
const user = new User(req.body.username, req.body.email);
const password = req.body.password;
const user = new User(req.body.username, req.body.email);
const password = req.body.password;
userSecurity.createNewUser(user, password)
.then(() => userRepository.checkAdmin(user))
.then(checkAdmin => {
const isAdmin = checkAdmin === 1 ? true : false;
const token = new Token(user, isAdmin).toString(secret);
res.send({
success: true, message: 'Welcome to Seasoned!', token
});
})
.catch(error => {
res.status(401).send({ success: false, message: error.message });
});
userSecurity
.createNewUser(user, password)
.then(() => {
const token = new Token(user, false).toString(secret);
return res
.cookie("authorization", token, cookieOptions)
.status(200)
.send({
success: true,
message: "Welcome to Seasoned!"
});
})
.catch(error => {
res.status(401).send({ success: false, message: error.message });
});
}
module.exports = registerController;

View File

@@ -1,4 +1,4 @@
const RequestRepository = require('src/plex/requestRepository.js');
const RequestRepository = require("src/plex/requestRepository.js");
const requestRepository = new RequestRepository();
@@ -9,15 +9,20 @@ const requestRepository = new RequestRepository();
* @returns {Callback}
*/
function requestsController(req, res) {
const user = req.loggedInUser;
const username = req.loggedInUser ? req.loggedInUser.username : null;
requestRepository.userRequests(user)
.then(requests => {
res.send({ success: true, results: requests, total_results: requests.length });
})
.catch(error => {
res.status(500).send({ success: false, message: error.message });
requestRepository
.userRequests(username)
.then(requests => {
res.send({
success: true,
results: requests,
total_results: requests.length
});
})
.catch(error => {
res.status(500).send({ success: false, message: error.message });
});
}
module.exports = requestsController;

View File

@@ -1,4 +1,4 @@
const SearchHistory = require('src/searchHistory/searchHistory');
const SearchHistory = require("src/searchHistory/searchHistory");
const searchHistory = new SearchHistory();
@@ -9,16 +9,16 @@ const searchHistory = new SearchHistory();
* @returns {Callback}
*/
function historyController(req, res) {
const user = req.loggedInUser;
const username = user === undefined ? undefined : user.username;
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 });
});
searchHistory
.read(username)
.then(searchQueries => {
res.send({ success: true, searchQueries });
})
.catch(error => {
res.status(404).send({ success: false, message: error.message });
});
}
module.exports = historyController;

View File

@@ -1,4 +1,4 @@
const UserRepository = require('src/user/userRepository');
const UserRepository = require("src/user/userRepository");
const userRepository = new UserRepository();
/**
* Controller: Retrieves settings of a logged in user
@@ -7,36 +7,35 @@ const userRepository = new UserRepository();
* @returns {Callback}
*/
const getSettingsController = (req, res) => {
const user = req.loggedInUser;
const username = user === undefined ? undefined : user.username;
userRepository.getSettings(username)
.then(settings => {
res.send({ success: true, settings });
})
.catch(error => {
res.status(404).send({ success: false, message: error.message });
});
}
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 user = req.loggedInUser;
const username = user === undefined ? undefined : user.username;
const username = req.loggedInUser ? req.loggedInUser.username : null;
const idempotencyKey = req.headers('Idempotency-Key'); // TODO implement better transactions
const { dark_mode, emoji } = req.body;
const idempotencyKey = req.headers("Idempotency-Key"); // TODO implement better transactions
const { 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 });
});
}
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
}
getSettingsController,
updateSettingsController
};

View File

@@ -1,47 +1,52 @@
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 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) {
res.status(status).send({ success: false, message })
return res.status(status).send({ success: false, message });
} else {
console.log('caught view history controller error', error)
res.status(500).send({ message: 'An unexpected error occured while fetching view history'})
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;
tautulli.watchTimeStats(user.plex_userid)
return tautulli
.watchTimeStats(user.plex_userid)
.then(data => {
console.log('data', data, JSON.stringify(data.response.data))
return res.send({
success: true,
data: data.response.data,
message: 'watch time successfully fetched from tautulli'
})
message: "watch time successfully fetched from tautulli"
});
})
.catch(error => handleError(error, res));
}
function getPlaysByDayOfWeekController(req, res) {
const user = req.loggedInUser;
const { days, y_axis } = req.query;
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'
})
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) {
@@ -52,49 +57,46 @@ function getPlaysByDaysController(req, res) {
return res.status(422).send({
success: false,
message: "Missing parameter: days (number)"
})
});
}
const allowedYAxisDataType = ['plays', 'duration'];
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 }]`
})
message: `Y axis parameter must be one of values: [${allowedYAxisDataType}]`
});
}
tautulli.getPlaysByDays(user.plex_userid, days, y_axis)
.then(data => res.send({
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;
const user = req.loggedInUser;
console.log('user', user)
// 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));
// TODO here we should check if we can init tau
// and then return 501 Not implemented
tautulli.viewHistory(user.plex_userid)
.then(data => {
console.log('data', data, JSON.stringify(data.response.data.data))
return res.send({
success: true,
data: data.response.data.data,
message: 'view history successfully fetched from tautulli'
})
})
.catch(error => handleError(error))
// const username = user.username;
// const username = user.username;
}
module.exports = {

View File

@@ -1,11 +1,11 @@
const mustBeAuthenticated = (req, res, next) => {
if (req.loggedInUser === undefined) {
return res.status(401).send({
success: false,
message: 'You must be logged in.',
});
}
return next();
if (req.loggedInUser === undefined) {
return res.status(401).send({
success: false,
message: "You must be logged in."
});
}
return next();
};
module.exports = mustBeAuthenticated;

View File

@@ -0,0 +1,32 @@
/* eslint-disable no-param-reassign */
const configuration = require("src/config/configuration").getInstance();
const Token = require("src/user/token");
const secret = configuration.get("authentication", "secret");
// Token example:
// curl -i -H "Authorization:[token]" localhost:31459/api/v1/user/history
const reqTokenToUser = (req, res, next) => {
const cookieAuthToken = req.cookies.authorization;
const headerAuthToken = req.headers.authorization;
if (cookieAuthToken || headerAuthToken) {
try {
const token = Token.fromString(
cookieAuthToken || headerAuthToken,
secret
);
req.loggedInUser = token.user;
} catch (error) {
req.loggedInUser = undefined;
}
} else {
// guest session
console.debug("No auth token in header or cookie.");
}
next();
};
module.exports = reqTokenToUser;

View File

@@ -1,23 +0,0 @@
/* eslint-disable no-param-reassign */
const configuration = require('src/config/configuration').getInstance();
const secret = configuration.get('authentication', 'secret');
const Token = require('src/user/token');
// Token example:
// curl -i -H "Authorization:[token]" localhost:31459/api/v1/user/history
const tokenToUser = (req, res, next) => {
const rawToken = req.headers.authorization;
if (rawToken) {
try {
const token = Token.fromString(rawToken, secret);
req.loggedInUser = token.user;
} catch (error) {
req.loggedInUser = undefined;
}
}
next();
};
module.exports = tokenToUser;

File diff suppressed because it is too large Load Diff

4
yarn.lock Normal file
View File

@@ -0,0 +1,4 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1