Merge pull request #111 from KevinMidboe/api/v2

Api/v2
This commit is contained in:
2019-11-04 18:01:15 +01:00
committed by GitHub
89 changed files with 6262 additions and 1679 deletions

1
.gitignore vendored
View File

@@ -1,5 +1,6 @@
.DS_Store
development.json
env
shows.db

View File

@@ -1,5 +1,5 @@
language: node_js
node_js: '8.7.0'
node_js: '11.9.0'
git:
submodules: true
script:

View File

@@ -10,8 +10,8 @@
<img src="https://travis-ci.org/KevinMidboe/seasonedShows.svg?branch=master"
alt="Travis CI">
</a>
<a href="https://coveralls.io/github/KevinMidboe/seasonedShows?branch=coverage">
<img src="https://coveralls.io/repos/github/KevinMidboe/seasonedShows/badge.svg?branch=coverage" alt="">
<a href="https://coveralls.io/github/KevinMidboe/seasonedShows?branch=api/v2">
<img src="https://coveralls.io/repos/github/KevinMidboe/seasonedShows/badge.svg?branch=api/v2" alt="">
</a>
<a href="https://snyk.io/test/github/KevinMidboe/seasonedShows?targetFile=seasoned_api/package.json">
<img src="https://snyk.io/test/github/KevinMidboe/seasonedShows/badge.svg?targetFile=seasoned_api/package.json" alt="">

View File

@@ -30,8 +30,8 @@
"redux-thunk": "^2.2.0",
"urijs": "^1.18.12",
"webfontloader": "^1.6.28",
"webpack": "^3.5.5",
"webpack-dev-server": "^2.4.5",
"webpack": "^4.0.0",
"webpack-dev-server": "^3.1.11",
"webpack-merge": "^4.1.0"
},
"devDependencies": {

View File

@@ -8,17 +8,13 @@
"tmdb": {
"apiKey": ""
},
"plex": {
"ip": ""
},
"raven": {
"DSN": ""
},
"mail": {
"host": "",
"user": "",
"password": "",
"user_pi": "",
"password_pi": ""
},
"authentication": {
"secret": "secret"
}
"secret": "secret"
}
}

View File

@@ -8,6 +8,9 @@
"tmdb": {
"apiKey": "bogus-api-key"
},
"plex": {
"ip": "0.0.0.0"
},
"raven": {
"DSN": ""
},

View File

@@ -7,37 +7,48 @@
},
"main": "webserver/server.js",
"scripts": {
"start": "cross-env SEASONED_CONFIG=conf/development.json PROD=true NODE_PATH=. node src/webserver/server.js",
"test": "cross-env SEASONED_CONFIG=conf/test.json NODE_PATH=. mocha --recursive test",
"coverage": "cross-env SEASONED_CONFIG=conf/test.json NODE_PATH=. nyc mocha --recursive test && nyc report --reporter=text-lcov | coveralls",
"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",
"coverage": "cross-env SEASONED_CONFIG=conf/test.json NODE_PATH=. nyc mocha --require @babel/register --recursive test && nyc report --reporter=text-lcov | coveralls",
"lint": "./node_modules/.bin/eslint src/",
"update": "cross-env SEASONED_CONFIG=conf/development.json NODE_PATH=. node src/plex/updateRequestsInPlex.js"
"update": "cross-env SEASONED_CONFIG=conf/development.json NODE_PATH=. node src/plex/updateRequestsInPlex.js",
"docs": "yarn apiDocs; yarn classDocs",
"apiDocs": "",
"classDocs": "./script/generate-class-docs.sh"
},
"dependencies": {
"bcrypt-nodejs": "^0.0.3",
"axios": "^0.18.0",
"bcrypt": "^3.0.6",
"body-parser": "~1.18.2",
"cross-env": "~5.1.4",
"express": "~4.16.0",
"jsonwebtoken": "^8.2.0",
"mongoose": "~5.2.12",
"km-moviedb": "^0.2.12",
"node-cache": "^4.1.1",
"node-fetch": "^2.6.0",
"python-shell": "^0.5.0",
"raven": "^2.4.2",
"request": "^2.87.0",
"request-promise": "^4.2",
"sqlite3": "^4.0.0"
},
"devDependencies": {
"coveralls": "^3.0.0",
"@babel/core": "^7.5.5",
"@babel/node": "^7.5.5",
"@babel/preset-env": "^7.5.5",
"@babel/register": "^7.5.5",
"@types/node": "^12.6.8",
"coveralls": "^3.0.5",
"documentation": "^12.0.3",
"eslint": "^4.9.0",
"eslint-config-airbnb-base": "^12.1.0",
"eslint-plugin-import": "^2.8.0",
"istanbul": "^0.4.5",
"mocha": "^5.0.4",
"mocha": "^6.2.0",
"mocha-lcov-reporter": "^1.3.0",
"nyc": "^11.6.0",
"raven": "^2.4.2",
"supertest": "^3.0.0",
"supertest-as-promised": "^4.0.1"
"supertest-as-promised": "^4.0.1",
"typescript": "^3.5.3"
}
}

View File

