2 Commits

60 changed files with 3593 additions and 5143 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,19 +1,11 @@
CREATE TABLE IF NOT EXISTS user ( CREATE TABLE IF NOT EXISTS user (
user_name varchar(127) UNIQUE, user_name varchar(127) UNIQUE,
password varchar(127), password varchar(127),
admin boolean DEFAULT 0,
email varchar(127) UNIQUE, email varchar(127) UNIQUE,
admin boolean DEFAULT 0,
primary key (user_name) primary key (user_name)
); );
CREATE TABLE IF NOT EXISTS settings (
user_name varchar(127) UNIQUE,
dark_mode boolean DEFAULT 0,
plex_userid varchar(127) DEFAULT NULL,
emoji varchar(16) DEFAULT NULL,
foreign key(user_name) REFERENCES user(user_name) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS cache ( CREATE TABLE IF NOT EXISTS cache (
key varchar(255), key varchar(255),
value blob, value blob,
@@ -31,18 +23,17 @@ CREATE TABLE IF NOT EXISTS search_history (
); );
CREATE TABLE IF NOT EXISTS requests( CREATE TABLE IF NOT EXISTS requests(
id NUMBER, id TEXT,
title TEXT, title TEXT,
year NUMBER, year NUMBER,
poster_path TEXT DEFAULT NULL, poster_path TEXT DEFAULT NULL,
background_path TEXT DEFAULT NULL, background_path TEXT DEFAULT NULL,
requested_by varchar(127) DEFAULT NULL, requested_by TEXT,
ip TEXT, ip TEXT,
date DATE DEFAULT CURRENT_TIMESTAMP, date DATE DEFAULT CURRENT_TIMESTAMP,
status CHAR(25) DEFAULT 'requested' NOT NULL, status CHAR(25) DEFAULT 'requested' NOT NULL,
user_agent CHAR(255) DEFAULT NULL, user_agent CHAR(255) DEFAULT NULL,
type CHAR(50) DEFAULT 'movie', type CHAR(50) DEFAULT 'movie'
foreign key(requested_by) REFERENCES user(user_name) ON DELETE SET NULL
); );
CREATE TABLE IF NOT EXISTS request( CREATE TABLE IF NOT EXISTS request(

View File

@@ -1,5 +1,4 @@
DROP TABLE IF EXISTS user; DROP TABLE IF EXISTS user;
DROP TABLE IF EXISTS settings;
DROP TABLE IF EXISTS search_history; DROP TABLE IF EXISTS search_history;
DROP TABLE IF EXISTS requests; DROP TABLE IF EXISTS requests;
DROP TABLE IF EXISTS request; DROP TABLE IF EXISTS request;

View File

@@ -6,7 +6,6 @@ class SqliteDatabase {
constructor(host) { constructor(host) {
this.host = host; this.host = host;
this.connection = new sqlite3.Database(this.host); this.connection = new sqlite3.Database(this.host);
this.execute('pragma foreign_keys = on;');
this.schemaDirectory = path.join(__dirname, 'schemas'); this.schemaDirectory = path.join(__dirname, 'schemas');
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,7 +1,9 @@
const assert = require('assert') const assert = require('assert')
const configuration = require('src/config/configuration').getInstance(); const configuration = require('src/config/configuration').getInstance();
const Cache = require('src/tmdb/cache');
const TMDB = require('src/tmdb/tmdb'); const TMDB = require('src/tmdb/tmdb');
const tmdb = new TMDB(configuration.get('tmdb', 'apiKey')); const cache = new Cache();
const tmdb = new TMDB(cache, configuration.get('tmdb', 'apiKey'));
const establishedDatabase = require('src/database/database'); const establishedDatabase = require('src/database/database');
const utils = require('./utils'); const utils = require('./utils');
@@ -84,18 +86,18 @@ class RequestRepository {
* @param {tmdb} tmdb class of movie|show to add * @param {tmdb} tmdb class of movie|show to add
* @returns {Promise} * @returns {Promise}
*/ */
requestFromTmdb(tmdb, ip, user_agent, username) { requestFromTmdb(tmdb, ip, user_agent, user) {
return Promise.resolve() return Promise.resolve()
.then(() => this.database.get(this.queries.read, [tmdb.id, tmdb.type])) .then(() => this.database.get(this.queries.read, [tmdb.id, tmdb.type]))
.then(row => assert.equal(row, undefined, 'Id has already been requested')) .then(row => assert.equal(row, undefined, 'Id has already been requested'))
.then(() => this.database.run(this.queries.add, [tmdb.id, tmdb.title, tmdb.year, tmdb.poster, tmdb.backdrop, username, ip, user_agent, tmdb.type])) .then(() => this.database.run(this.queries.add, [tmdb.id, tmdb.title, tmdb.year, tmdb.poster, tmdb.backdrop, user, ip, user_agent, tmdb.type]))
.catch((error) => { .catch((error) => {
if (error.name === 'AssertionError' || error.message.endsWith('been requested')) { if (error.name === 'AssertionError' || error.message.endsWith('been requested')) {
throw new Error('This id is already requested', error.message); throw new Error('This id is already requested', error.message);
} }
console.log('Error @ request.addTmdb:', error); console.log('Error @ request.addTmdb:', error);
throw new Error('Could not add request'); throw new Error('Could not add request');
}); });
} }
/** /**

View File

@@ -28,23 +28,17 @@ class SearchHistory {
/** /**
* Creates a new search entry in the database. * Creates a new search entry in the database.
* @param {String} username logged in user doing the search * @param {User} user a new user
* @param {String} searchQuery the query the user searched for * @param {String} searchQuery the query the user searched for
* @returns {Promise} * @returns {Promise}
*/ */
create(username, searchQuery) { create(user, searchQuery) {
return this.database.run(this.queries.create, [searchQuery, username]) return Promise.resolve()
.catch(error => { .then(() => this.database.run(this.queries.create, [searchQuery, user]))
.catch((error) => {
if (error.message.includes('FOREIGN')) { if (error.message.includes('FOREIGN')) {
throw new Error('Could not create search history.'); throw new Error('Could not create search history.');
} }
throw {
success: false,
status: 500,
message: 'An unexpected error occured',
source: 'database'
}
}); });
} }
} }

View File

@@ -1,74 +0,0 @@
const fetch = require("node-fetch");
class Tautulli {
constructor(apiKey, ip, port) {
this.apiKey = apiKey;
this.ip = ip;
this.port = port;
}
buildUrlWithCmdAndUserid(cmd, user_id) {
const url = new URL("api/v2", `http://${this.ip}:${this.port}`);
url.searchParams.append("apikey", this.apiKey);
url.searchParams.append("cmd", cmd);
url.searchParams.append("user_id", user_id);
return url;
}
logTautulliError(error) {
console.error("error fetching from tautulli");
throw error;
}
getPlaysByDayOfWeek(plex_userid, days, y_axis) {
const url = this.buildUrlWithCmdAndUserid(
"get_plays_by_dayofweek",
plex_userid
);
url.searchParams.append("time_range", days);
url.searchParams.append("y_axis", y_axis);
return fetch(url.href)
.then(resp => resp.json())
.catch(error => this.logTautulliError(error));
}
getPlaysByDays(plex_userid, days, y_axis) {
const url = this.buildUrlWithCmdAndUserid("get_plays_by_date", plex_userid);
url.searchParams.append("time_range", days);
url.searchParams.append("y_axis", y_axis);
return fetch(url.href)
.then(resp => resp.json())
.catch(error => this.logTautulliError(error));
}
watchTimeStats(plex_userid) {
const url = this.buildUrlWithCmdAndUserid(
"get_user_watch_time_stats",
plex_userid
);
url.searchParams.append("grouping", 0);
return fetch(url.href)
.then(resp => resp.json())
.catch(error => this.logTautulliError(error));
}
viewHistory(plex_userid) {
const url = this.buildUrlWithCmdAndUserid("get_history", plex_userid);
url.searchParams.append("start", 0);
url.searchParams.append("length", 50);
console.log("fetching url", url.href);
return fetch(url.href)
.then(resp => resp.json())
.catch(error => this.logTautulliError(error));
}
}
module.exports = Tautulli;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,93 +0,0 @@
const UserRepository = require("src/user/userRepository");
const userRepository = new UserRepository();
const fetch = require("node-fetch");
const FormData = require("form-data");
function handleError(error, res) {
let { status, message, source } = error;
if (status && message) {
if (status === 401) {
(message = "Unauthorized. Please check plex credentials."),
(source = "plex");
}
res.status(status).send({ success: false, message, source });
} else {
console.log("caught authenticate plex account controller error", error);
res.status(500).send({
message:
"An unexpected error occured while authenticating your account with plex",
source
});
}
}
function handleResponse(response) {
if (!response.ok) {
throw {
success: false,
status: response.status,
message: response.statusText
};
}
return response.json();
}
function plexAuthenticate(username, password) {
const url = "https://plex.tv/api/v2/users/signin";
const form = new FormData();
form.append("login", username);
form.append("password", password);
form.append("rememberMe", "false");
const headers = {
Accept: "application/json, text/plain, */*",
"Content-Type": form.getHeaders()["content-type"],
"X-Plex-Client-Identifier": "seasonedRequest"
};
const options = {
method: "POST",
headers,
body: form
};
return fetch(url, options).then(resp => handleResponse(resp));
}
function link(req, res) {
const user = req.loggedInUser;
const { username, password } = req.body;
return plexAuthenticate(username, password)
.then(plexUser => userRepository.linkPlexUserId(user.username, plexUser.id))
.then(response =>
res.send({
success: true,
message:
"Successfully authenticated and linked plex account with seasoned request."
})
)
.catch(error => handleError(error, res));
}
function unlink(req, res) {
const username = req.loggedInUser ? req.loggedInUser.username : null;
return userRepository
.unlinkPlexUserId(username)
.then(response =>
res.send({
success: true,
message: "Successfully unlinked plex account from seasoned request."
})
)
.catch(error => handleError(error, res));
}
module.exports = {
link,
unlink
};

View File

@@ -0,0 +1,24 @@
const SearchHistory = require('src/searchHistory/searchHistory');
const searchHistory = new SearchHistory();
/**
* Controller: Retrieves search history of a logged in user
* @param {Request} req http request variable
* @param {Response} res
* @returns {Callback}
*/
function historyController(req, res) {
const user = req.loggedInUser;
const username = user === undefined ? undefined : user.username;
searchHistory.read(username)
.then(searchQueries => {
res.send({ success: true, searchQueries });
})
.catch(error => {
res.status(404).send({ success: false, message: error.message });
});
}
module.exports = historyController;

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,24 +0,0 @@
const SearchHistory = require("src/searchHistory/searchHistory");
const searchHistory = new SearchHistory();
/**
* Controller: Retrieves search history of a logged in user
* @param {Request} req http request variable
* @param {Response} res
* @returns {Callback}
*/
function historyController(req, res) {
const username = req.loggedInUser ? req.loggedInUser.username : null;
searchHistory
.read(username)
.then(searchQueries => {
res.send({ success: true, searchQueries });
})
.catch(error => {
res.status(404).send({ success: false, message: error.message });
});
}
module.exports = historyController;

View File

@@ -1,41 +0,0 @@
const UserRepository = require("src/user/userRepository");
const userRepository = new UserRepository();
/**
* Controller: Retrieves settings of a logged in user
* @param {Request} req http request variable
* @param {Response} res
* @returns {Callback}
*/
const getSettingsController = (req, res) => {
const username = req.loggedInUser ? req.loggedInUser.username : null;
userRepository
.getSettings(username)
.then(settings => {
res.send({ success: true, settings });
})
.catch(error => {
res.status(404).send({ success: false, message: error.message });
});
};
const updateSettingsController = (req, res) => {
const username = req.loggedInUser ? req.loggedInUser.username : null;
const idempotencyKey = req.headers("Idempotency-Key"); // TODO implement better transactions
const { dark_mode, emoji } = req.body;
userRepository
.updateSettings(username, dark_mode, emoji)
.then(settings => {
res.send({ success: true, settings });
})
.catch(error => {
res.status(404).send({ success: false, message: error.message });
});
};
module.exports = {
getSettingsController,
updateSettingsController
};

View File

@@ -1,107 +0,0 @@
const configuration = require("src/config/configuration").getInstance();
const Tautulli = require("src/tautulli/tautulli");
const apiKey = configuration.get("tautulli", "apiKey");
const ip = configuration.get("tautulli", "ip");
const port = configuration.get("tautulli", "port");
const tautulli = new Tautulli(apiKey, ip, port);
function handleError(error, res) {
const { status, message } = error;
if (status && message) {
return res.status(status).send({ success: false, message });
} else {
console.log("caught view history controller error", error);
return res.status(500).send({
message: "An unexpected error occured while fetching view history"
});
}
}
function watchTimeStatsController(req, res) {
const user = req.loggedInUser;
return tautulli
.watchTimeStats(user.plex_userid)
.then(data => {
return res.send({
success: true,
data: data.response.data,
message: "watch time successfully fetched from tautulli"
});
})
.catch(error => handleError(error, res));
}
function getPlaysByDayOfWeekController(req, res) {
const user = req.loggedInUser;
const { days, y_axis } = req.query;
return tautulli
.getPlaysByDayOfWeek(user.plex_userid, days, y_axis)
.then(data =>
res.send({
success: true,
data: data.response.data,
message: "play by day of week successfully fetched from tautulli"
})
)
.catch(error => handleError(error, res));
}
function getPlaysByDaysController(req, res) {
const user = req.loggedInUser;
const { days, y_axis } = req.query;
if (days === undefined) {
return res.status(422).send({
success: false,
message: "Missing parameter: days (number)"
});
}
const allowedYAxisDataType = ["plays", "duration"];
if (!allowedYAxisDataType.includes(y_axis)) {
return res.status(422).send({
success: false,
message: `Y axis parameter must be one of values: [${allowedYAxisDataType}]`
});
}
return tautulli
.getPlaysByDays(user.plex_userid, days, y_axis)
.then(data =>
res.send({
success: true,
data: data.response.data
})
)
.catch(error => handleError(error, res));
}
function userViewHistoryController(req, res) {
const user = req.loggedInUser;
// TODO here we should check if we can init tau
// and then return 501 Not implemented
return tautulli
.viewHistory(user.plex_userid)
.then(data => {
return res.send({
success: true,
data: data.response.data.data,
message: "view history successfully fetched from tautulli"
});
})
.catch(error => handleError(error, res));
// const username = user.username;
}
module.exports = {
watchTimeStatsController,
getPlaysByDaysController,
getPlaysByDayOfWeekController,
userViewHistoryController
};

View File

@@ -0,0 +1,294 @@
const graphql = require("graphql");
const establishedDatabase = require('src/database/database');
const fetch = require('node-fetch');
const TorrentType = new graphql.GraphQLObjectType({
name: "Torrent",
fields: {
magnet: { type: graphql.GraphQLString },
torrent_name: { type: graphql.GraphQLString},
tmdb_id: { type: graphql.GraphQLString }
}
});
const RequestType = new graphql.GraphQLObjectType({
name: "Request",
fields: {
id: { type: graphql.GraphQLID },
title: { type: graphql.GraphQLString },
year: { type: graphql.GraphQLInt},
poster_path: { type: graphql.GraphQLString },
background_path: { type: graphql.GraphQLString },
requested_by: { type: graphql.GraphQLString },
ip: { type: graphql.GraphQLString },
date: { type: graphql.GraphQLString },
status: { type: graphql.GraphQLString },
user_agent: { type: graphql.GraphQLString },
type: { type: graphql.GraphQLString }
}
});
const RequestsType = new graphql.GraphQLObjectType({
name: 'Requests',
type: graphql.GraphQLList(RequestType),
resolve: (root, args, context, info) => {
return establishedDatabase.all("SELECT * FROM requests;")
.catch(error => console.error("something went wrong fetching 'all' query. Error:", error))
}
})
const ProgressType = new graphql.GraphQLObjectType({
name: 'TorrentProgress',
fields: {
eta: { type: graphql.GraphQLInt },
finished: { type: graphql.GraphQLBoolean },
key: { type: graphql.GraphQLString },
name: { type: graphql.GraphQLString },
progress: { type: graphql.GraphQLFloat },
state: { type: graphql.GraphQLString },
Torrent: {
type: TorrentType,
resolve(parent) {
console.log('prante: ', parent.name)
console.log(parent.name.slice(0,10))
return establishedDatabase.get(`select magnet, torrent_name, tmdb_id from requested_torrent where torrent_name like (?);`, [parent.name.slice(0, 10) + '%'])
}
},
Requested: {
type: graphql.GraphQLList(RequestType),
// resolve: () => fetch('https://api.kevinmidboe.com/api/v2/request?page=1/').then(resp => resp.json())
// .then(data => {
// // console.log('data', data)
// return data.results
// })
resolve: (parent) => {
return establishedDatabase.all("SELECT * FROM requests;")
}
}
}
})
const TorrentsRequestedType = new graphql.GraphQLObjectType({
name: 'TorrentsRequested',
fields: {
magnet: { type: graphql.GraphQLString },
torrent_name: { type: graphql.GraphQLString },
tmdb_id: { type: graphql.GraphQLString },
date_added: { type: graphql.GraphQLString },
Request: {
type: RequestType,
// resolve: () => fetch('https://api.kevinmidboe.com/api/v2/request?page=1/').then(resp => resp.json())
// .then(data => {
// return data.results
// })
resolve(parentValue, args) {
return establishedDatabase.get('select * from requests where id = (?);', [parentValue.tmdb_id])
}
},
Progress: {
type: ProgressType,
resolve(parentValue, args) {
return fetch('http://localhost:5000/')
.then(resp => resp.json())
// .then(data => { console.log('data', data); return data.filter(download => download.name === parentValue.torrent_name) })
}
}
}
})
// create a graphql query to select all and by id
var queryType = new graphql.GraphQLObjectType({
name: 'Query',
fields: {
//first query to select all
Requests: {
type: graphql.GraphQLList(RequestType),
resolve: (root, args, context, info) => {
return establishedDatabase.all("SELECT * FROM requests;")
.catch(error => console.error("something went wrong fetching 'all' query. Error:", error))
}
},
Progress: {
type: graphql.GraphQLList(ProgressType),
resolve: (root, args, context, info) => {
console.log('user', context.loggedInUser)
return fetch('http://localhost:5000')
.then(resp => resp.json())
}
},
ProgressRequested: {
type: graphql.GraphQLList(ProgressType),
resolve: (root, args, context, info) => {
console.log('root & args', root, args)
}
},
TorrentsRequested: {
type: graphql.GraphQLList(TorrentsRequestedType),
resolve: (root, args, context, info) => {
return establishedDatabase.all("SELECT * FROM requested_torrent;")
.then(data => data.filter(request => { if (request.tmdb_id === '83666') { console.log('request', request, root);}; return request }))
.catch(error => console.error("something went wrong fetching 'all' query. Error:", error))
}
},
DownloadingRequestByName: {
type: RequestType,
args:{
name:{
type: new graphql.GraphQLNonNull(graphql.GraphQLString)
}
},
resolve: (root, { name }, context, info) => {
return establishedDatabase.all("SELECT * FROM requests where requested_by = (?);", [name])
.catch(error => console.error("something went wrong fetching 'all' query. Error:", error))
}
},
//second query to select by id
Request:{
type: RequestType,
args:{
id:{
type: new graphql.GraphQLNonNull(graphql.GraphQLID)
}
},
resolve: (root, {id}, context, info) => {
return establishedDatabase.get("SELECT * FROM requests WHERE id = (?);",[id])
.catch(error => console.error(`something went wrong fetching by id: '${ id }'. Error: ${ error }`))
}
},
Torrents: {
type: graphql.GraphQLList(TorrentType),
resolve: (root, {id}, context, info) => {
// console.log('parent', parent)
return establishedDatabase.all("SELECT * FROM requested_torrent")
.catch(error => console.error(`something went wrong fetching all torrents. Error: ${ error }`))
}
},
Torrent: {
type: TorrentType,
args: {
id: {
type: new graphql.GraphQLNonNull(graphql.GraphQLID)
}
},
resolve: (parent, {id}, context, info) => {
// console.log('searcing from parent', parent)
return establishedDatabase.get("SELECT * FROM requested_torrent WHERE tmdb_id = (?);", [id])
}
}
}
});
//mutation type is a type of object to modify data (INSERT,DELETE,UPDATE)
// var mutationType = new graphql.GraphQLObjectType({
// name: 'Mutation',
// fields: {
// //mutation for creacte
// createPost: {
// //type of object to return after create in SQLite
// type: PostType,
// //argument of mutation creactePost to get from request
// args: {
// title: {
// type: new graphql.GraphQLNonNull(graphql.GraphQLString)
// },
// description:{
// type: new graphql.GraphQLNonNull(graphql.GraphQLString)
// },
// createDate:{
// type: new graphql.GraphQLNonNull(graphql.GraphQLString)
// },
// author:{
// type: new graphql.GraphQLNonNull(graphql.GraphQLString)
// }
// },
// resolve: (root, {title, description, createDate, author}) => {
// return new Promise((resolve, reject) => {
// //raw SQLite to insert a new post in post table
// database.run('INSERT INTO Posts (title, description, createDate, author) VALUES (?,?,?,?);', [title, description, createDate, author], (err) => {
// if(err) {
// reject(null);
// }
// database.get("SELECT last_insert_rowid() as id", (err, row) => {
// resolve({
// id: row["id"],
// title: title,
// description: description,
// createDate:createDate,
// author: author
// });
// });
// });
// })
// }
// },
// //mutation for update
// updatePost: {
// //type of object to return afater update in SQLite
// type: graphql.GraphQLString,
// //argument of mutation creactePost to get from request
// args:{
// id:{
// type: new graphql.GraphQLNonNull(graphql.GraphQLID)
// },
// title: {
// type: new graphql.GraphQLNonNull(graphql.GraphQLString)
// },
// description:{
// type: new graphql.GraphQLNonNull(graphql.GraphQLString)
// },
// createDate:{
// type: new graphql.GraphQLNonNull(graphql.GraphQLString)
// },
// author:{
// type: new graphql.GraphQLNonNull(graphql.GraphQLString)
// }
// },
// resolve: (root, {id, title, description, createDate, author}) => {
// return new Promise((resolve, reject) => {
// //raw SQLite to update a post in post table
// database.run('UPDATE Posts SET title = (?), description = (?), createDate = (?), author = (?) WHERE id = (?);', [title, description, createDate, author, id], (err) => {
// if(err) {
// reject(err);
// }
// resolve(`Post #${id} updated`);
// });
// })
// }
// },
// //mutation for update
// deletePost: {
// //type of object resturn after delete in SQLite
// type: graphql.GraphQLString,
// args:{
// id:{
// type: new graphql.GraphQLNonNull(graphql.GraphQLID)
// }
// },
// resolve: (root, {id}) => {
// return new Promise((resolve, reject) => {
// //raw query to delete from post table by id
// database.run('DELETE from Posts WHERE id =(?);', [id], (err) => {
// if(err) {
// reject(err);
// }
// resolve(`Post #${id} deleted`);
// });
// })
// }
// }
// }
// });
//define schema with post object, queries, and mustation
const schema = new graphql.GraphQLSchema({
query: queryType,
// mutation: mutationType
});
//export schema to use on index.js
module.exports = {
schema
}

View File

@@ -6,7 +6,7 @@ const mustBeAdmin = (req, res, next) => {
if (req.loggedInUser === undefined) { if (req.loggedInUser === undefined) {
return res.status(401).send({ return res.status(401).send({
success: false, success: false,
message: 'You must be logged in.', error: 'You must be logged in.',
}); });
} else { } else {
database.get(`SELECT admin FROM user WHERE user_name IS ?`, req.loggedInUser.username) database.get(`SELECT admin FROM user WHERE user_name IS ?`, req.loggedInUser.username)
@@ -15,7 +15,7 @@ const mustBeAdmin = (req, res, next) => {
if (isAdmin.admin == 0) { if (isAdmin.admin == 0) {
return res.status(401).send({ return res.status(401).send({
success: false, success: false,
message: 'You must be logged in as a admin.' error: 'You must be logged in as a admin.'
}) })
} }
}) })

View File

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

View File

@@ -1,31 +0,0 @@
const establishedDatabase = require('src/database/database');
const mustHaveAccountLinkedToPlex = (req, res, next) => {
let database = establishedDatabase;
const loggedInUser = req.loggedInUser;
if (loggedInUser === undefined) {
return res.status(401).send({
success: false,
message: 'You must have your account linked to a plex account.',
});
} else {
database.get(`SELECT plex_userid FROM settings WHERE user_name IS ?`, loggedInUser.username)
.then(row => {
const plex_userid = row.plex_userid;
if (plex_userid === null || plex_userid === undefined) {
return res.status(403).send({
success: false,
message: 'No plex account user id found for your user. Please authenticate your plex account at /user/authenticate.'
})
} else {
req.loggedInUser.plex_userid = plex_userid;
return next();
}
})
}
};
module.exports = mustHaveAccountLinkedToPlex;

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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