@@ -36,6 +36,14 @@ CREATE TABLE IF NOT EXISTS requests(
type CHAR(50) DEFAULT 'movie'
);
CREATE TABLE IF NOT EXISTS request(
id int not null,
title text not null,
year int not null,
type char(10) not null,
date timestamp default (strftime('%s', 'now'))
);
CREATE TABLE IF NOT EXISTS stray_eps(
id TEXT UNIQUE,

View File

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

View File

@@ -25,7 +25,7 @@ class SqliteDatabase {
* @param {Array} parameters in the SQL query
* @returns {Promise}
*/
async run(sql, parameters) {
run(sql, parameters) {
return new Promise((resolve, reject) => {
this.connection.run(sql, parameters, (error, result) => {
if (error)
@@ -41,7 +41,7 @@ class SqliteDatabase {
* @param {Array} parameters in the SQL query
* @returns {Promise}
*/
async all(sql, parameters) {
all(sql, parameters) {
return new Promise((resolve, reject) => {
this.connection.all(sql, parameters, (err, rows) => {
if (err) {
@@ -58,7 +58,7 @@ class SqliteDatabase {
* @param {Array} parameters in the SQL query
* @returns {Promise}
*/
async get(sql, parameters) {
get(sql, parameters) {
return new Promise((resolve, reject) => {
this.connection.get(sql, parameters, (err, rows) => {
if (err) {
@@ -75,7 +75,7 @@ class SqliteDatabase {
* @param {Array} parameters in the SQL query
* @returns {Promise}
*/
async execute(sql) {
execute(sql) {
return new Promise(resolve => {
this.connection.exec(sql, (err, database) => {
if (err) {

View File

@@ -12,7 +12,7 @@ function getMagnetFromURL(url) {
resolve(url)
http.get(options, (res) => {
if (res.statusCode == 301) {
if (res.statusCode == 301 || res.statusCode == 302) {
resolve(res.headers.location)
}
});
@@ -21,12 +21,12 @@ function getMagnetFromURL(url) {
async function find(searchterm, callback) {
const options = {
pythonPath: '/usr/bin/python3',
// pythonPath: '/Library/Frameworks/Python.framework/Versions/3.6/bin/python3',
args: [searchterm, '-s', 'jackett', '-f', '--print'],
};
pythonPath: '../torrent_search/env/bin/python3',
scriptPath: '../torrent_search',
args: [searchterm, '-s', 'jackett', '-f', '--print']
}
PythonShell.run('../torrent_search/torrentSearch/search.py', options, callback);
PythonShell.run('torrentSearch/search.py', options, callback);
// PythonShell does not support return
}
@@ -35,12 +35,12 @@ async function callPythonAddMagnet(url, callback) {
getMagnetFromURL(url)
.then((magnet) => {
const options = {
pythonPath: '/usr/bin/python',
// pythonPath: '/Library/Frameworks/Python.framework/Versions/3.6/bin/python3',
args: [magnet],
pythonPath: '../delugeClient/env/bin/python3',
scriptPath: '../delugeClient',
args: ['add', magnet]
};
PythonShell.run('../app/magnet.py', options, callback);
PythonShell.run('deluge_cli.py', options, callback);
})
.catch((err) => {
console.log(err);
@@ -51,19 +51,16 @@ async function callPythonAddMagnet(url, callback) {
async function SearchPiratebay(query) {
return await new Promise((resolve, reject) => find(query, (err, results) => {
if (err) {
/* eslint-disable no-console */
console.log('THERE WAS A FUCKING ERROR!\n', err);
reject(Error('There was a error when searching for torrents'));
}
if (results) {
/* eslint-disable no-console */
console.log('result', results);
resolve(JSON.parse(results, null, '\t'));
}
}));
}
async function AddMagnet(magnet) {
async function AddMagnet(magnet, name, tmdb_id) {
return await new Promise((resolve, reject) => callPythonAddMagnet(magnet, (err, results) => {
if (err) {
/* eslint-disable no-console */

View File

@@ -0,0 +1,20 @@
const Episode = require('src/plex/types/episode');
function convertPlexToEpisode(plexEpisode) {
const episode = new Episode(plexEpisode.title, plexEpisode.grandparentTitle, plexEpisode.year);
episode.season = plexEpisode.parentIndex;
episode.episode = plexEpisode.index;
episode.summary = plexEpisode.summary;
episode.rating = plexEpisode.rating;
if (plexEpisode.viewCount !== undefined) {
episode.views = plexEpisode.viewCount;
}
if (plexEpisode.originallyAvailableAt !== undefined) {
episode.airdate = new Date(plexEpisode.originallyAvailableAt)
}
return episode;
}
module.exports = convertPlexToEpisode;

View File

@@ -0,0 +1,15 @@
const Movie = require('src/plex/types/movie');
function convertPlexToMovie(plexMovie) {
const movie = new Movie(plexMovie.title, plexMovie.year);
movie.rating = plexMovie.rating;
movie.tagline = plexMovie.tagline;
if (plexMovie.summary !== undefined) {
movie.summary = plexMovie.summary;
}
return movie;
}
module.exports = convertPlexToMovie;

View File

@@ -0,0 +1,13 @@
const Show = require('src/plex/types/show');
function convertPlexToShow(plexShow) {
const show = new Show(plexShow.title, plexShow.year);
show.summary = plexShow.summary;
show.rating = plexShow.rating;
show.seasons = plexShow.childCount;
show.episodes = plexShow.leafCount;
return show;
}
module.exports = convertPlexToShow;

View File

@@ -0,0 +1,90 @@
const fetch = require('node-fetch')
const convertPlexToMovie = require('src/plex/convertPlexToMovie')
const convertPlexToShow = require('src/plex/convertPlexToShow')
const convertPlexToEpisode = require('src/plex/convertPlexToEpisode')
const { Movie, Show, Person } = require('src/tmdb/types');
// const { Movie, }
// TODO? import class definitions to compare types ?
// what would typescript do?
class Plex {
constructor(ip, port=32400) {
this.plexIP = ip
this.plexPort = port
}
matchTmdbAndPlexMedia(plex, tmdb) {
if (plex === undefined || tmdb === undefined)
return false
const sanitize = (string) => string.toLowerCase()
const matchTitle = sanitize(plex.title) === sanitize(tmdb.title)
const matchYear = plex.year === tmdb.year
return matchTitle && matchYear
}
existsInPlex(tmdbMovie) {
return this.search(tmdbMovie.title)
.then(plexMovies => plexMovies.some(plex => this.matchTmdbAndPlexMedia(plex, tmdbMovie)))
}
successfullResponse(response) {
const { status, statusText } = response
if (status === 200) {
return response.json()
} else {
throw { message: statusText, status: status }
}
}
search(query) {
const url = `http://${this.plexIP}:${this.plexPort}/hubs/search?query=${query}`
const options = {
timeout: 2000,
headers: { 'Accept': 'application/json' }
}
return fetch(url, options)
.then(this.successfullResponse)
.then(this.mapResults)
.catch(error => {
if (error.type === 'request-timeout') {
throw { message: 'Plex did not respond', status: 408, success: false }
}
throw error
})
}
mapResults(response) {
if (response === undefined || response.MediaContainer === undefined) {
console.log('response was not valid to map', response)
return []
}
return response.MediaContainer.Hub
.filter(category => category.size > 0)
.map(category => {
if (category.type === 'movie') {
return category.Metadata.map(movie => {
const ovie = Movie.convertFromPlexResponse(movie)
return ovie.createJsonResponse()
})
} else if (category.type === 'show') {
return category.Metadata.map(convertPlexToShow)
} else if (category.type === 'episode') {
return category.Metadata.map(convertPlexToEpisode)
}
})
.filter(result => result !== undefined)
.flat()
}
}
module.exports = Plex;

View File

@@ -3,6 +3,10 @@ const convertPlexToStream = require('src/plex/convertPlexToStream');
const rp = require('request-promise');
class PlexRepository {
constructor(plexIP) {
this.plexIP = plexIP;
}
inPlex(tmdbResult) {
return Promise.resolve()
.then(() => this.search(tmdbResult.title))
@@ -15,9 +19,10 @@ class PlexRepository {
}
search(query) {
console.log('searching:', query)
const queryUri = encodeURIComponent(query)
const uri = encodeURI(`http://${this.plexIP}:32400/search?query=${queryUri}`)
const options = {
uri: `http://10.0.0.44:32400/search?query=${query}`,
uri: uri,
headers: {
Accept: 'application/json',
},
@@ -26,6 +31,7 @@ class PlexRepository {
return rp(options)
.catch((error) => {
console.log(error)
throw new Error('Unable to search plex.')
})
.then(result => this.mapResults(result))
@@ -39,6 +45,7 @@ class PlexRepository {
tmdb.matchedInPlex = false
}
else {
// console.log('plex and tmdb:', plexResult, '\n', tmdb)
plexResult.results.map((plexItem) => {
if (tmdb.title === plexItem.title && tmdb.year === plexItem.year)
tmdb.matchedInPlex = true;
@@ -52,7 +59,6 @@ class PlexRepository {
mapResults(response) {
return Promise.resolve()
.then(() => {
console.log('plexResponse:', response)
if (!response.MediaContainer.hasOwnProperty('Metadata')) return [[], 0];
const mappedResults = response.MediaContainer.Metadata.filter((element) => {
@@ -65,7 +71,7 @@ class PlexRepository {
nowPlaying() {
const options = {
uri: 'http://10.0.0.44:32400/status/sessions',
uri: `http://${this.plexIP}:32400/status/sessions`,
headers: {
Accept: 'application/json',
},

View File

@@ -4,7 +4,7 @@ const configuration = require('src/config/configuration').getInstance();
const TMDB = require('src/tmdb/tmdb');
const establishedDatabase = require('src/database/database');
const plexRepository = new PlexRepository();
const plexRepository = new PlexRepository(configuration.get('plex', 'ip'));
const cache = new Cache();
const tmdb = new TMDB(cache, configuration.get('tmdb', 'apiKey'));
@@ -86,7 +86,11 @@ class RequestRepository {
}
throw new Error('Unable to fetch your requests');
})
.then((result) => { return result; });
.then((result) => {
// TODO do a correct mapping before sending, not just a dump of the database
result.map(item => item.poster = item.poster_path)
return result
});
}
updateRequestedById(id, type, status) {

View File

@@ -0,0 +1,16 @@
class Episode {
constructor(title, show, year) {
this.title = title;
this.show = show;
this.year = year;
this.season = null;
this.episode = null;
this.summary = null;
this.rating = null;
this.views = null;
this.aired = null;
this.type = 'episode';
}
}
module.exports = Episode;

View File

@@ -0,0 +1,12 @@
class Movie {
constructor(title, year) {
this.title = title;
this.year = year;
this.summary = null;
this.rating = null;
this.tagline = null;
this.type = 'movie';
}
}
module.exports = Movie;

View File

@@ -0,0 +1,12 @@
class Show {
constructor(title, year) {
this.title = title;
this.year = year;
this.summary = null;
this.rating = null;
this.seasons = null;
this.episodes = null;
}
}
module.exports = Show;

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

@@ -0,0 +1,169 @@
const assert = require('assert')
const configuration = require('src/config/configuration').getInstance();
const Cache = require('src/tmdb/cache');
const TMDB = require('src/tmdb/tmdb');
const cache = new Cache();
const tmdb = new TMDB(cache, configuration.get('tmdb', 'apiKey'));
const establishedDatabase = require('src/database/database');
const utils = require('./utils');
class RequestRepository {
constructor(database) {
this.database = database || establishedDatabase;
this.queries = {
add: 'insert into requests (id,title,year,poster_path,background_path,requested_by,ip,user_agent,type) values(?,?,?,?,?,?,?,?,?)',
fetchAll: 'select * from requests where status != "downloaded" order by date desc LIMIT 25 OFFSET ?*25-25',
fetchAllFilteredStatus: 'select * from requests where status = ? order by date desc LIMIT 25 offset ?*25-25',
totalRequests: 'select count(*) as totalRequests from requests where status != "downloaded"',
totalRequestsFilteredStatus: 'select count(*) as totalRequests from requests where status = ?',
fetchAllSort: `select id, type from request order by ? ?`,
fetchAllFilter: `select id, type from request where ? is "?"`,
fetchAllQuery: `select id, type from request where title like "%?%" or year like "%?%"`,
fetchAllFilterAndSort: `select id, type from request where ? is "?" order by ? ?`,
downloaded: '(select status from requests where id is request.id and type is request.type limit 1)',
// deluge: '(select status from deluge_torrent where id is request.id and type is request.type limit 1)',
// fetchAllFilterStatus: 'select * from request where '
readWithoutUserData: 'select id, title, year, type, status, date from requests where id is ? and type is ?',
read: 'select id, title, year, type, status, requested_by, ip, date, user_agent from requests where id is ? and type is ?'
};
}
sortAndFilterToDbQuery(by, direction, filter, query) {
let dbQuery = undefined;
if (query !== undefined) {
const dbParams = [query, query];
const dbquery = this.queries.fetchAllQuery
dbQuery = dbquery.split('').map((char) => char === '?' ? dbParams.shift() : char).join('')
}
else if (by !== undefined && filter !== undefined) {
const paramToColumnAndValue = {
movie: ['type', 'movie'],
show: ['type', 'show']
}
const dbParams = paramToColumnAndValue[filter].concat([by, direction]);
const query = this.queries.fetchAllFilterAndSort;
dbQuery = query.split('').map((char) => char === '?' ? dbParams.shift() : char).join('')
}
else if (by !== undefined) {
const dbParams = [by, direction];
const query = this.queries.fetchAllSort;
dbQuery = query.split('').map((char) => char === '?' ? dbParams.shift() : char).join('')
}
else if (filter !== undefined) {
const paramToColumnAndValue = {
movie: ['type', 'movie'],
show: ['type', 'show'],
downloaded: [this.queries.downloaded, 'downloaded']
// downloading: [this.database.delugeStatus, 'downloading']
}
const dbParams = paramToColumnAndValue[filter]
const query = this.queries.fetchAllFilter;
dbQuery = query.split('').map((char) => char === '?' ? dbParams.shift() : char).join('')
}
else {
dbQuery = this.queries.fetchAll;
}
return dbQuery
}
mapToTmdbByType(rows) {
return rows.map((row) => {
if (row.type === 'movie')
return tmdb.movieInfo(row.id)
else if (row.type === 'show')
return tmdb.showInfo(row.id)
})
}
/**
* Add tmdb movie|show to requests
* @param {tmdb} tmdb class of movie|show to add
* @returns {Promise}
*/
requestFromTmdb(tmdb, ip, user_agent, user) {
return Promise.resolve()
.then(() => this.database.get(this.queries.read, [tmdb.id, tmdb.type]))
.then(row => assert.equal(row, undefined, 'Id has already been requested'))
.then(() => this.database.run(this.queries.add, [tmdb.id, tmdb.title, tmdb.year, tmdb.poster, tmdb.backdrop, user, ip, user_agent, tmdb.type]))
.catch((error) => {
if (error.name === 'AssertionError' || error.message.endsWith('been requested')) {
throw new Error('This id is already requested', error.message);
}
console.log('Error @ request.addTmdb:', error);
throw new Error('Could not add request');
});
}
/**
* Get request item by id
* @param {String} id
* @param {String} type
* @returns {Promise}
*/
getRequestByIdAndType(id, type) {
return this.database.get(this.queries.readWithoutUserData, [id, type])
.then(row => {
assert(row, 'Could not find request item with that id and type')
return {
id: row.id,
title: row.title,
year: row.year,
type: row.type,
status: row.status,
requested_date: new Date(row.date)
}
})
}
/**
* Fetch all requests with optional sort and filter params
* @param {String} what we are sorting by
* @param {String} direction that can be either 'asc' or 'desc', default 'asc'.
* @param {String} params to filter by
* @param {String} query param to filter result on. Filters on title and year
* @returns {Promise}
*/
fetchAll(page=1, sort_by=undefined, sort_direction='asc', filter=undefined, query=undefined) {
// TODO implemented sort and filter
page = parseInt(page)
let fetchQuery = this.queries.fetchAll
let fetchTotalResults = this.queries.totalRequests
let fetchParams = [page]
if (filter && (filter === 'downloading' || filter === 'downloaded' || filter === 'requested')) {
console.log('tes')
fetchQuery = this.queries.fetchAllFilteredStatus
fetchTotalResults = this.queries.totalRequestsFilteredStatus
fetchParams = [filter, page]
} else {
filter = undefined
}
return Promise.resolve()
.then((dbQuery) => this.database.all(fetchQuery, fetchParams))
.then(async (rows) => {
const sqliteResponse = await this.database.get(fetchTotalResults, filter ? filter : undefined)
const totalRequests = sqliteResponse['totalRequests']
const totalPages = Math.ceil(totalRequests / 26)
return [ rows.map(item => {
item.poster = item.poster_path; delete item.poster_path;
item.backdrop = item.background_path; delete item.background_path;
return item
}), totalPages, totalRequests ]
return Promise.all(this.mapToTmdbByType(rows))
})
.then(([result, totalPages, totalRequests]) => Promise.resolve({
results: result, total_results: totalRequests, page: page, total_pages: totalPages
}))
.catch(error => { console.log(error);throw error })
}
}
module.exports = RequestRepository;

View File

@@ -0,0 +1,34 @@
// TODO : test title and date are valid matches to columns in the database
const validSortParams = ['title', 'date']
const validSortDirs = ['asc', 'desc']
const validFilterParams = ['movie', 'show', 'seeding', 'downloading', 'paused', 'finished', 'downloaded']
function validSort(by, direction) {
return new Promise((resolve, reject) => {
if (by === undefined) {
resolve()
}
if (validSortParams.includes(by) && validSortDirs.includes(direction)) {
resolve()
} else {
reject(new Error(`invalid sort parameter, must be of: ${validSortParams} with optional sort directions: ${validSortDirs} appended with ':'`))
}
});
}
function validFilter(filter_param) {
return new Promise((resolve, reject) => {
if (filter_param === undefined) {
resolve()
}
if (filter_param && validFilterParams.includes(filter_param)) {
resolve()
} else {
reject(new Error(`filter parameteres must be of type: ${validFilterParams}`))
}
});
}
module.exports = { validSort, validFilter }

View File

@@ -0,0 +1,3 @@
{
"presets": ["@babel/preset-env"]
}

View File

@@ -18,12 +18,12 @@ class Cache {
* @returns {Object}
*/
get(key) {
return Promise.resolve()
.then(() => this.database.get(this.queries.read, [key]))
.then((row) => {
assert(row, 'Could not find cache enrty with that key.');
return JSON.parse(row.value);
});
return Promise.resolve()
.then(() => this.database.get(this.queries.read, [key]))
.then(row => {
assert(row, 'Could not find cache entry with that key.');
return JSON.parse(row.value);
})
}
/**

View File

@@ -1,44 +0,0 @@
const TMDB = require('src/media_classes/tmdb');
function translateYear(tmdbReleaseDate) {
return new Date(tmdbReleaseDate).getFullYear();
}
function translateGenre(tmdbGenres) {
return tmdbGenres.map(genre => genre.name);
}
function convertType(tmdbType) {
if (tmdbType === 'tv') return 'show';
return undefined;
}
function convertTmdbToSeasoned(tmdb, manualType = undefined) {
const title = tmdb.title || tmdb.name;
const year = translateYear(tmdb.release_date || tmdb.first_air_date);
const type = manualType || convertType(tmdb.media_type) || 'movie';
const id = tmdb.id;
const summary = tmdb.overview;
const poster_path = tmdb.poster_path;
const background_path = tmdb.backdrop_path;
const popularity = tmdb.popularity;
const score = tmdb.vote_average;
// const genres = translateGenre(tmdb.genres);
const release_status = tmdb.status;
const tagline = tmdb.tagline;
const seasons = tmdb.number_of_seasons;
const episodes = tmdb.episodes;
const seasoned = new TMDB(
title, year, type, id, summary, poster_path, background_path,
popularity, score, release_status, tagline, seasons, episodes
);
// seasoned.print()
return seasoned;
}
module.exports = convertTmdbToSeasoned;

View File

@@ -1,114 +1,241 @@
const moviedb = require('km-moviedb');
const convertTmdbToSeasoned = require('src/tmdb/convertTmdbToSeasoned');
const TMDB_METHODS = {
upcoming: { movie: 'miscUpcomingMovies' },
discover: { movie: 'discoverMovie', show: 'discoverTv' },
popular: { movie: 'miscPopularMovies', show: 'miscPopularTvs' },
nowplaying: { movie: 'miscNowPlayingMovies', show: 'tvOnTheAir' },
similar: { movie: 'movieSimilar', show: 'tvSimilar' },
search: { movie: 'searchMovie', show: 'searchTv', multi: 'searchMulti' },
info: { movie: 'movieInfo', show: 'tvInfo' }
};
const { Movie, Show, Person, Credits, ReleaseDates } = require('src/tmdb/types');
// const { tmdbInfo } = require('src/tmdb/types')
class TMDB {
constructor(cache, apiKey, tmdbLibrary) {
this.cache = cache;
this.tmdbLibrary = tmdbLibrary || moviedb(apiKey);
this.cacheTags = {
search: 'se',
info: 'i',
upcoming: 'u',
discover: 'd',
popular: 'p',
nowplaying: 'n',
similar: 'si',
};
}
constructor(cache, apiKey, tmdbLibrary) {
this.cache = cache;
this.tmdbLibrary = tmdbLibrary || moviedb(apiKey);
this.cacheTags = {
multiSearch: 'mus',
movieSearch: 'mos',
showSearch: 'ss',
personSearch: 'ps',
movieInfo: 'mi',
movieCredits: 'mc',
movieReleaseDates: 'mrd',
showInfo: 'si',
showCredits: 'sc',
personInfo: 'pi',
miscNowPlayingMovies: 'npm',
miscPopularMovies: 'pm',
miscTopRatedMovies: 'tpm',
miscUpcomingMovies: 'um',
tvOnTheAir: 'toa',
miscPopularTvs: 'pt',
miscTopRatedTvs: 'trt',
};
}
/**
/**
* Retrieve a specific movie by id from TMDB.
* @param {Number} identifier of the movie you want to retrieve
* @param {String} type filter results by type (default movie).
* @param {Boolean} add credits (cast & crew) for movie
* @param {Boolean} add release dates for every country
* @returns {Promise} succeeds if movie was found
*/
lookup(identifier, type = 'movie') {
const query = { id: identifier };
const cacheKey = `${this.cacheTags.info}:${type}:${identifier}`;
return Promise.resolve()
.then(() => this.cache.get(cacheKey))
.catch(() => this.tmdb(TMDB_METHODS['info'][type], query))
.catch(() => { throw new Error('Could not find a movie with that id.'); })
.then(response => this.cache.set(cacheKey, response))
.then((response) => {
try {
return convertTmdbToSeasoned(response, type);
} catch (parseError) {
console.error(parseError);
throw new Error('Could not parse movie.');
}
});
}
movieInfo(identifier) {
const query = { id: identifier };
const cacheKey = `${this.cacheTags.movieInfo}:${identifier}`;
/**
* Retrive search results from TMDB.
return this.cache.get(cacheKey)
.catch(() => this.tmdb('movieInfo', query))
.catch(tmdbError => tmdbErrorResponse(tmdbError, 'movie info'))
.then(movie => this.cache.set(cacheKey, movie, 1))
.then(movie => Movie.convertFromTmdbResponse(movie))
}
/**
* Retrieve credits for a movie
* @param {Number} identifier of the movie to get credits for
* @returns {Promise} movie cast object
*/
movieCredits(identifier) {
const query = { id: identifier }
const cacheKey = `${this.cacheTags.movieCredits}:${identifier}`
return this.cache.get(cacheKey)
.catch(() => this.tmdb('movieCredits', query))
.catch(tmdbError => tmdbErrorResponse(tmdbError, 'movie credits'))
.then(credits => this.cache.set(cacheKey, credits, 1))
.then(credits => Credits.convertFromTmdbResponse(credits))
}
/**
* Retrieve release dates for a movie
* @param {Number} identifier of the movie to get release dates for
* @returns {Promise} movie release dates object
*/
movieReleaseDates(identifier) {
const query = { id: identifier }
const cacheKey = `${this.cacheTags.movieReleaseDates}:${identifier}`
return this.cache.get(cacheKey)
.catch(() => this.tmdb('movieReleaseDates', query))
.catch(tmdbError => tmdbErrorResponse(tmdbError, 'movie release dates'))
.then(releaseDates => this.cache.set(cacheKey, releaseDates, 1))
.then(releaseDates => ReleaseDates.convertFromTmdbResponse(releaseDates))
}
/**
* Retrieve a specific show by id from TMDB.
* @param {Number} identifier of the show you want to retrieve
* @param {String} type filter results by type (default show).
* @returns {Promise} succeeds if show was found
*/
showInfo(identifier) {
const query = { id: identifier };
const cacheKey = `${this.cacheTags.showInfo}:${identifier}`;
return this.cache.get(cacheKey)
.catch(() => this.tmdb('tvInfo', query))
.catch(tmdbError => tmdbErrorResponse(tmdbError, 'tv info'))
.then(show => this.cache.set(cacheKey, show, 1))
.then(show => Show.convertFromTmdbResponse(show))
}
showCredits(identifier) {
const query = { id: identifier }
const cacheKey = `${this.cacheTags.showCredits}:${identifier}`
return this.cache.get(cacheKey)
.catch(() => this.tmdb('tvCredits', query))
.catch(tmdbError => tmdbErrorResponse(tmdbError, 'show credits'))
.then(credits => this.cache.set(cacheKey, credits, 1))
.then(credits => Credits.convertFromTmdbResponse(credits))
}
/**
* Retrieve a specific person id from TMDB.
* @param {Number} identifier of the person you want to retrieve
* @param {String} type filter results by type (default person).
* @returns {Promise} succeeds if person was found
*/
personInfo(identifier) {
const query = { id: identifier };
const cacheKey = `${this.cacheTags.personInfo}:${identifier}`;
return this.cache.get(cacheKey)
.catch(() => this.tmdb('personInfo', query))
.catch(tmdbError => tmdbErrorResponse(tmdbError, 'person info'))
.then(person => this.cache.set(cacheKey, person, 1))
.then(person => Person.convertFromTmdbResponse(person))
}
multiSearch(search_query, page=1) {
const query = { query: search_query, page: page };
const cacheKey = `${this.cacheTags.multiSearch}:${page}:${search_query}`;
return this.cache.get(cacheKey)
.catch(() => this.tmdb('searchMulti', query))
.catch(tmdbError => tmdbErrorResponse(tmdbError, 'search results'))
.then(response => this.cache.set(cacheKey, response, 1))
.then(response => this.mapResults(response));
}
/**
* Retrive movie search results from TMDB.
* @param {String} text query you want to search for
* @param {Number} page representing pagination of results
* @param {String} type filter results by type (default multi)
* @returns {Promise} dict with query results, current page and total_pages
*/
search(text, page = 1, type = 'multi') {
const query = { query: text, page: page };
const cacheKey = `${this.cacheTags.search}:${page}:${type}:${text}`;
return Promise.resolve()
.then(() => this.cache.get(cacheKey))
.catch(() => this.tmdb(TMDB_METHODS['search'][type], query))
.catch(() => { throw new Error('Could not search for movies/shows at tmdb.'); })
.then(response => this.cache.set(cacheKey, response))
.then(response => this.mapResults(response));
}
movieSearch(query, page=1) {
const tmdbquery = { query: query, page: page };
const cacheKey = `${this.cacheTags.movieSearch}:${page}:${query}`;
/**
* Fetches a given list from tmdb.
* @param {String} listName Name of list
* @param {String} type filter results by type (default movie)
return this.cache.get(cacheKey)
.catch(() => this.tmdb('searchMovie', tmdbquery))
.catch(tmdbError => tmdbErrorResponse(tmdbError, 'movie search results'))
.then(response => this.cache.set(cacheKey, response, 1))
.then(response => this.mapResults(response, 'movie'))
}
/**
* Retrive show search results from TMDB.
* @param {String} text query you want to search for
* @param {Number} page representing pagination of results
* @returns {Promise} dict with query results, current page and total_pages
*/
listSearch(listName, type = 'movie', page = '1') {
const query = { page: page };
console.log(query);
const cacheKey = `${this.cacheTags[listName]}:${type}:${page}`;
return Promise.resolve()
.then(() => this.cache.get(cacheKey))
.catch(() => this.tmdb(TMDB_METHODS[listName][type], query))
.catch(() => { throw new Error('Error fetching list from tmdb.'); })
.then(response => this.cache.set(cacheKey, response))
.then(response => this.mapResults(response, type));
}
showSearch(query, page=1) {
const tmdbquery = { query: query, page: page };
const cacheKey = `${this.cacheTags.showSearch}:${page}:${query}`;
/**
return this.cache.get(cacheKey)
.catch(() => this.tmdb('searchTv', tmdbquery))
.catch(tmdbError => tmdbErrorResponse(tmdbError, 'tv search results'))
.then(response => this.cache.set(cacheKey, response, 1))
.then(response => this.mapResults(response, 'show'))
}
/**
* Retrive person search results from TMDB.
* @param {String} text query you want to search for
* @param {Number} page representing pagination of results
* @returns {Promise} dict with query results, current page and total_pages
*/
personSearch(query, page=1) {
const tmdbquery = { query: query, page: page, include_adult: true };
const cacheKey = `${this.cacheTags.personSearch}:${page}:${query}`;
return this.cache.get(cacheKey)
.catch(() => this.tmdb('searchPerson', tmdbquery))
.catch(tmdbError => tmdbErrorResponse(tmdbError, 'person search results'))
.then(response => this.cache.set(cacheKey, response, 1))
.then(response => this.mapResults(response, 'person'))
}
movieList(listname, page = 1) {
const query = { page: page };
const cacheKey = `${this.cacheTags[listname]}:${page}`;
return this.cache.get(cacheKey)
.catch(() => this.tmdb(listname, query))
.catch(tmdbError => this.tmdbErrorResponse(tmdbError, 'movie list ' + listname))
.then(response => this.cache.set(cacheKey, response, 1))
.then(response => this.mapResults(response, 'movie'))
}
showList(listname, page = 1) {
const query = { page: page };
const cacheKey = `${this.cacheTags[listname]}:${page}`;
return this.cache.get(cacheKey)
.catch(() => this.tmdb(listname, query))
.catch(tmdbError => this.tmdbErrorResponse(tmdbError, 'show list ' + listname))
.then(response => this.cache.set(cacheKey, response, 1))
.then(response => this.mapResults(response, 'show'))
}
/**
* Maps our response from tmdb api to a movie/show object.
* @param {String} response from tmdb.
* @param {String} The type declared in listSearch.
* @returns {Promise} dict with tmdb results, mapped as movie/show objects.
*/
mapResults(response, type) {
console.log(response.page);
return Promise.resolve()
.then(() => {
const mappedResults = response.results.filter((element) => {
return (element.media_type === 'movie' || element.media_type === 'tv' || element.media_type === undefined);
}).map((element) => convertTmdbToSeasoned(element, type));
return {
results: mappedResults,
page: response.page,
total_pages: response.total_pages,
total_results: response.total_results
}
})
.catch((error) => { throw new Error(error); });
}
mapResults(response, type=undefined) {
// console.log(response.results)
// response.results.map(te => console.table(te))
let results = response.results.map(result => {
if (type === 'movie' || result.media_type === 'movie') {
const movie = Movie.convertFromTmdbResponse(result)
return movie.createJsonResponse()
} else if (type === 'show' || result.media_type === 'tv') {
const show = Show.convertFromTmdbResponse(result)
return show.createJsonResponse()
} else if (type === 'person' || result.media_type === 'person') {
const person = Person.convertFromTmdbResponse(result)
return person.createJsonResponse()
}
})
return {
results: results,
page: response.page,
total_results: response.total_results,
total_pages: response.total_pages
}
}
/**
* Wraps moviedb library to support Promises.
@@ -122,7 +249,7 @@ class TMDB {
if (error) {
return reject(error);
}
return resolve(reponse);
resolve(reponse);
};
if (!argument) {
@@ -132,6 +259,28 @@ class TMDB {
}
});
}
}
function tmdbErrorResponse(error, typeString=undefined) {
if (error.status === 404) {
let message = error.response.body.status_message;
throw {
status: 404,
message: message.slice(0, -1) + " in tmdb."
}
} else if (error.status === 401) {
throw {
status: 401,
message: error.response.body.status_message
}
}
throw {
status: 500,
message: `An unexpected error occured while fetching ${typeString} from tmdb`
}
}
module.exports = TMDB;

View File

@@ -0,0 +1,7 @@
import { Movie } from './types'
Movie('str', 123)
module.exports = TMDB;

View File

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

View File

@@ -0,0 +1,64 @@
interface Movie {
adult: boolean;
backdrop: string;
genres: Genre[];
id: number;
imdb_id: number;
overview: string;
popularity: number;
poster: string;
release_date: Date;
rank: number;
runtime: number;
status: string;
tagline: string;
title: string;
vote_count: number;
}
interface Show {
adult: boolean;
backdrop: string;
episodes: number;
genres: Genre[];
id: number;
imdb_id: number;
overview: string;
popularity: number;
poster: string;
rank: number;
runtime: number;
seasons: number;
status: string;
tagline: string;
title: string;
vote_count: number;
}
interface Person {
birthday: Date;
deathday: Date;
id: number;
known_for: string;
name: string;
poster: string;
}
interface SearchResult {
adult: boolean;
backdrop_path: string;
id: number;
original_title: string;
release_date: Date;
poster_path: string;
popularity: number;
vote_average: number;
vote_counte: number;
}
interface Genre {
id: number;
name: string;
}
export { Movie, Show, Person, Genre }

View File

@@ -0,0 +1,75 @@
class Credits {
constructor(id, cast=[], crew=[]) {
this.id = id;
this.cast = cast;
this.crew = crew;
this.type = 'credits';
}
static convertFromTmdbResponse(response) {
const { id, cast, crew } = response;
const allCast = cast.map(cast =>
new CastMember(cast.character, cast.gender, cast.id, cast.name, cast.profile_path))
const allCrew = crew.map(crew =>
new CrewMember(crew.department, crew.gender, crew.id, crew.job, crew.name, crew.profile_path))
return new Credits(id, allCast, allCrew)
}
createJsonResponse() {
return {
id: this.id,
cast: this.cast.map(cast => cast.createJsonResponse()),
crew: this.crew.map(crew => crew.createJsonResponse())
}
}
}
class CastMember {
constructor(character, gender, id, name, profile_path) {
this.character = character;
this.gender = gender;
this.id = id;
this.name = name;
this.profile_path = profile_path;
this.type = 'cast member';
}
createJsonResponse() {
return {
character: this.character,
gender: this.gender,
id: this.id,
name: this.name,
profile_path: this.profile_path,
type: this.type
}
}
}
class CrewMember {
constructor(department, gender, id, job, name, profile_path) {
this.department = department;
this.gender = gender;
this.id = id;
this.job = job;
this.name = name;
this.profile_path = profile_path;
this.type = 'crew member';
}
createJsonResponse() {
return {
department: this.department,
gender: this.gender,
id: this.id,
job: this.job,
name: this.name,
profile_path: this.profile_path,
type: this.type
}
}
}
module.exports = Credits;

View File

@@ -0,0 +1,62 @@
class Movie {
constructor(id, title, year=undefined, overview=undefined, poster=undefined, backdrop=undefined,
releaseDate=undefined, rating=undefined, genres=undefined, productionStatus=undefined,
tagline=undefined, runtime=undefined, imdb_id=undefined, popularity=undefined) {
this.id = id;
this.title = title;
this.year = year;
this.overview = overview;
this.poster = poster;
this.backdrop = backdrop;
this.releaseDate = releaseDate;
this.rating = rating;
this.genres = genres;
this.productionStatus = productionStatus;
this.tagline = tagline;
this.runtime = runtime;
this.imdb_id = imdb_id;
this.popularity = popularity;
this.type = 'movie';
}
static convertFromTmdbResponse(response) {
const { id, title, release_date, overview, poster_path, backdrop_path, vote_average, genres, status,
tagline, runtime, imdb_id, popularity } = response;
const releaseDate = new Date(release_date);
const year = releaseDate.getFullYear();
const genreNames = genres ? genres.map(g => g.name) : undefined
return new Movie(id, title, year, overview, poster_path, backdrop_path, releaseDate, vote_average, genreNames, status,
tagline, runtime, imdb_id, popularity)
}
static convertFromPlexResponse(response) {
// console.log('response', response)
const { title, year, rating, tagline, summary } = response;
const _ = undefined
return new Movie(null, title, year, summary, _, _, _, rating, _, _, tagline)
}
createJsonResponse() {
return {
id: this.id,
title: this.title,
year: this.year,
overview: this.overview,
poster: this.poster,
backdrop: this.backdrop,
release_date: this.releaseDate,
rating: this.rating,
genres: this.genres,
production_status: this.productionStatus,
tagline: this.tagline,
runtime: this.runtime,
imdb_id: this.imdb_id,
type: this.type
}
}
}
module.exports = Movie;

View File

@@ -0,0 +1,37 @@
class Person {
constructor(id, name, poster=undefined, birthday=undefined, deathday=undefined,
adult=undefined, knownForDepartment=undefined) {
this.id = id;
this.name = name;
this.poster = poster;
this.birthday = birthday;
this.deathday = deathday;
this.adult = adult;
this.knownForDepartment = knownForDepartment;
this.type = 'person';
}
static convertFromTmdbResponse(response) {
const { id, name, poster, birthday, deathday, adult, known_for_department } = response;
const birthDay = new Date(birthday)
const deathDay = deathday ? new Date(deathday) : null
return new Person(id, name, poster, birthDay, deathDay, adult, known_for_department)
}
createJsonResponse() {
return {
id: this.id,
name: this.name,
poster: this.poster,
birthday: this.birthday,
deathday: this.deathday,
known_for_department: this.knownForDepartment,
adult: this.adult,
type: this.type
}
}
}
module.exports = Person;

View File

@@ -0,0 +1,78 @@
class ReleaseDates {
constructor(id, releases) {
this.id = id;
this.releases = releases;
}
static convertFromTmdbResponse(response) {
const { id, results } = response;
const releases = results.map(countryRelease =>
new Release(
countryRelease.iso_3166_1,
countryRelease.release_dates.map(rd => new ReleaseDate(rd.certification, rd.iso_639_1, rd.release_date, rd.type, rd.note))
))
return new ReleaseDates(id, releases)
}
createJsonResponse() {
return {
id: this.id,
results: this.releases.map(release => release.createJsonResponse())
}
}
}
class Release {
constructor(country, releaseDates) {
this.country = country;
this.releaseDates = releaseDates;
}
createJsonResponse() {
return {
country: this.country,
release_dates: this.releaseDates.map(releaseDate => releaseDate.createJsonResponse())
}
}
}
class ReleaseDate {
constructor(certification, language, releaseDate, type, note) {
this.certification = certification;
this.language = language;
this.releaseDate = releaseDate;
this.type = this.releaseTypeLookup(type);
this.note = note;
}
releaseTypeLookup(releaseTypeKey) {
const releaseTypeEnum = {
1: 'Premier',
2: 'Limited theatrical',
3: 'Theatrical',
4: 'Digital',
5: 'Physical',
6: 'TV'
}
if (releaseTypeKey <= Object.keys(releaseTypeEnum).length) {
return releaseTypeEnum[releaseTypeKey]
} else {
// TODO log | Release type not defined, does this need updating?
return null
}
}
createJsonResponse() {
return {
certification: this.certification,
language: this.language,
release_date: this.releaseDate,
type: this.type,
note: this.note
}
}
}
module.exports = ReleaseDates;

View File

@@ -0,0 +1,50 @@
class Show {
constructor(id, title, year=undefined, overview=undefined, poster=undefined, backdrop=undefined,
seasons=undefined, episodes=undefined, rank=undefined, genres=undefined, status=undefined,
runtime=undefined) {
this.id = id;
this.title = title;
this.year = year;
this.overview = overview;
this.poster = poster;
this.backdrop = backdrop;
this.seasons = seasons;
this.episodes = episodes;
this.rank = rank;
this.genres = genres;
this.productionStatus = status;
this.runtime = runtime;
this.type = 'show';
}
static convertFromTmdbResponse(response) {
const { id, name, first_air_date, overview, poster_path, backdrop_path, number_of_seasons, number_of_episodes,
rank, genres, status, episode_run_time, popularity } = response;
const year = new Date(first_air_date).getFullYear()
const genreNames = genres ? genres.map(g => g.name) : undefined
return new Show(id, name, year, overview, poster_path, backdrop_path, number_of_seasons, number_of_episodes,
rank, genreNames, status, episode_run_time, popularity)
}
createJsonResponse() {
return {
id: this.id,
title: this.title,
year: this.year,
overview: this.overview,
poster: this.poster,
backdrop: this.backdrop,
seasons: this.seasons,
episodes: this.episodes,
rank: this.rank,
genres: this.genres,
production_status: this.productionStatus,
runtime: this.runtime,
type: this.type
}
}
}
module.exports = Show;

View File

@@ -2,36 +2,44 @@ const User = require('src/user/user');
const jwt = require('jsonwebtoken');
class Token {
constructor(user) {
this.user = user;
}
constructor(user, admin=false) {
this.user = user;
this.admin = admin;
}
/**
/**
* Generate a new token.
* @param {String} secret a cipher of the token
* @returns {String}
*/
toString(secret) {
return jwt.sign({ username: this.user.username }, secret);
}
toString(secret) {
const username = this.user.username;
const admin = this.admin;
let data = { username }
/**
if (admin)
data = { ...data, admin }
return jwt.sign(data, secret, { expiresIn: '90d' });
}
/**
* Decode a token.
* @param {Token} jwtToken an encrypted token
* @param {String} secret a cipher of the token
* @returns {Token}
*/
static fromString(jwtToken, secret) {
let username = null;
static fromString(jwtToken, secret) {
let username = null;
try {
username = jwt.verify(jwtToken, secret).username;
} catch (error) {
throw new Error('The token is invalid.');
}
const user = new User(username);
return new Token(user);
}
const token = jwt.verify(jwtToken, secret, { clockTolerance: 10000 })
if (token.username === undefined || token.username === null)
throw new Error('Malformed token')
username = token.username
const user = new User(username)
return new Token(user)
}
}
module.exports = Token;

View File

@@ -26,6 +26,7 @@ class UserRepository {
if (error.name === 'AssertionError' || error.message.endsWith('user_name')) {
throw new Error('That username is already registered');
}
throw Error(error)
});
}

View File

@@ -1,73 +1,72 @@
const bcrypt = require('bcrypt-nodejs');
const bcrypt = require('bcrypt');
const UserRepository = require('src/user/userRepository');
class UserSecurity {
constructor(database) {
this.userRepository = new UserRepository(database);
}
constructor(database) {
this.userRepository = new UserRepository(database);
}
/**
/**
* Create a new user in PlanFlix.
* @param {User} user the new user you want to create
* @param {String} clearPassword a password of the user
* @returns {Promise}
*/
createNewUser(user, clearPassword) {
if (user.username.trim() === '') {
throw new Error('The username is empty.');
} else if (clearPassword.trim() === '') {
throw new Error('The password is empty.');
} else {
return Promise.resolve()
.then(() => this.userRepository.create(user))
.then(() => UserSecurity.hashPassword(clearPassword))
.then(hash => this.userRepository.changePassword(user, hash));
}
}
createNewUser(user, clearPassword) {
if (user.username.trim() === '') {
throw new Error('The username is empty.');
} else if (clearPassword.trim() === '') {
throw new Error('The password is empty.');
} else {
return Promise.resolve()
.then(() => this.userRepository.create(user))
.then(() => UserSecurity.hashPassword(clearPassword))
.then(hash => this.userRepository.changePassword(user, hash))
}
}
/**
/**
* Login into PlanFlix.
* @param {User} user the user you want to login
* @param {String} clearPassword the user's password
* @returns {Promise}
*/
login(user, clearPassword) {
return Promise.resolve()
.then(() => this.userRepository.retrieveHash(user))
.then(hash => UserSecurity.compareHashes(hash, clearPassword))
.catch(() => { throw new Error('Wrong username or password.'); });
}
login(user, clearPassword) {
return Promise.resolve()
.then(() => this.userRepository.retrieveHash(user))
.then(hash => UserSecurity.compareHashes(hash, clearPassword))
.catch(() => { throw new Error('Incorrect username or password.'); });
}
/**
* Compare between a password and a hash password from database.
* @param {String} hash the hash password from database
* @param {String} clearPassword the user's password
* @returns {Promise}
*/
static compareHashes(hash, clearPassword) {
return new Promise((resolve, reject) => {
bcrypt.compare(clearPassword, hash, (error, matches) => {
if (matches === true) {
resolve();
} else {
reject();
}
});
* Compare between a password and a hash password from database.
* @param {String} hash the hash password from database
* @param {String} clearPassword the user's password
* @returns {Promise}
*/
static compareHashes(hash, clearPassword) {
return new Promise((resolve, reject) => {
bcrypt.compare(clearPassword, hash, (error, match) => {
if (match)
resolve()
reject()
});
}
});
}
/**
/**
* Hashes a password.
* @param {String} clearPassword the user's password
* @returns {Promise}
*/
static hashPassword(clearPassword) {
return new Promise((resolve) => {
bcrypt.hash(clearPassword, null, null, (error, hash) => {
resolve(hash);
});
static hashPassword(clearPassword) {
return new Promise((resolve) => {
const saltRounds = 10;
bcrypt.hash(clearPassword, saltRounds, (error, hash) => {
resolve(hash);
});
}
});
}
}
module.exports = UserSecurity;

View File

@@ -6,16 +6,14 @@ const mustBeAuthenticated = require('./middleware/mustBeAuthenticated');
const mustBeAdmin = require('./middleware/mustBeAdmin');
const configuration = require('src/config/configuration').getInstance();
const listController = require('./controllers/list/listController');
// TODO: Have our raven router check if there is a value, if not don't enable raven.
Raven.config(configuration.get('raven', 'DSN')).install();
const app = express(); // define our app using express
app.use(Raven.requestHandler());
// this will let us get the data from a POST
// configure app to use bodyParser()
app.use(bodyParser.json());
// router.use(bodyParser.urlencoded({ extended: true }));
const router = express.Router();
const allowedOrigins = ['https://kevinmidboe.com', 'http://localhost:8080'];
@@ -30,11 +28,9 @@ router.use(tokenToUser);
// TODO: Should have a separate middleware/router for handling headers.
router.use((req, res, next) => {
// TODO add logging of all incoming
console.log('Request: ', req.originalUrl);
const origin = req.headers.origin;
const origin = req.headers.origin;
if (allowedOrigins.indexOf(origin) > -1) {
console.log('allowed');
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');
@@ -67,19 +63,50 @@ router.get('/v1/seasoned/all', require('./controllers/seasoned/readStrays.js'));
router.get('/v1/seasoned/:strayId', require('./controllers/seasoned/strayById.js'));
router.post('/v1/seasoned/verify/:strayId', require('./controllers/seasoned/verifyStray.js'));
router.get('/v2/search/', require('./controllers/search/multiSearch.js'));
router.get('/v2/search/movie', require('./controllers/search/movieSearch.js'));
router.get('/v2/search/show', require('./controllers/search/showSearch.js'));
router.get('/v2/search/person', require('./controllers/search/personSearch.js'));
router.get('/v2/movie/now_playing', listController.nowPlayingMovies);
router.get('/v2/movie/popular', listController.popularMovies);
router.get('/v2/movie/top_rated', listController.topRatedMovies);
router.get('/v2/movie/upcoming', listController.upcomingMovies);
router.get('/v2/show/now_playing', listController.nowPlayingShows);
router.get('/v2/show/popular', listController.popularShows);
router.get('/v2/show/top_rated', listController.topRatedShows);
router.get('/v2/movie/:id/credits', require('./controllers/movie/credits.js'));
router.get('/v2/movie/:id/release_dates', require('./controllers/movie/releaseDates.js'));
router.get('/v2/show/:id/credits', require('./controllers/show/credits.js'));
router.get('/v2/movie/:id', require('./controllers/movie/info.js'));
router.get('/v2/show/:id', require('./controllers/show/info.js'));
router.get('/v2/person/:id', require('./controllers/person/info.js'));
/**
* Plex
*/
router.get('/v2/plex/search', require('./controllers/plex/search'));
/**
* List
*/
router.get('/v1/plex/search', require('./controllers/plex/searchMedia.js'));
router.get('/v1/plex/playing', require('./controllers/plex/plexPlaying.js'));
router.get('/v1/plex/request', require('./controllers/plex/searchRequest.js'));
router.get('/v1/plex/request/:mediaId', require('./controllers/plex/readRequest.js'));
router.post('/v1/plex/request/:mediaId', require('./controllers/plex/submitRequest.js'));
router.get('/v1/plex/hook', require('./controllers/plex/hookDump.js'));
router.post('/v1/plex/hook', require('./controllers/plex/hookDump.js'));
/**
* Requests
*/
router.get('/v2/request', require('./controllers/request/fetchAllRequests.js'));
router.get('/v2/request/:id', require('./controllers/request/getRequest.js'));
router.post('/v2/request', require('./controllers/request/requestTmdbId.js'));
router.get('/v1/plex/requests/all', require('./controllers/plex/fetchRequested.js'));
router.put('/v1/plex/request/:requestId', mustBeAuthenticated, require('./controllers/plex/updateRequested.js'));
@@ -89,13 +116,6 @@ router.put('/v1/plex/request/:requestId', mustBeAuthenticated, require('./contro
router.get('/v1/pirate/search', mustBeAuthenticated, require('./controllers/pirate/searchTheBay.js'));
router.post('/v1/pirate/add', mustBeAuthenticated, require('./controllers/pirate/addMagnet.js'));
/**
* TMDB
*/
router.get('/v1/tmdb/search', require('./controllers/tmdb/searchMedia.js'));
router.get('/v1/tmdb/list/:listname', require('./controllers/tmdb/listSearch.js'));
router.get('/v1/tmdb/:mediaId', require('./controllers/tmdb/readMedia.js'));
/**
* git
*/

View File

@@ -0,0 +1,70 @@
const configuration = require('src/config/configuration').getInstance();
const Cache = require('src/tmdb/cache');
const TMDB = require('src/tmdb/tmdb');
const cache = new Cache();
const tmdb = new TMDB(cache, configuration.get('tmdb', 'apiKey'));
// there should be a translate function from query params to
// tmdb list that is valid. Should it be a helper function or does it
// belong in tmdb.
// + could also have default value that are sent to the client.
// * have the same class create a getListNames() and a fetchList()
// * dicover list might be overkill_https://tinyurl.com/y7f8ragw
// + trending! https://tinyurl.com/ydywrqox
// by all, mediatype, or person. Can also define time periode to
// get more trending view of what people are checking out.
// + newly created (tv/latest).
// + movie/latest
//
function handleError(error, res) {
const { status, message } = error;
if (status && message) {
res.status(status).send({ success: false, message })
} else {
console.log('caught list controller error', error)
res.status(500).send({ message: 'An unexpected error occured while requesting list'})
}
}
function handleListResponse(response, res) {
return res.send(response)
.catch(error => handleError(error, res))
}
function fetchTmdbList(req, res, listname, type) {
const { page } = req.query;
if (type === 'movie') {
return tmdb.movieList(listname, page)
.then(listResponse => res.send(listResponse))
.catch(error => handleError(error, res))
} else if (type === 'show') {
return tmdb.showList(listname, page)
.then(listResponse => res.send(listResponse))
.catch(error => handleError(error, res))
}
handleError({
status: 400,
message: `'${type}' is not a valid list type.`
}, res)
}
const nowPlayingMovies = (req, res) => fetchTmdbList(req, res, 'miscNowPlayingMovies', 'movie')
const popularMovies = (req, res) => fetchTmdbList(req, res, 'miscPopularMovies', 'movie')
const topRatedMovies = (req, res) => fetchTmdbList(req, res, 'miscTopRatedMovies', 'movie')
const upcomingMovies = (req, res) => fetchTmdbList(req, res, 'miscUpcomingMovies', 'movie')
const nowPlayingShows = (req, res) => fetchTmdbList(req, res, 'tvOnTheAir', 'show')
const popularShows = (req, res) => fetchTmdbList(req, res, 'miscPopularTvs', 'show')
const topRatedShows = (req, res) => fetchTmdbList(req, res, 'miscTopRatedTvs', 'show')
module.exports = {
nowPlayingMovies,
popularMovies,
topRatedMovies,
upcomingMovies,
nowPlayingShows,
popularShows,
topRatedShows
}

View File

@@ -0,0 +1,26 @@
const configuration = require('src/config/configuration').getInstance();
const Cache = require('src/tmdb/cache');
const TMDB = require('src/tmdb/tmdb');
const cache = new Cache();
const tmdb = new TMDB(cache, configuration.get('tmdb', 'apiKey'));
const movieCreditsController = (req, res) => {
const movieId = req.params.id;
tmdb.movieCredits(movieId)
.then(credits => res.send(credits.createJsonResponse()))
.catch(error => {
const { status, message } = error;
if (status && message) {
res.status(status).send({ success: false, message })
} else {
// TODO log unhandled errors
console.log('caugth movie credits controller error', error)
res.status(500).send({ message: 'An unexpected error occured while requesting movie credits' })
}
})
}
module.exports = movieCreditsController;

View File

@@ -0,0 +1,58 @@
const configuration = require('src/config/configuration').getInstance();
const Cache = require('src/tmdb/cache');
const TMDB = require('src/tmdb/tmdb');
const Plex = require('src/plex/plex');
const cache = new Cache();
const tmdb = new TMDB(cache, configuration.get('tmdb', 'apiKey'));
const plex = new Plex(configuration.get('plex', 'ip'));
function handleError(error, res) {
const { status, message } = error;
if (status && message) {
res.status(status).send({ success: false, message })
} else {
console.log('caught movieinfo controller error', error)
res.status(500).send({ message: 'An unexpected error occured while requesting movie info'})
}
}
/**
* Controller: Retrieve information for a movie
* @param {Request} req http request variable
* @param {Response} res
* @returns {Callback}
*/
async function movieInfoController(req, res) {
const movieId = req.params.id;
let { credits, release_dates, check_existance } = req.query;
credits && credits.toLowerCase() === 'true' ? credits = true : credits = false
release_dates && release_dates.toLowerCase() === 'true' ? release_dates = true : release_dates = false
check_existance && check_existance.toLowerCase() === 'true' ? check_existance = true : check_existance = false
let tmdbQueue = [tmdb.movieInfo(movieId)]
if (credits)
tmdbQueue.push(tmdb.movieCredits(movieId))
if (release_dates)
tmdbQueue.push(tmdb.movieReleaseDates(movieId))
try {
const [ Movie, Credits, ReleaseDates ] = await Promise.all(tmdbQueue)
const movie = Movie.createJsonResponse()
if (Credits)
movie.credits = Credits.createJsonResponse()
if (ReleaseDates)
movie.release_dates = ReleaseDates.createJsonResponse().results
if (check_existance)
movie.exists_in_plex = await plex.existsInPlex(movie)
res.send(movie)
} catch(error) {
handleError(error, res)
}
}
module.exports = movieInfoController;

View File

@@ -0,0 +1,26 @@
const configuration = require('src/config/configuration').getInstance();
const Cache = require('src/tmdb/cache');
const TMDB = require('src/tmdb/tmdb');
const cache = new Cache();
const tmdb = new TMDB(cache, configuration.get('tmdb', 'apiKey'));
const movieReleaseDatesController = (req, res) => {
const movieId = req.params.id;
tmdb.movieReleaseDates(movieId)
.then(releaseDates => res.send(releaseDates.createJsonResponse()))
.catch(error => {
const { status, message } = error;
if (status && message) {
res.status(status).send({ success: false, message })
} else {
// TODO log unhandled errors : here our at tmdbReleaseError ?
console.log('caugth release dates controller error', error)
res.status(500).send({ message: 'An unexpected error occured while requesting movie credits' })
}
})
}
module.exports = movieReleaseDatesController;

View File

@@ -0,0 +1,25 @@
const configuration = require('src/config/configuration').getInstance();
const Cache = require('src/tmdb/cache');
const TMDB = require('src/tmdb/tmdb');
const cache = new Cache();
const tmdb = new TMDB(cache, configuration.get('tmdb', 'apiKey'));
/**
* Controller: Retrieve information for a person
* @param {Request} req http request variable
* @param {Response} res
* @returns {Callback}
*/
function personInfoController(req, res) {
const personId = req.params.id;
tmdb.personInfo(personId)
.then(person => res.send(person.createJsonResponse()))
.catch(error => {
res.status(404).send({ success: false, message: error.message });
});
}
module.exports = personInfoController;

View File

@@ -17,8 +17,8 @@ function addMagnet(req, res) {
.then((result) => {
res.send(result);
})
.catch((error) => {
res.status(500).send({ success: false, error: error.message });
.catch(error => {
res.status(500).send({ success: false, message: error.message });
});
}

View File

@@ -21,8 +21,8 @@ function updateRequested(req, res) {
.then((result) => {
res.send({ success: true, results: result });
})
.catch((error) => {
res.status(401).send({ success: false, error: error.message });
.catch(error => {
res.status(401).send({ success: false, message: error.message });
});
}

View File

@@ -17,7 +17,7 @@ function fetchRequestedController(req, res) {
res.send({ success: true, results: requestedItems, total_results: requestedItems.length });
})
.catch((error) => {
res.status(401).send({ success: false, error: error.message });
res.status(401).send({ success: false, message: error.message });
});
}

View File

@@ -1,14 +1,15 @@
const PlexRepository = require('src/plex/plexRepository');
const configuration = require('src/config/configuration').getInstance();
const plexRepository = new PlexRepository();
const plexRepository = new PlexRepository(configuration.get('plex', 'ip'));
function playingController(req, res) {
plexRepository.nowPlaying()
.then((movies) => {
.then(movies => {
res.send(movies);
})
.catch((error) => {
res.status(500).send({ success: false, error: error.message });
.catch(error => {
res.status(500).send({ success: false, message: error.message });
});
}

View File

@@ -12,10 +12,10 @@ function readRequestController(req, res) {
const mediaId = req.params.mediaId;
const { type } = req.query;
requestRepository.lookup(mediaId, type)
.then((movies) => {
.then(movies => {
res.send(movies);
}).catch((error) => {
res.status(404).send({ success: false, error: error.message });
}).catch(error => {
res.status(404).send({ success: false, message: error.message });
});
}

View File

@@ -0,0 +1,25 @@
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 searchPlexController(req, res) {
const { query, type } = req.query;
plex.search(query, type)
.then(movies => {
if (movies.length > 0) {
res.send(movies);
} else {
res.status(404).send({ success: false, message: 'Search query did not give any results from plex.'})
}
}).catch(error => {
res.status(500).send({ success: false, message: error.message });
});
}
module.exports = searchPlexController;

View File

@@ -1,6 +1,7 @@
const PlexRepository = require('src/plex/plexRepository');
const configuration = require('src/config/configuration').getInstance();
const plexRepository = new PlexRepository();
const plexRepository = new PlexRepository(configuration.get('plex', 'ip'));
/**
* Controller: Search for media and check existence
@@ -13,15 +14,15 @@ function searchMediaController(req, res) {
const { query } = req.query;
plexRepository.search(query)
.then((media) => {
.then(media => {
if (media !== undefined || media.length > 0) {
res.send(media);
} else {
res.status(404).send({ success: false, error: 'Search query did not return any results.' });
res.status(404).send({ success: false, message: 'Search query did not return any results.' });
}
})
.catch((error) => {
res.status(500).send({ success: false, error: error.message });
.catch(error => {
res.status(500).send({ success: false, message: error.message });
});
}

View File

@@ -18,8 +18,8 @@ function searchRequestController(req, res) {
.then((searchResult) => {
res.send(searchResult);
})
.catch((error) => {
res.status(500).send({ success: false, error: error });
.catch(error => {
res.status(500).send({ success: false, message: error.message });
});
}

View File

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

View File

@@ -18,7 +18,7 @@ function updateRequested(req, res) {
res.send({ success: true });
})
.catch((error) => {
res.status(401).send({ success: false, error: error.message });
res.status(401).send({ success: false, message: error.message });
});
}

View File

@@ -0,0 +1,27 @@
const RequestRepository = require('src/request/request');
const request = new RequestRepository();
/**
* Controller: Fetch all requested items
* @param {Request} req http request variable
* @param {Response} res
* @returns {Callback}
*/
function fetchAllRequests(req, res) {
let { page, filter, sort, query } = req.query;
let sort_by = sort;
let sort_direction = undefined;
if (sort !== undefined && sort.includes(':')) {
[sort_by, sort_direction] = sort.split(':')
}
Promise.resolve()
.then(() => request.fetchAll(page, sort_by, sort_direction, filter, query))
.then(result => res.send(result))
.catch(error => {
res.status(404).send({ success: false, message: error.message });
});
}
module.exports = fetchAllRequests;

View File

@@ -0,0 +1,21 @@
const RequestRepository = require('src/request/request');
const request = new RequestRepository();
/**
* Controller: Get requested item by tmdb id and type
* @param {Request} req http request variable
* @param {Response} res
* @returns {Callback}
*/
function fetchAllRequests(req, res) {
const id = req.params.id;
const { type } = req.query;
request.getRequestByIdAndType(id, type)
.then(result => res.send(result))
.catch(error => {
res.status(404).send({ success: false, message: error.message });
});
}
module.exports = fetchAllRequests;

View File

@@ -0,0 +1,53 @@
const configuration = require('src/config/configuration').getInstance();
const Cache = require('src/tmdb/cache');
const TMDB = require('src/tmdb/tmdb');
const RequestRepository = require('src/request/request');
const cache = new Cache();
const tmdb = new TMDB(cache, configuration.get('tmdb', 'apiKey'));
const request = new RequestRepository();
const tmdbMovieInfo = (id) => {
return tmdb.movieInfo(id)
}
const tmdbShowInfo = (id) => {
return tmdb.showInfo(id)
}
/**
* Controller: Request by id with type param
* @param {Request} req http request variable
* @param {Response} res
* @returns {Callback}
*/
function requestTmdbIdController(req, res) {
const { id, type } = req.body
const ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress;
const user_agent = req.headers['user-agent'];
const user = req.loggedInUser;
let mediaFunction = undefined
if (id === undefined || type === undefined) {
res.status(422).send({ success: false, message: "'Missing parameteres: 'id' and/or 'type'"})
}
if (type === 'movie') {
mediaFunction = tmdbMovieInfo
} else if (type === 'show') {
mediaFunction = tmdbShowInfo
} else {
res.status(422).send({ success: false, message: 'Incorrect type. Allowed types: "movie" or "show"'})
}
mediaFunction(id)
// .catch((error) => { console.error(error); res.status(404).send({ success: false, error: 'Id not found' }) })
.then(tmdbMedia => request.requestFromTmdb(tmdbMedia, ip, user_agent, user))
.then(() => res.send({success: true, message: 'Request has been submitted.'}))
.catch(error => {
res.send({ success: false, message: error.message });
})
}
module.exports = requestTmdbIdController;

View File

@@ -0,0 +1,40 @@
const configuration = require('src/config/configuration').getInstance();
const Cache = require('src/tmdb/cache');
const TMDB = require('src/tmdb/tmdb');
const SearchHistory = require('src/searchHistory/searchHistory');
const cache = new Cache();
const tmdb = new TMDB(cache, configuration.get('tmdb', 'apiKey'));
const searchHistory = new SearchHistory();
/**
* Controller: Search for movies by query and pagey
* @param {Request} req http request variable
* @param {Response} res
* @returns {Callback}
*/
function movieSearchController(req, res) {
const user = req.loggedInUser;
const { query, page } = req.query;
if (user) {
return searchHistory.create(user, query);
}
tmdb.movieSearch(query, page)
.then(movieSearchResults => res.send(movieSearchResults))
.catch(error => {
const { status, message } = error;
if (status && message) {
res.status(status).send({ success: false, message })
} else {
// TODO log unhandled errors
console.log('caugth movie search controller error', error)
res.status(500).send({
message: `An unexpected error occured while searching movies with query: ${query}`
})
}
})
}
module.exports = movieSearchController;

View File

@@ -0,0 +1,45 @@
const configuration = require('src/config/configuration').getInstance();
const Cache = require('src/tmdb/cache');
const TMDB = require('src/tmdb/tmdb');
const SearchHistory = require('src/searchHistory/searchHistory');
const cache = new Cache();
const tmdb = new TMDB(cache, configuration.get('tmdb', 'apiKey'));
const searchHistory = new SearchHistory();
function checkAndCreateJsonResponse(result) {
if (typeof result['createJsonResponse'] === 'function') {
return result.createJsonResponse()
}
return result
}
/**
* Controller: Search for multi (movies, shows and people by query and pagey
* @param {Request} req http request variable
* @param {Response} res
* @returns {Callback}
*/
function multiSearchController(req, res) {
const user = req.loggedInUser;
const { query, page } = req.query;
if (user) {
searchHistory.create(user, query)
}
return tmdb.multiSearch(query, page)
.then(multiSearchResults => res.send(multiSearchResults))
.catch(error => {
const { status, message } = error;
if (status && message) {
res.status(status).send({ success: false, message })
} else {
// TODO log unhandled errors
console.log('caugth multi search controller error', error)
res.status(500).send({ message: `An unexpected error occured while searching with query: ${query}` })
}
})
}
module.exports = multiSearchController;

View File

@@ -0,0 +1,42 @@
const configuration = require('src/config/configuration').getInstance();
const Cache = require('src/tmdb/cache');
const TMDB = require('src/tmdb/tmdb');
const SearchHistory = require('src/searchHistory/searchHistory');
const cache = new Cache();
const tmdb = new TMDB(cache, configuration.get('tmdb', 'apiKey'));
const searchHistory = new SearchHistory();
/**
* Controller: Search for person by query and pagey
* @param {Request} req http request variable
* @param {Response} res
* @returns {Callback}
*/
function personSearchController(req, res) {
const user = req.loggedInUser;
const { query, page } = req.query;
if (user) {
return searchHistory.create(user, query);
}
tmdb.personSearch(query, page)
.then((person) => {
res.send(person);
})
.catch(error => {
const { status, message } = error;
if (status && message) {
res.status(status).send({ success: false, message })
} else {
// TODO log unhandled errors
console.log('caugth person search controller error', error)
res.status(500).send({
message: `An unexpected error occured while searching people with query: ${query}`
})
}
})
}
module.exports = personSearchController;

View File

@@ -0,0 +1,35 @@
const SearchHistory = require('src/searchHistory/searchHistory');
const configuration = require('src/config/configuration').getInstance();
const Cache = require('src/tmdb/cache');
const TMDB = require('src/tmdb/tmdb');
const cache = new Cache();
const tmdb = new TMDB(cache, configuration.get('tmdb', 'apiKey'));
const searchHistory = new SearchHistory();
/**
* Controller: Search for shows by query and pagey
* @param {Request} req http request variable
* @param {Response} res
* @returns {Callback}
*/
function showSearchController(req, res) {
const user = req.loggedInUser;
const { query, page } = req.query;
Promise.resolve()
.then(() => {
if (user) {
return searchHistory.create(user, query);
}
return null
})
.then(() => tmdb.showSearch(query, page))
.then((shows) => {
res.send(shows);
})
.catch(error => {
res.status(500).send({ success: false, message: error.message });
});
}
module.exports = showSearchController;

View File

@@ -10,7 +10,7 @@ function readStraysController(req, res) {
res.send(strays);
})
.catch((error) => {
res.status(500).send({ success: false, error: error.message });
res.status(500).send({ success: false, message: error.message });
});
}

View File

@@ -10,7 +10,7 @@ function strayByIdController(req, res) {
res.send(stray);
})
.catch((error) => {
res.status(500).send({ success: false, error: error.message });
res.status(500).send({ success: false, message: error.message });
});
}

View File

@@ -10,7 +10,7 @@ function verifyStrayController(req, res) {
res.send({ success: true, message: 'Episode verified' });
})
.catch((error) => {
res.status(500).send({ success: false, error: error.message });
res.status(500).send({ success: false, message: error.message });
});
}

View File

@@ -0,0 +1,26 @@
const configuration = require('src/config/configuration').getInstance();
const Cache = require('src/tmdb/cache');
const TMDB = require('src/tmdb/tmdb');
const cache = new Cache();
const tmdb = new TMDB(cache, configuration.get('tmdb', 'apiKey'));
const showCreditsController = (req, res) => {
const showId = req.params.id;
tmdb.showCredits(showId)
.then(credits => res.send(credits.createJsonResponse()))
.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 show credits' })
}
})
}
module.exports = showCreditsController;

View File

@@ -0,0 +1,56 @@
const configuration = require('src/config/configuration').getInstance();
const Cache = require('src/tmdb/cache');
const TMDB = require('src/tmdb/tmdb');
const Plex = require('src/plex/plex');
const cache = new Cache();
const tmdb = new TMDB(cache, configuration.get('tmdb', 'apiKey'));
const plex = new Plex(configuration.get('plex', 'ip'));
function handleError(error, res) {
const { status, message } = error;
if (status && message) {
res.status(status).send({ success: false, message })
} else {
console.log('caught showinfo controller error', error)
res.status(500).send({
message: 'An unexpected error occured while requesting show info.'
})
}
}
/**
* Controller: Retrieve information for a show
* @param {Request} req http request variable
* @param {Response} res
* @returns {Callback}
*/
async function showInfoController(req, res) {
const showId = req.params.id;
let { credits, check_existance } = req.query;
credits && credits.toLowerCase() === 'true' ? credits = true : credits = false
check_existance && check_existance.toLowerCase() === 'true' ? check_existance = true : check_existance = false
let tmdbQueue = [tmdb.showInfo(showId)]
if (credits)
tmdbQueue.push(tmdb.showCredits(showId))
try {
const [Show, Credits] = await Promise.all(tmdbQueue)
const show = Show.createJsonResponse()
if (credits)
show.credits = Credits.createJsonResponse()
if (check_existance)
show.exists_in_plex = await plex.existsInPlex(show)
res.send(show)
} catch(error) {
handleError(error, res)
}
}
module.exports = showInfoController;

View File

@@ -1,25 +0,0 @@
const configuration = require('src/config/configuration').getInstance();
const Cache = require('src/tmdb/cache');
const TMDB = require('src/tmdb/tmdb');
const cache = new Cache();
const tmdb = new TMDB(cache, configuration.get('tmdb', 'apiKey'));
/**
* Controller: Retrieve nowplaying movies / now airing shows
* @param {Request} req http request variable
* @param {Response} res
* @returns {Callback}
*/
function listSearchController(req, res) {
const listname = req.params.listname;
const { type, page } = req.query;
tmdb.listSearch(listname, type, page)
.then((results) => {
res.send(results);
}).catch((error) => {
res.status(404).send({ success: false, error: error.message });
});
}
module.exports = listSearchController;

View File

@@ -1,25 +0,0 @@
const configuration = require('src/config/configuration').getInstance();
const Cache = require('src/tmdb/cache');
const TMDB = require('src/tmdb/tmdb');
const cache = new Cache();
const tmdb = new TMDB(cache, configuration.get('tmdb', 'apiKey'));
/**
* Controller: Retrieve information for a movie
* @param {Request} req http request variable
* @param {Response} res
* @returns {Callback}
*/
function readMediaController(req, res) {
const mediaId = req.params.mediaId;
const { type } = req.query;
tmdb.lookup(mediaId, type)
.then((movies) => {
res.send(movies);
}).catch((error) => {
res.status(404).send({ success: false, error: error.message });
});
}
module.exports = readMediaController;

View File

@@ -1,31 +0,0 @@
const configuration = require('src/config/configuration').getInstance();
const Cache = require('src/tmdb/cache');
const TMDB = require('src/tmdb/tmdb');
const cache = new Cache();
const tmdb = new TMDB(cache, configuration.get('tmdb', 'apiKey'));
/**
* Controller: Search for movies by query, page and optional type
* @param {Request} req http request variable
* @param {Response} res
* @returns {Callback}
*/
function searchMediaController(req, res) {
const { query, page, type } = req.query;
Promise.resolve()
.then(() => tmdb.search(query, page, type))
.then((movies) => {
if (movies !== undefined || movies.length > 0) {
res.send(movies);
} else {
res.status(404).send({ success: false, error: 'Search query did not return any results.' });
}
})
.catch((error) => {
res.status(500).send({ success: false, error: error.message });
});
}
module.exports = searchMediaController;

View File

@@ -13,11 +13,11 @@ function historyController(req, res) {
const username = user === undefined ? undefined : user.username;
searchHistory.read(username)
.then((searchQueries) => {
.then(searchQueries => {
res.send({ success: true, searchQueries });
})
.catch((error) => {
res.status(404).send({ success: false, error: error });
.catch(error => {
res.status(404).send({ success: false, message: error.message });
});
}

View File

@@ -20,13 +20,13 @@ function loginController(req, res) {
userSecurity.login(user, password)
.then(() => userRepository.checkAdmin(user))
.then((checkAdmin) => {
const token = new Token(user).toString(secret);
const admin_state = checkAdmin === 1 ? true : false;
res.send({ success: true, token, admin: admin_state });
.then(checkAdmin => {
const isAdmin = checkAdmin === 1 ? true : false;
const token = new Token(user, isAdmin).toString(secret);
res.send({ success: true, token });
})
.catch((error) => {
res.status(401).send({ success: false, error: error.message });
.catch(error => {
res.status(401).send({ success: false, message: error.message });
});
}

View File

@@ -20,15 +20,15 @@ function registerController(req, res) {
userSecurity.createNewUser(user, password)
.then(() => userRepository.checkAdmin(user))
.then((checkAdmin) => {
const token = new Token(user).toString(secret);
const admin_state = checkAdmin === 1 ? true : false;
.then(checkAdmin => {
const isAdmin = checkAdmin === 1 ? true : false;
const token = new Token(user, isAdmin).toString(secret);
res.send({
success: true, message: 'Welcome to Seasoned!', token, admin: admin_state,
success: true, message: 'Welcome to Seasoned!', token
});
})
.catch((error) => {
res.status(401).send({ success: false, error: error.message });
.catch(error => {
res.status(401).send({ success: false, message: error.message });
});
}

View File

@@ -12,12 +12,11 @@ function requestsController(req, res) {
const user = req.loggedInUser;
requestRepository.userRequests(user)
.then((requests) => {
.then(requests => {
res.send({ success: true, results: requests, total_results: requests.length });
})
.catch((error) => {
console.log(error)
res.status(500).send({ success: false, error: error });
.catch(error => {
res.status(500).send({ success: false, message: error.message });
});
}

View File

@@ -8,16 +8,16 @@ const Token = require('src/user/token');
// 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();
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;

View File

@@ -0,0 +1 @@
[{"adult":false,"backdrop_path":"/mVr0UiqyltcfqxbAUcLl9zWL8ah.jpg","belongs_to_collection":{"id":422837,"name":"Blade Runner Collection","poster_path":"/cWESb1o9lW2i2Z3Xllv9u40aNIk.jpg","backdrop_path":"/bSHZIvLoPBWyGLeiAudN1mXdvQX.jpg"},"budget":150000000,"genres":[{"id":9648,"name":"Mystery"},{"id":878,"name":"Science Fiction"},{"id":53,"name":"Thriller"}],"homepage":"http://bladerunnermovie.com/","id":335984,"imdb_id":"tt1856101","original_language":"en","original_title":"Blade Runner 2049","overview":"Thirty years after the events of the first film, a new blade runner, LAPD Officer K, unearths a long-buried secret that has the potential to plunge what's left of society into chaos. K's discovery leads him on a quest to find Rick Deckard, a former LAPD blade runner who has been missing for 30 years.","popularity":30.03,"poster_path":"/gajva2L0rPYkEWjzgFlBXCAVBE5.jpg","production_companies":[{"id":79529,"logo_path":"/gVN3k8emmKy4iV4KREWcCtxusZK.png","name":"Torridon Films","origin_country":"US"},{"id":101829,"logo_path":"/8IOjCvgjq0zTrtP91cWD3kL2jMK.png","name":"16:14 Entertainment","origin_country":"US"},{"id":1645,"logo_path":"/6Ry6uNBaa0IbbSs1XYIgX5DkA9r.png","name":"Scott Free Productions","origin_country":""},{"id":5,"logo_path":"/71BqEFAF4V3qjjMPCpLuyJFB9A.png","name":"Columbia Pictures","origin_country":"US"},{"id":1088,"logo_path":"/9WOE5AQUXbOtLU6GTwfjS8OMF0v.png","name":"Alcon Entertainment","origin_country":"US"},{"id":78028,"logo_path":"/sTFcDFfJaSVT3sv3DoaZDE4SlGB.png","name":"Thunderbird Entertainment","origin_country":"CA"},{"id":174,"logo_path":"/ky0xOc5OrhzkZ1N6KyUxacfQsCk.png","name":"Warner Bros. Pictures","origin_country":"US"}],"production_countries":[{"iso_3166_1":"CA","name":"Canada"},{"iso_3166_1":"US","name":"United States of America"},{"iso_3166_1":"HU","name":"Hungary"},{"iso_3166_1":"GB","name":"United Kingdom"}],"release_date":"2017-10-04","revenue":259239658,"runtime":163,"spoken_languages":[{"iso_639_1":"en","name":"English"},{"iso_639_1":"fi","name":"suomi"}],"status":"Released","tagline":"There's still a page left.","title":"Blade Runner 2049","video":false,"vote_average":7.3,"vote_count":5478}]

View File

@@ -0,0 +1,6 @@
{
"page":1,
"results":[],
"total_results":0,
"total_pages":1
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,16 @@
const tmdbMock = () => ({
error: null,
response: null,
searchMovie(query, callback) {
callback(this.error, this.response);
},
movieInfo(query, callback) {
callback(this.error, this.response);
},
miscPopularMovies(callback) {
console.log('miscPopMovies callback', callback)
callback(this.error, this.response);
},
});
module.exports = tmdbMock;

View File

@@ -5,8 +5,8 @@ xdescribe('As a developer I want the server to start', () => {
beforeEach(() =>
this.server = require('src/webserver/server'));
it('should listen on port 31459', (done) => {
net.createConnection(31459, done);
it('should listen on port 31400', (done) => {
net.createConnection(31400, done);
});
afterEach(() =>

View File

@@ -15,6 +15,6 @@ describe('As a user I want error when registering existing username', () => {
.post('/api/v1/user')
.send({ username: 'test_user', password: 'password' })
.expect(401)
.then(response => assert.equal(response.text, '{"success":false,"error":"That username is already registered"}'))
.then(response => assert.equal(response.text, '{"success":false,"message":"That username is already registered"}'))
);
});

View File

@@ -7,12 +7,12 @@ const popularMoviesSuccess = require('test/fixtures/popular-movies-success-respo
describe('As a user I want to get popular movies', () => {
before(() => resetDatabase());
before(() => createCacheEntry('p:movie:1', popularMoviesSuccess));
before(() => createCacheEntry('pm:1', popularMoviesSuccess));
it('should return 200 with the information', () =>
request(app)
.get('/api/v1/tmdb/list/popular')
.get('/api/v2/movie/popular')
.expect(200)
.then(response => assert.equal(response.body.results.length, 20))
);
});
});

View File

@@ -7,12 +7,12 @@ const popularShowsSuccess = require('test/fixtures/popular-show-success-response
describe('As a user I want to get popular shows', () => {
before(() => resetDatabase());
before(() => createCacheEntry('p:show:1', popularShowsSuccess));
before(() => createCacheEntry('pt:1', popularShowsSuccess));
it('should return 200 with the information', () =>
request(app)
.get('/api/v1/tmdb/list/popular?type=show')
.get('/api/v2/show/popular')
.expect(200)
.then(response => assert.equal(response.body.results.length, 20))
);
});
});

View File

@@ -4,19 +4,20 @@ const app = require('src/webserver/app');
const request = require('supertest-as-promised');
const createUser = require('test/helpers/createUser');
const createToken = require('test/helpers/createToken');
const infoMovieSuccess = require('test/fixtures/arrival-info-success-response.json');
const infoMovieSuccess = require('test/fixtures/blade_runner_2049-info-success-response.json');
describe('As a user I want to request a movie', () => {
before(() => {
return resetDatabase()
.then(() => createUser('test_user', 'test@gmail.com', 'password'));
before(async () => {
await resetDatabase()
await createUser('test_user', 'test@gmail.com', 'password')
})
before(() => createCacheEntry('i:movie:329865', infoMovieSuccess));
before(() => createCacheEntry('mi:335984:false', infoMovieSuccess));
it('should return 200 when item is requested', () =>
request(app)
.post('/api/v1/plex/request/329865')
.set('Authorization', createToken('test_user', 'secret'))
.post('/api/v2/request')
.set('authorization', createToken('test_user', 'secret'))
.send({ id: 335984, type: 'movie' })
.expect(200)
);
});

View File

@@ -2,15 +2,15 @@ const createCacheEntry = require('test/helpers/createCacheEntry');
const resetDatabase = require('test/helpers/resetDatabase');
const request = require('supertest-as-promised');
const app = require('src/webserver/app');
const interstellarQuerySuccess = require('test/fixtures/interstellar-query-success-response.json');
const interstellarQuerySuccess = require('test/fixtures/interstellar-query-movie-success-response.json');
describe('As an anonymous user I want to search for a movie', () => {
before(() => resetDatabase());
before(() => createCacheEntry('se:1:multi:interstellar', interstellarQuerySuccess));
before(() => createCacheEntry('mos:1:interstellar', interstellarQuerySuccess));
it('should return 200 with the search results even if user is not logged in', () =>
request(app)
.get('/api/v1/tmdb/search?query=interstellar&page=1')
.get('/api/v2/search/movie?query=interstellar&page=1')
.expect(200)
);
});

View File

@@ -0,0 +1,31 @@
const assert = require('assert');
// const convertTmdbToMovie = require('src/tmdb/convertTmdbToMovie');
const { Movie } = require('src/tmdb/types');
const bladeRunnerQuerySuccess = require('test/fixtures/blade_runner_2049-info-success-response.json')
describe('Convert tmdb movieInfo to movie', () => {
beforeEach(() => [this.bladeRunnerTmdbMovie] = bladeRunnerQuerySuccess);
it('should translate the tmdb release date to movie year', () => {
const bladeRunner = Movie.convertFromTmdbResponse(this.bladeRunnerTmdbMovie);
assert.strictEqual(bladeRunner.year, 2017);
});
it('should translate the tmdb release date to instance of Date', () => {
const bladeRunner = Movie.convertFromTmdbResponse(this.bladeRunnerTmdbMovie);
assert(bladeRunner.releaseDate instanceof Date);
});
it('should translate the tmdb title to title', () => {
const bladeRunner = Movie.convertFromTmdbResponse(this.bladeRunnerTmdbMovie);
assert.equal(bladeRunner.title, 'Blade Runner 2049');
});
it('should translate the tmdb vote_average to rating', () => {
const bladeRunner = Movie.convertFromTmdbResponse(this.bladeRunnerTmdbMovie);
assert.equal(bladeRunner.rating, 7.3);
});
});

View File

@@ -0,0 +1,31 @@
const assert = require('assert');
// const Movie = require('src/movie/movie');
const TMDB = require('src/tmdb/tmdb');
const Cache = require('src/tmdb/cache');
const SqliteDatabase = require('src/database/sqliteDatabase');
const tmdbMock = require('test/helpers/tmdbMock');
const emptyQuerySuccess = require('test/fixtures/empty-query-success-response.json');
const interstellarQuerySuccess = require('test/fixtures/arrival-info-success-response.json');
const popularMovieSuccessResponse = require('test/fixtures/popular-movies-success-response.json');
describe('TMDB', function test() {
beforeEach(() => {
this.mockMoviedb = tmdbMock();
this.database = new SqliteDatabase(':memory:');
return Promise.resolve()
.then(() => this.database.setUp());
});
describe('popular', () => {
it('should return the "Blade Runner 2049" year in the collection of popular movies', () => {
this.mockMoviedb.response = popularMovieSuccessResponse;
const cache = new Cache(this.database);
const tmdb = new TMDB(cache, 'bogus-pi-key', this.mockMoviedb);
return tmdb.popular()
.then(movies =>
assert.equal(movies[0].title, "Blade Runner 2049")
);
});
})
});

File diff suppressed because it is too large Load Diff