54 Commits

Author SHA1 Message Date
488da889d8 Current state of graphql implementation. Some things are broken. Focused on getting progress from deluge linked with requests and requested torrents. Warning! the payload size of this action is 1MB pr second. This also heavily dependes on un-tracked changes in both delugeClient and the frontend for the visualization. 2019-11-21 22:32:08 +01:00
8da7c159b1 WIP graphql to get requests, torrents and working on requests with torrents by username. 2019-11-11 16:54:54 +01:00
ea5bc36956 Merge pull request #111 from KevinMidboe/api/v2
Api/v2
2019-11-04 18:01:15 +01:00
fd475265c1 Updated test to reflect changes to all error response objects. (changed from returning message in error key to message. 2019-11-04 17:54:08 +01:00
b0804f8a08 Merge branch 'api/v2' of github.com:kevinmidboe/seasonedShows into api/v2 2019-11-04 00:57:45 +01:00
6b737b8ab4 Updated all controller responses to return message not error on errors. 2019-11-04 00:57:27 +01:00
5623344666 Merge branch 'master' into api/v2 2019-11-03 20:57:25 +01:00
f8cc19b510 Added rating and release_date when parsing movies and fixed respective tests 2019-11-03 20:56:46 +01:00
c589457a6c Removed unused mongoose package 2019-11-03 20:55:54 +01:00
b802a7b62b Moved, renamed, re-did and added a lot of stuff. Getting ready for the v2 upgrade 2019-11-03 20:33:30 +01:00
879a02b388 Finished movie credits and release dates 2019-11-03 16:01:19 +01:00
bc3d4881bd New release_dates endpoint for movie 2019-11-03 15:43:35 +01:00
ef8d4d90b2 Removed console log 2019-11-03 15:08:42 +01:00
d2d396bb7a Set cache TTL for credits to 1 day 2019-11-03 15:07:11 +01:00
500b75eaf6 We know there could be a 401 response so we handle it 2019-11-03 15:04:14 +01:00
9308d4ea9b Credits endpoint for movies 2019-11-03 15:02:45 +01:00
6c2c81a1a1 Updated movieInfo controller to also handle requesting release_dates as query parameter 2019-10-04 22:36:39 +02:00
90aa4d2485 Rewrote the movie & show list controller to be more abstract and easier to extend later 2019-10-04 21:21:52 +02:00
0ca3f81bf8 listController first defines all async functions as constant variables then module exports them all as a dict 2019-10-04 20:55:39 +02:00
b9831c6b3d Forgot to reasing variables after copy-pasting them in convertTmdbToMovie 2019-10-04 20:37:06 +02:00
4781e9ae65 Merge pull request #119 from KevinMidboe/snyk-fix-a43be890852096db7a469faa6c44f8ef
[Snyk] Fix for 3 vulnerable dependencies
2019-07-27 02:17:50 +02:00
snyk-test
eb0881f19e fix: client/package.json to reduce vulnerabilities
The following vulnerabilities are fixed with an upgrade:
- https://snyk.io/vuln/SNYK-JS-JSYAML-173999
- https://snyk.io/vuln/SNYK-JS-JSYAML-174129
- https://snyk.io/vuln/npm:mem:20180117
2019-07-27 00:16:09 +00:00
bc4d73821d Upgraded webpack-dev-server to not have a screaming vulnerability 2019-07-27 02:05:38 +02:00
ab6144eb81 Update yarn lock, updated coveralls and mocha run under coverage command now uses required babel register 2019-07-27 02:03:11 +02:00
c3d87e2200 Merge pull request #117 from KevinMidboe/snyk-fix-9429d5dfb86996c66ac12aef1a5178b1
[Snyk] Fix for 2 vulnerable dependencies
2019-07-27 01:58:33 +02:00
e391ce7ef9 Merge pull request #112 from KevinMidboe/snyk-fix-5oeu5e
[Snyk] Fix for 4 vulnerable dependencies
2019-07-27 01:58:07 +02:00
ca707078d9 Merge branch 'master' into snyk-fix-5oeu5e 2019-07-27 01:57:08 +02:00
snyk-test
53228a2662 fix: client/package.json to reduce vulnerabilities
The following vulnerabilities are fixed with an upgrade:
- https://snyk.io/vuln/SNYK-JS-JSYAML-173999
- https://snyk.io/vuln/SNYK-JS-JSYAML-174129
2019-07-26 23:56:40 +00:00
2a9fa27341 Merge pull request #113 from KevinMidboe/snyk-fix-25dne6
[Snyk] Fix for 1 vulnerable dependencies
2019-07-27 01:56:24 +02:00
3068281461 Update README.md 2019-07-27 01:55:43 +02:00
81e9fe5b15 Testing for a running application is a bit weird, disabling it for now 2019-07-27 01:42:44 +02:00
5d2e375213 Upgraded node version for travis from 8 to 11 2019-07-27 01:34:15 +02:00
7ede37039a info-success-response is now a list so need this reflected in this test also. Changed the port we test for to whats in our config/test.jsono 2019-07-27 01:30:08 +02:00
8e23ae5a27 Added babel for ES6 functionality. In this case the new import statements 2019-07-27 01:28:41 +02:00
04ba094a14 Throw more errors and cleanup some unmerged code 2019-07-26 21:55:59 +02:00
23f9911237 Throw errors when failing to create user 2019-07-26 21:54:13 +02:00
3b27af1f83 Error handling for themoviedb api response codes that are not 200. Started with 401 and 404. See issue #116 for info. 2019-07-26 21:52:20 +02:00
afb7af46b8 The test uses the cached to not need to query themoviedb, but the function that would in prod now saves a Class containing movie result and extras such as credits. 2019-07-26 21:09:58 +02:00
6ba8ca2add Updated search query response 2019-07-26 21:07:26 +02:00
135375cb94 instead of "describe" "xdescribe" was defined which made the test pending in results. This has now been resolved. 2019-07-26 21:06:45 +02:00
e5d5bdefd6 Updated cache key and cleaned up formatting 2019-07-26 21:05:45 +02:00
6f9ca9e067 Added package and commands for generating documentation and upgraded mocha 2019-07-26 20:56:33 +02:00
c42195d242 Removed swap file 2019-07-25 00:53:40 +02:00
a5aaf1bfca Merge branch 'api/v2' of github.com:KevinMidboe/seasonedShows into api/v2 2019-07-25 00:53:16 +02:00
af7b1f2424 Script for updating all requested and downloading request status to downloaded if exist in plex 2019-07-25 00:48:16 +02:00
6aba9774c6 When requesting all request elements we now also return the page as int not string 2019-07-25 00:47:17 +02:00
e19cfb5870 Updated formatting 2019-07-25 00:24:04 +02:00
144b27f128 Renamed token variable from user to username 2019-07-25 00:23:32 +02:00
12afbf6364 Tokens can also have a admin property. When admin is defined its included in the jwt token. 2019-07-25 00:13:28 +02:00
8a5ab204e1 Change node bcrypt package from bcrypt-nodejs to bcrypt. Change response message on invalid username/pass and changed to bcrypt syntax for compare and hash. 2019-07-24 23:02:06 +02:00
3f04d9bc56 Update script for updating all plex statuses of requestes. 2019-07-24 23:02:06 +02:00
de50805d1e Handle both status code 301 and 302 from jackett 2019-07-15 18:16:46 +02:00
snyk-bot
4eaa60b044 fix: seasoned_api/package.json to reduce vulnerabilities
The following vulnerabilities are fixed with an upgrade:
- https://snyk.io/vuln/SNYK-JS-MPATH-72672
2018-12-13 03:19:03 +00:00
snyk-bot
7db8f752c5 fix: seasoned_api/package.json to reduce vulnerabilities
The following vulnerabilities are fixed with an upgrade:
- https://snyk.io/vuln/npm:base64url:20180511
- https://snyk.io/vuln/npm:cryptiles:20180710
- https://snyk.io/vuln/npm:extend:20180424
- https://snyk.io/vuln/npm:stringstream:20180511
2018-12-08 03:59:04 +00:00
77 changed files with 5674 additions and 1656 deletions

View File

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

View File

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

View File

@@ -12,7 +12,7 @@
}, },
"dependencies": { "dependencies": {
"clean-webpack-plugin": "^0.1.17", "clean-webpack-plugin": "^0.1.17",
"css-loader": "^0.28.4", "css-loader": "^1.0.0",
"html-webpack-plugin": "^2.28.0", "html-webpack-plugin": "^2.28.0",
"path": "^0.12.7", "path": "^0.12.7",
"react": "^15.6.1", "react": "^15.6.1",
@@ -30,8 +30,8 @@
"redux-thunk": "^2.2.0", "redux-thunk": "^2.2.0",
"urijs": "^1.18.12", "urijs": "^1.18.12",
"webfontloader": "^1.6.28", "webfontloader": "^1.6.28",
"webpack": "^3.5.5", "webpack": "^4.0.0",
"webpack-dev-server": "^2.4.5", "webpack-dev-server": "^3.1.11",
"webpack-merge": "^4.1.0" "webpack-merge": "^4.1.0"
}, },
"devDependencies": { "devDependencies": {

View File

@@ -7,39 +7,51 @@
}, },
"main": "webserver/server.js", "main": "webserver/server.js",
"scripts": { "scripts": {
"start": "cross-env SEASONED_CONFIG=conf/development.json PROD=true NODE_PATH=. node src/webserver/server.js", "start": "cross-env SEASONED_CONFIG=conf/development.json PROD=true NODE_PATH=. babel-node src/webserver/server.js",
"test": "cross-env SEASONED_CONFIG=conf/test.json NODE_PATH=. mocha --recursive test/unit test/system", "test": "cross-env SEASONED_CONFIG=conf/test.json NODE_PATH=. mocha --require @babel/register --recursive test/unit test/system",
"coverage": "cross-env SEASONED_CONFIG=conf/test.json NODE_PATH=. nyc mocha --recursive test && nyc report --reporter=text-lcov | coveralls", "coverage": "cross-env SEASONED_CONFIG=conf/test.json NODE_PATH=. nyc mocha --require @babel/register --recursive test && nyc report --reporter=text-lcov | coveralls",
"lint": "./node_modules/.bin/eslint src/", "lint": "./node_modules/.bin/eslint src/",
"update": "cross-env SEASONED_CONFIG=conf/development.json NODE_PATH=. node 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": { "dependencies": {
"axios": "^0.18.0", "axios": "^0.18.0",
"bcrypt-nodejs": "^0.0.3", "bcrypt": "^3.0.6",
"body-parser": "~1.18.2", "body-parser": "~1.18.2",
"cross-env": "~5.1.4", "cross-env": "~5.1.4",
"express": "~4.16.0", "express": "~4.16.0",
"jsonwebtoken": "^8.0.1", "express-graphql": "^0.9.0",
"km-moviedb": "^0.2.13", "express-reload": "^1.2.0",
"mongoose": "~5.0.11", "graphql": "^14.5.8",
"jsonwebtoken": "^8.2.0",
"km-moviedb": "^0.2.12", "km-moviedb": "^0.2.12",
"node-cache": "^4.1.1", "node-cache": "^4.1.1",
"node-fetch": "^2.6.0",
"python-shell": "^0.5.0", "python-shell": "^0.5.0",
"request": "^2.85.0", "raven": "^2.4.2",
"request": "^2.87.0",
"request-promise": "^4.2", "request-promise": "^4.2",
"sqlite3": "^4.0.0" "sqlite3": "^4.0.0"
}, },
"devDependencies": { "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": "^4.9.0",
"eslint-config-airbnb-base": "^12.1.0", "eslint-config-airbnb-base": "^12.1.0",
"eslint-plugin-import": "^2.8.0", "eslint-plugin-import": "^2.8.0",
"istanbul": "^0.4.5", "istanbul": "^0.4.5",
"mocha": "^5.0.4", "mocha": "^6.2.0",
"mocha-lcov-reporter": "^1.3.0", "mocha-lcov-reporter": "^1.3.0",
"nyc": "^11.6.0", "nyc": "^11.6.0",
"raven": "^2.4.2",
"supertest": "^3.0.0", "supertest": "^3.0.0",
"supertest-as-promised": "^4.0.1" "supertest-as-promised": "^4.0.1",
"typescript": "^3.5.3"
} }
} }

View File

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

View File

@@ -12,7 +12,7 @@ function getMagnetFromURL(url) {
resolve(url) resolve(url)
http.get(options, (res) => { http.get(options, (res) => {
if (res.statusCode == 301) { if (res.statusCode == 301 || res.statusCode == 302) {
resolve(res.headers.location) resolve(res.headers.location)
} }
}); });
@@ -21,7 +21,7 @@ function getMagnetFromURL(url) {
async function find(searchterm, callback) { async function find(searchterm, callback) {
const options = { const options = {
pythonPath: '../torrent_search/env/bin/python3.6', pythonPath: '../torrent_search/env/bin/python3',
scriptPath: '../torrent_search', scriptPath: '../torrent_search',
args: [searchterm, '-s', 'jackett', '-f', '--print'] args: [searchterm, '-s', 'jackett', '-f', '--print']
} }
@@ -35,7 +35,7 @@ async function callPythonAddMagnet(url, callback) {
getMagnetFromURL(url) getMagnetFromURL(url)
.then((magnet) => { .then((magnet) => {
const options = { const options = {
pythonPath: '../delugeClient/env/bin/python3.6', pythonPath: '../delugeClient/env/bin/python3',
scriptPath: '../delugeClient', scriptPath: '../delugeClient',
args: ['add', magnet] args: ['add', magnet]
}; };
@@ -51,13 +51,10 @@ async function callPythonAddMagnet(url, callback) {
async function SearchPiratebay(query) { async function SearchPiratebay(query) {
return await new Promise((resolve, reject) => find(query, (err, results) => { return await new Promise((resolve, reject) => find(query, (err, results) => {
if (err) { if (err) {
/* eslint-disable no-console */
console.log('THERE WAS A FUCKING ERROR!\n', err); console.log('THERE WAS A FUCKING ERROR!\n', err);
reject(Error('There was a error when searching for torrents')); reject(Error('There was a error when searching for torrents'));
} }
if (results) { if (results) {
/* eslint-disable no-console */
console.log('result', results);
resolve(JSON.parse(results, null, '\t')); resolve(JSON.parse(results, null, '\t'));
} }
})); }));

View File

@@ -1,59 +1,89 @@
const axios = require('axios') const fetch = require('node-fetch')
const convertPlexToMovie = require('src/plex/convertPlexToMovie') const convertPlexToMovie = require('src/plex/convertPlexToMovie')
const convertPlexToShow = require('src/plex/convertPlexToShow') const convertPlexToShow = require('src/plex/convertPlexToShow')
const convertPlexToEpisode = require('src/plex/convertPlexToEpisode') const convertPlexToEpisode = require('src/plex/convertPlexToEpisode')
const { Movie, Show, Person } = require('src/tmdb/types');
// const { Movie, }
// TODO? import class definitions to compare types ?
// what would typescript do?
class Plex { class Plex {
constructor(ip) { constructor(ip, port=32400) {
this.plexIP = ip this.plexIP = ip
this.plexPort = 32400 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) { existsInPlex(tmdbMovie) {
return Promise.resolve() return this.search(tmdbMovie.title)
.then(() => this.search(tmdbMovie.title)) .then(plexMovies => plexMovies.some(plex => this.matchTmdbAndPlexMedia(plex, tmdbMovie)))
// TODO handle this when whitelist of local ip is not set in plex }
.catch((error) => { console.error('Unable to search plex')})
.then((plexMovies) => {
const matches = plexMovies.some((plexMovie) => {
return tmdbMovie.title === plexMovie.title && tmdbMovie.type === plexMovie.type
})
tmdbMovie.existsInPlex = matches successfullResponse(response) {
return tmdbMovie const { status, statusText } = response
})
if (status === 200) {
return response.json()
} else {
throw { message: statusText, status: status }
}
} }
search(query) { search(query) {
const url = `http://${this.plexIP}:${this.plexPort}/hubs/search?query=${query}`
const options = { const options = {
baseURL: `http://${this.plexIP}:${this.plexPort}`, timeout: 2000,
url: '/hubs/search', headers: { 'Accept': 'application/json' }
params: { query: query },
responseType: 'json',
timeout: 3000
} }
return Promise.resolve() return fetch(url, options)
.then(() => axios.request(options)) .then(this.successfullResponse)
.catch((error) => { throw new Error(`Unable to search plex library`, error) }) .then(this.mapResults)
.then(response => this.mapResults(response)) .catch(error => {
if (error.type === 'request-timeout') {
throw { message: 'Plex did not respond', status: 408, success: false }
}
throw error
})
} }
mapResults(response) { mapResults(response) {
return response.data.MediaContainer.Hub.reduce((result, hub) => { if (response === undefined || response.MediaContainer === undefined) {
if (hub.type === 'movie' && hub.Metadata !== undefined) { console.log('response was not valid to map', response)
return [...result, ...hub.Metadata.map(convertPlexToMovie)] return []
} }
else if (hub.type === 'show' && hub.Metadata !== undefined) {
return [...result, ...hub.Metadata.map(convertPlexToShow)]
}
else if (hub.type === 'episode' && hub.Metadata !== undefined) {
return [...result, ...hub.Metadata.map(convertPlexToEpisode)]
}
return result 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()
} }
} }

View File

@@ -6,7 +6,6 @@ class Show {
this.rating = null; this.rating = null;
this.seasons = null; this.seasons = null;
this.episodes = null; this.episodes = null;
this.type = '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

@@ -23,6 +23,7 @@ class RequestRepository {
downloaded: '(select status from requests where id is request.id and type is request.type limit 1)', 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)', // deluge: '(select status from deluge_torrent where id is request.id and type is request.type limit 1)',
// fetchAllFilterStatus: 'select * from request where ' // 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 ?' read: 'select id, title, year, type, status, requested_by, ip, date, user_agent from requests where id is ? and type is ?'
}; };
} }
@@ -106,11 +107,17 @@ class RequestRepository {
* @returns {Promise} * @returns {Promise}
*/ */
getRequestByIdAndType(id, type) { getRequestByIdAndType(id, type) {
return Promise.resolve() return this.database.get(this.queries.readWithoutUserData, [id, type])
.then(() => this.database.get(this.queries.read, [id, type]))
.then(row => { .then(row => {
assert(row, 'Could not find request item with that id and type') assert(row, 'Could not find request item with that id and type')
return JSON.stringify(row) return {
id: row.id,
title: row.title,
year: row.year,
type: row.type,
status: row.status,
requested_date: new Date(row.date)
}
}) })
} }
@@ -124,6 +131,7 @@ class RequestRepository {
*/ */
fetchAll(page=1, sort_by=undefined, sort_direction='asc', filter=undefined, query=undefined) { fetchAll(page=1, sort_by=undefined, sort_direction='asc', filter=undefined, query=undefined) {
// TODO implemented sort and filter // TODO implemented sort and filter
page = parseInt(page)
let fetchQuery = this.queries.fetchAll let fetchQuery = this.queries.fetchAll
let fetchTotalResults = this.queries.totalRequests let fetchTotalResults = this.queries.totalRequests
let fetchParams = [page] let fetchParams = [page]
@@ -144,11 +152,15 @@ class RequestRepository {
const totalRequests = sqliteResponse['totalRequests'] const totalRequests = sqliteResponse['totalRequests']
const totalPages = Math.ceil(totalRequests / 26) const totalPages = Math.ceil(totalRequests / 26)
return [ rows.map(item => { item.poster = item.poster_path; return item }), totalPages ] 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)) return Promise.all(this.mapToTmdbByType(rows))
}) })
.then(([result, totalPages]) => Promise.resolve({ .then(([result, totalPages, totalRequests]) => Promise.resolve({
results: result, total_results: result.length, page: page, total_pages: totalPages results: result, total_results: totalRequests, page: page, total_pages: totalPages
})) }))
.catch(error => { console.log(error);throw error }) .catch(error => { console.log(error);throw error })
} }

View File

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

View File

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

View File

@@ -1,49 +0,0 @@
const Movie = require('src/tmdb/types/movie');
const tmdbSwitcher = (tmdbMovie, property) => tmdbMovie[property]
function convertTmdbToMovie(tmdbMovie, credits=undefined) {
const movie = new Movie(tmdbMovie.id, tmdbMovie.title)
movie.overview = tmdbMovie.overview;
movie.rank = tmdbMovie.vote_average;
if (credits) {
movie.credits = credits;
}
if (tmdbMovie.release_date !== undefined) {
movie.release_date = new Date(tmdbMovie.release_date);
movie.year = movie.release_date.getFullYear();
}
if (tmdbMovie.poster_path !== undefined) {
movie.poster = tmdbMovie.poster_path;
}
if (tmdbMovie.backdrop_path !== undefined) {
movie.backdrop = tmdbMovie.backdrop_path;
}
if (tmdbMovie.status !== undefined) {
movie.status = tmdbMovie.status;
}
if (tmdbMovie.genres !== undefined) {
movie.genres = tmdbMovie.genres.map(genre => genre.name);
}
if (tmdbMovie.tagline !== undefined) {
movie.tagline = tmdbMovie.tagline;
}
if (tmdbMovie.runtime !== undefined) {
movie.runtime = tmdbMovie.runtime;
}
if (tmdbMovie.imdb_id !== undefined) {
movie.imdb_id = tmdbMovie.imdb_id;
}
return movie;
}
module.exports = convertTmdbToMovie;

View File

@@ -1,30 +0,0 @@
const Person = require('src/tmdb/types/person');
const convertTmdbToMovie = require('src/tmdb/convertTmdbToMovie');
function convertTmdbToPerson(tmdbPerson, cast=undefined) {
const person = new Person(tmdbPerson.id, tmdbPerson.name);
if (tmdbPerson.profile_path !== undefined) {
person.poster = tmdbPerson.profile_path;
}
if (tmdbPerson.birthday !== undefined) {
person.birthday = new Date(tmdbPerson.birthday);
}
if (tmdbPerson.deathday !== undefined) {
person.deathday = tmdbPerson.deathday;
}
if (tmdbPerson.known_for !== undefined) {
person.known_for = tmdbPerson.known_for.map(convertTmdbToMovie);
}
if (cast) {
person.cast = cast.cast;
}
return person;
}
module.exports = convertTmdbToPerson;

View File

@@ -1,62 +0,0 @@
const TMDB = require('src/media_classes/tmdb');
const Movie = require('src/types/movie');
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 convertTmdbToMovie(tmdb) {
const year =
const movie = new Movie();
let seasoned = undefined;
if (tmdb.id && tmdb.title && tmdb.release_date) {
const year = tmdb.release_date.getFullYear();
seasoned = new Movie(tmdb.id, tmdb.title, year);
}
else if (tmdb.id && tmdb.name && tmdb.first_air_date) {
const year = tmdb.first_air_date.getFullYear();
seasoned = new Show(tmdb.id, tmdb.name, year);
seasoned.seasons = tmdb.number_of_season;
seasoned.episodes = tmdb.episodes;
return
}
}
const title = tmdb.title || tmdb.name;
const year = translateYear(tmdb.release_date || tmdb.first_air_date);
const type = manualType || convertType(tmdb.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,41 +0,0 @@
const Show = require('src/tmdb/types/show');
function convertTmdbToShow(tmdbShow, credits=undefined) {
const show = new Show(tmdbShow.id, tmdbShow.name)
show.seasons = tmdbShow.number_of_seasons;
show.episodes = tmdbShow.number_of_episodes;
show.overview = tmdbShow.overview;
show.rank = tmdbShow.vote_average;
if (credits) {
show.credits = credits
}
if (tmdbShow.genres !== undefined) {
show.genres = tmdbShow.genres.map(genre => genre.name);
}
if (tmdbShow.first_air_date !== undefined) {
show.first_air_date = new Date(tmdbShow.first_air_date);
show.year = show.first_air_date.getFullYear();
}
if (tmdbShow.poster_path !== undefined) {
show.poster = tmdbShow.poster_path;
}
if (tmdbShow.backdrop_path !== undefined) {
show.backdrop = tmdbShow.backdrop_path;
}
if (tmdbShow.status !== undefined) {
show.status = tmdbShow.status;
}
if (tmdbShow.episode_run_time !== undefined) {
show.runtime = tmdbShow.runtime;
}
return show;
}
module.exports = convertTmdbToShow;

View File

@@ -1,7 +1,7 @@
const moviedb = require('km-moviedb'); const moviedb = require('km-moviedb');
const convertTmdbToMovie = require('src/tmdb/convertTmdbToMovie');
const convertTmdbToShow = require('src/tmdb/convertTmdbToShow'); const { Movie, Show, Person, Credits, ReleaseDates } = require('src/tmdb/types');
const convertTmdbToPerson = require('src/tmdb/convertTmdbToPerson'); // const { tmdbInfo } = require('src/tmdb/types')
class TMDB { class TMDB {
constructor(cache, apiKey, tmdbLibrary) { constructor(cache, apiKey, tmdbLibrary) {
@@ -12,8 +12,11 @@ class TMDB {
movieSearch: 'mos', movieSearch: 'mos',
showSearch: 'ss', showSearch: 'ss',
personSearch: 'ps', personSearch: 'ps',
movieInfo: 'mi', movieInfo: 'mi',
showInfo: 'si', movieCredits: 'mc',
movieReleaseDates: 'mrd',
showInfo: 'si',
showCredits: 'sc',
personInfo: 'pi', personInfo: 'pi',
miscNowPlayingMovies: 'npm', miscNowPlayingMovies: 'npm',
miscPopularMovies: 'pm', miscPopularMovies: 'pm',
@@ -25,70 +28,54 @@ class TMDB {
}; };
} }
/**
* 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).
* @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.');
}
});
}
/**
* Retrive 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));
}
/** /**
* Retrieve a specific movie by id from TMDB. * Retrieve a specific movie by id from TMDB.
* @param {Number} identifier of the movie you want to retrieve * @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 * @returns {Promise} succeeds if movie was found
*/ */
movieInfo(identifier, credits=false) { movieInfo(identifier) {
const query = { id: identifier }; const query = { id: identifier };
const cacheKey = `${this.cacheTags.movieInfo}:${identifier}:${credits}`; const cacheKey = `${this.cacheTags.movieInfo}:${identifier}`;
const requests = [this.tmdb('movieInfo', query)] 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))
}
if (credits) { /**
requests.push(this.tmdb('movieCredits', query)) * 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 Promise.resolve() return this.cache.get(cacheKey)
.then(() => this.cache.get(cacheKey)) .catch(() => this.tmdb('movieCredits', query))
.catch(() => Promise.all(requests)) .catch(tmdbError => tmdbErrorResponse(tmdbError, 'movie credits'))
.catch((error) => { console.log(error); throw new Error('Could not find a movie with that id.'); }) .then(credits => this.cache.set(cacheKey, credits, 1))
.then(([movies, credits]) => this.cache.set(cacheKey, [movies, credits])) .then(credits => Credits.convertFromTmdbResponse(credits))
.then(([movies, credits]) => convertTmdbToMovie(movies, 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))
} }
/** /**
@@ -97,22 +84,26 @@ class TMDB {
* @param {String} type filter results by type (default show). * @param {String} type filter results by type (default show).
* @returns {Promise} succeeds if show was found * @returns {Promise} succeeds if show was found
*/ */
showInfo(identifier, credits=false) { showInfo(identifier) {
const query = { id: identifier }; const query = { id: identifier };
const cacheKey = `${this.cacheTags.showInfo}:${identifier}:${credits}`; const cacheKey = `${this.cacheTags.showInfo}:${identifier}`;
const requests = [this.tmdb('tvInfo', query)] 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))
}
if (credits) { showCredits(identifier) {
requests.push(this.tmdb('tvCredits', query)) const query = { id: identifier }
} const cacheKey = `${this.cacheTags.showCredits}:${identifier}`
return Promise.resolve() return this.cache.get(cacheKey)
.then(() => this.cache.get(cacheKey)) .catch(() => this.tmdb('tvCredits', query))
.catch(() => Promise.all(requests)) .catch(tmdbError => tmdbErrorResponse(tmdbError, 'show credits'))
.catch(() => { throw new Error('Could not find a show with that id.'); }) .then(credits => this.cache.set(cacheKey, credits, 1))
.then(([shows, credits]) => this.cache.set(cacheKey, [shows, credits])) .then(credits => Credits.convertFromTmdbResponse(credits))
.then(([shows, credits]) => convertTmdbToShow(shows, credits))
} }
/** /**
@@ -125,25 +116,20 @@ class TMDB {
const query = { id: identifier }; const query = { id: identifier };
const cacheKey = `${this.cacheTags.personInfo}:${identifier}`; const cacheKey = `${this.cacheTags.personInfo}:${identifier}`;
return Promise.resolve() return this.cache.get(cacheKey)
.then(() => this.cache.get(cacheKey)) .catch(() => this.tmdb('personInfo', query))
.catch(() => Promise.all([this.tmdb('personInfo', query), this.tmdb('personCombinedCredits', query)])) .catch(tmdbError => tmdbErrorResponse(tmdbError, 'person info'))
.catch(() => { throw new Error('Could not find a person with that id.'); }) .then(person => this.cache.set(cacheKey, person, 1))
.then(([person, cast]) => this.cache.set(cacheKey, [person, cast])) .then(person => Person.convertFromTmdbResponse(person))
.then(([person, cast]) => convertTmdbToPerson(person, cast))
.catch(err => new Error('Unable to convert result to person', err))
} }
multiSearch(search_query, page=1) { multiSearch(search_query, page=1) {
const query = { query: search_query, page: page }; const query = { query: search_query, page: page };
const cacheKey = `${this.cacheTags.multiSearch}:${page}:${search_query}`; const cacheKey = `${this.cacheTags.multiSearch}:${page}:${search_query}`;
return Promise.resolve() return this.cache.get(cacheKey)
.then(() => this.cache.get(cacheKey))
.catch(() => this.tmdb('searchMulti', query)) .catch(() => this.tmdb('searchMulti', query))
.catch(() => { throw new Error('Could not complete search to tmdb'); }) .catch(tmdbError => tmdbErrorResponse(tmdbError, 'search results'))
.then(response => this.cache.set(cacheKey, response)) .then(response => this.cache.set(cacheKey, response, 1))
.then(response => this.mapResults(response)); .then(response => this.mapResults(response));
} }
@@ -157,13 +143,11 @@ class TMDB {
const tmdbquery = { query: query, page: page }; const tmdbquery = { query: query, page: page };
const cacheKey = `${this.cacheTags.movieSearch}:${page}:${query}`; const cacheKey = `${this.cacheTags.movieSearch}:${page}:${query}`;
return Promise.resolve() return this.cache.get(cacheKey)
.then(() => this.cache.get(cacheKey)) .catch(() => this.tmdb('searchMovie', tmdbquery))
.catch(() => this.tmdb('searchMovie', tmdbquery)) .catch(tmdbError => tmdbErrorResponse(tmdbError, 'movie search results'))
.catch(() => { throw new Error('Could not complete movie search to tmdb'); }) .then(response => this.cache.set(cacheKey, response, 1))
.then(response => this.cache.set(cacheKey, response)) .then(response => this.mapResults(response, 'movie'))
.then(response => this.mapAndCreateResponse(response, convertTmdbToMovie))
.catch((error) => { console.log(error); throw new Error('Could not parse movie search result') })
} }
/** /**
@@ -175,13 +159,12 @@ class TMDB {
showSearch(query, page=1) { showSearch(query, page=1) {
const tmdbquery = { query: query, page: page }; const tmdbquery = { query: query, page: page };
const cacheKey = `${this.cacheTags.showSearch}:${page}:${query}`; const cacheKey = `${this.cacheTags.showSearch}:${page}:${query}`;
return Promise.resolve()
.then(() => this.cache.get(cacheKey)) return this.cache.get(cacheKey)
.catch(() => this.tmdb('searchTv', tmdbquery)) .catch(() => this.tmdb('searchTv', tmdbquery))
.catch(() => { throw new Error('Could not complete show search to tmdb'); }) .catch(tmdbError => tmdbErrorResponse(tmdbError, 'tv search results'))
.then(response => this.cache.set(cacheKey, response)) .then(response => this.cache.set(cacheKey, response, 1))
.then(response => this.mapAndCreateResponse(response, convertTmdbToShow)) .then(response => this.mapResults(response, 'show'))
.catch((error) => { console.log(error); throw new Error('Could not parse show search result') })
} }
/** /**
@@ -191,78 +174,36 @@ class TMDB {
* @returns {Promise} dict with query results, current page and total_pages * @returns {Promise} dict with query results, current page and total_pages
*/ */
personSearch(query, page=1) { personSearch(query, page=1) {
const tmdbquery = { query: query, page: page };
const tmdbquery = { query: query, page: page, include_adult: true };
const cacheKey = `${this.cacheTags.personSearch}:${page}:${query}`; const cacheKey = `${this.cacheTags.personSearch}:${page}:${query}`;
return Promise.resolve()
.then(() => this.cache.get(cacheKey))
.catch(() => this.tmdb('searchPerson', tmdbquery))
.catch(() => { throw new Error('Could not complete person search to tmdb'); })
.then(response => this.cache.set(cacheKey, response))
.then(response => this.mapAndCreateResponse(response, convertTmdbToPerson))
.catch((error) => { console.log(error); throw new Error('Could not parse person search result') })
}
mapAndCreateResponse(response, resultConvertFunction) { return this.cache.get(cacheKey)
// console.log(response) .catch(() => this.tmdb('searchPerson', tmdbquery))
return { .catch(tmdbError => tmdbErrorResponse(tmdbError, 'person search results'))
results: response.results.map(resultConvertFunction), .then(response => this.cache.set(cacheKey, response, 1))
page: response.page, .then(response => this.mapResults(response, 'person'))
total_results: response.total_results,
total_pages: response.total_pages
}
} }
movieList(listname, page = 1) { movieList(listname, page = 1) {
const query = { page: page }; const query = { page: page };
const cacheKey = `${this.cacheTags[listname]}:${page}`; const cacheKey = `${this.cacheTags[listname]}:${page}`;
return Promise.resolve() return this.cache.get(cacheKey)
.then(() => this.cache.get(cacheKey))
.catch(() => this.tmdb(listname, query)) .catch(() => this.tmdb(listname, query))
.catch(() => { throw new Error('Unable to get movie list from tmdb')}) .catch(tmdbError => this.tmdbErrorResponse(tmdbError, 'movie list ' + listname))
.then(response => this.cache.set(cacheKey, response)) .then(response => this.cache.set(cacheKey, response, 1))
.then(response => this.mapAndCreateResponse(response, convertTmdbToMovie)); .then(response => this.mapResults(response, 'movie'))
} }
showList(listname, page = 1) { showList(listname, page = 1) {
const query = { page: page }; const query = { page: page };
const cacheKey = `${this.cacheTags[listname]}:${page}`; const cacheKey = `${this.cacheTags[listname]}:${page}`;
return Promise.resolve()
.then(() => this.cache.get(cacheKey)) return this.cache.get(cacheKey)
.catch(() => this.tmdb(listname, query)) .catch(() => this.tmdb(listname, query))
.catch(() => { throw new Error('Unable to get show list from tmdb')}) .catch(tmdbError => this.tmdbErrorResponse(tmdbError, 'show list ' + listname))
.then(response => this.cache.set(cacheKey, response)) .then(response => this.cache.set(cacheKey, response, 1))
.then(response => this.mapAndCreateResponse(response, convertTmdbToShow)); .then(response => this.mapResults(response, 'show'))
}
/**
* Fetches a given list from tmdb.
* @param {String} listName Name of list
* @param {String} type filter results by type (default movie)
* @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));
}
popular(type='movie', page=1) {
const query = { type: type, page: page };
const cacheKey = `${this.cacheTags.popular}:${type}:${page}`;
return Promise.resolve()
.then(() => this.cache.get(cacheKey))
.catch(() => this.tmdb('miscPopularMovies', query))
.catch((e) => { throw new Error(`Error fetching popular list of type ${type} : ${e}`) })
.then(response => this.cache.set(cacheKey, response))
.then(response => this.mapResults(response, type));
} }
/** /**
@@ -271,14 +212,20 @@ class TMDB {
* @param {String} The type declared in listSearch. * @param {String} The type declared in listSearch.
* @returns {Promise} dict with tmdb results, mapped as movie/show objects. * @returns {Promise} dict with tmdb results, mapped as movie/show objects.
*/ */
mapResults(response, _) { mapResults(response, type=undefined) {
let results = response.results.map((result) => { // console.log(response.results)
if (result.media_type === 'movie') { // response.results.map(te => console.table(te))
return convertTmdbToMovie(result);
} else if (result.media_type === 'tv') { let results = response.results.map(result => {
return convertTmdbToShow(result); if (type === 'movie' || result.media_type === 'movie') {
} else if (result.media_type === 'person') { const movie = Movie.convertFromTmdbResponse(result)
return convertTmdbToPerson(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()
} }
}) })
@@ -312,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; 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

@@ -1,20 +1,62 @@
class Movie { class Movie {
constructor(id, title, year=null, overview=null, poster=null, backdrop=null, rank=null, genres=null, status=null, constructor(id, title, year=undefined, overview=undefined, poster=undefined, backdrop=undefined,
tagline=null, runtime=null, imdb_id=null) { releaseDate=undefined, rating=undefined, genres=undefined, productionStatus=undefined,
tagline=undefined, runtime=undefined, imdb_id=undefined, popularity=undefined) {
this.id = id; this.id = id;
this.title = title; this.title = title;
this.year = year; this.year = year;
this.overview = overview; this.overview = overview;
this.poster = poster; this.poster = poster;
this.backdrop = backdrop; this.backdrop = backdrop;
this.rank = rank; this.releaseDate = releaseDate;
this.rating = rating;
this.genres = genres; this.genres = genres;
this.status = status; this.productionStatus = productionStatus;
this.tagline = tagline; this.tagline = tagline;
this.runtime = runtime; this.runtime = runtime;
this.imdb_id = imdb_id; this.imdb_id = imdb_id;
this.popularity = popularity;
this.type = 'movie'; 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; module.exports = Movie;

View File

@@ -1,13 +1,37 @@
class Person { class Person {
constructor(id, name, poster=null, birthday=null, deathday=null, known_for=null) { constructor(id, name, poster=undefined, birthday=undefined, deathday=undefined,
adult=undefined, knownForDepartment=undefined) {
this.id = id; this.id = id;
this.name = name; this.name = name;
this.poster = poster; this.poster = poster;
this.birthday = birthday; this.birthday = birthday;
this.deathday = deathday; this.deathday = deathday;
this.known_for = known_for; this.adult = adult;
this.knownForDepartment = knownForDepartment;
this.type = 'person'; 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; 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

@@ -1,20 +1,50 @@
class Show { class Show {
constructor(id, title, year=null, seasons=null, episodes=null, overview=null, rank=null, genres=null, constructor(id, title, year=undefined, overview=undefined, poster=undefined, backdrop=undefined,
poster=null, backdrop=null, status=null, runtime=null) { seasons=undefined, episodes=undefined, rank=undefined, genres=undefined, status=undefined,
runtime=undefined) {
this.id = id; this.id = id;
this.title = title; this.title = title;
this.year = year; this.year = year;
this.seasons = seasons;
this.episodes = episodes;
this.overview = overview; this.overview = overview;
this.rank = rank;
this.genres = genres;
this.poster = poster; this.poster = poster;
this.backdrop = backdrop; this.backdrop = backdrop;
this.status = status; this.seasons = seasons;
this.episodes = episodes;
this.rank = rank;
this.genres = genres;
this.productionStatus = status;
this.runtime = runtime; this.runtime = runtime;
this.type = 'show'; 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; module.exports = Show;

View File

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

View File

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

View File

@@ -1,4 +1,5 @@
const express = require('express'); const express = require('express');
// const reload = require('express-reload');
const Raven = require('raven'); const Raven = require('raven');
const bodyParser = require('body-parser'); const bodyParser = require('body-parser');
const tokenToUser = require('./middleware/tokenToUser'); const tokenToUser = require('./middleware/tokenToUser');
@@ -23,13 +24,11 @@ const allowedOrigins = ['https://kevinmidboe.com', 'http://localhost:8080'];
app.use(bodyParser.urlencoded({ extended: true })); app.use(bodyParser.urlencoded({ extended: true }));
/* Decode the Authorization header if provided */ /* Decode the Authorization header if provided */
router.use(tokenToUser); app.use(tokenToUser);
// TODO: Should have a separate middleware/router for handling headers. // TODO: Should have a separate middleware/router for handling headers.
router.use((req, res, next) => { router.use((req, res, next) => {
// TODO add logging of all incoming // TODO add logging of all incoming
console.log('Request: ', req.originalUrl);
const origin = req.headers.origin; const origin = req.headers.origin;
if (allowedOrigins.indexOf(origin) > -1) { if (allowedOrigins.indexOf(origin) > -1) {
res.setHeader('Access-Control-Allow-Origin', origin); res.setHeader('Access-Control-Allow-Origin', origin);
@@ -50,6 +49,32 @@ app.use(function onError(err, req, res, next) {
res.end(res.sentry + '\n'); res.end(res.sentry + '\n');
}); });
/**
* GraphQL
*/
var graphqlHTTP = require('express-graphql');
var { buildSchema } = require('graphql');
// var schema = buildSchema(`
// type Query {
// hello: String
// }`);
const schema = require('./graphql/requests.js');
const roots = { hello: () => 'Hello world!' };
app.use('/graphql', graphqlHTTP({
schema: schema.schema,
graphiql: true
}))
/** /**
* User * User
*/ */
@@ -79,9 +104,14 @@ router.get('/v2/show/now_playing', listController.nowPlayingShows);
router.get('/v2/show/popular', listController.popularShows); router.get('/v2/show/popular', listController.popularShows);
router.get('/v2/show/top_rated', listController.topRatedShows); router.get('/v2/show/top_rated', listController.topRatedShows);
router.get('/v2/movie/:id', require('./controllers/info/movieInfo.js')); router.get('/v2/movie/:id/credits', require('./controllers/movie/credits.js'));
router.get('/v2/show/:id', require('./controllers/info/showInfo.js')); router.get('/v2/movie/:id/release_dates', require('./controllers/movie/releaseDates.js'));
router.get('/v2/person/:id', require('./controllers/info/personInfo.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 * Plex
*/ */
@@ -113,13 +143,6 @@ router.put('/v1/plex/request/:requestId', mustBeAuthenticated, require('./contro
router.get('/v1/pirate/search', mustBeAuthenticated, require('./controllers/pirate/searchTheBay.js')); router.get('/v1/pirate/search', mustBeAuthenticated, require('./controllers/pirate/searchTheBay.js'));
router.post('/v1/pirate/add', mustBeAuthenticated, require('./controllers/pirate/addMagnet.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 * git
*/ */

View File

@@ -1,30 +0,0 @@
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'));
/**
* 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;
const { credits } = req.query;
const movie = await tmdb.movieInfo(movieId, credits);
plex.existsInPlex(movie)
.catch((error) => { console.log('Error when searching plex'); })
.then(() => {
console.log('movie', movie)
res.send(movie);
}).catch((error) => {
res.status(404).send({ success: false, error: error.message });
});
}
module.exports = movieInfoController;

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 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'));
/**
* 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;
const { credits } = req.query;
const show = await tmdb.showInfo(showId, credits);
plex.existsInPlex(show)
.catch((error) => { console.log('Error when searching plex'); })
.then(() => {
console.log('show', show)
res.send(show);
}).catch((error) => {
res.status(404).send({ success: false, error: error.message });
});
}
module.exports = showInfoController;

View File

@@ -16,70 +16,55 @@ const tmdb = new TMDB(cache, configuration.get('tmdb', 'apiKey'));
// + newly created (tv/latest). // + newly created (tv/latest).
// + movie/latest // + movie/latest
// //
function handleError(error, res) {
const { status, message } = error;
function getTmdbMovieList(res, listname, page) { if (status && message) {
Promise.resolve() res.status(status).send({ success: false, message })
.then(() => tmdb.movieList(listname, page)) } else {
.then((response) => res.send(response)) console.log('caught list controller error', error)
.catch((error) => { res.status(500).send({ message: 'An unexpected error occured while requesting list'})
res.status(500).send({ success: false, error: error.message }); }
})
} }
function getTmdbShowList(res, listname, page) { function handleListResponse(response, res) {
Promise.resolve() return res.send(response)
.then(() => tmdb.showList(listname, page)) .catch(error => handleError(error, res))
.then((response) => res.send(response))
.catch((error) => {
res.status(500).send({ success: false, error: error.message });
})
} }
exports.nowPlayingMovies = (req, res) => { function fetchTmdbList(req, res, listname, type) {
const { page } = req.query; const { page } = req.query;
const listname = 'miscNowPlayingMovies'
getTmdbMovieList(res, listname, page); 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)
} }
exports.popularMovies = (req, res) => { const nowPlayingMovies = (req, res) => fetchTmdbList(req, res, 'miscNowPlayingMovies', 'movie')
const { page } = req.query; const popularMovies = (req, res) => fetchTmdbList(req, res, 'miscPopularMovies', 'movie')
const listname = 'miscPopularMovies' 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')
getTmdbMovieList(res, listname, page); module.exports = {
} nowPlayingMovies,
popularMovies,
exports.topRatedMovies = (req, res) => { topRatedMovies,
const { page } = req.query; upcomingMovies,
const listname = 'miscTopRatedMovies' nowPlayingShows,
popularShows,
getTmdbMovieList(res, listname, page); topRatedShows
}
exports.upcomingMovies = (req, res) => {
const { page } = req.query;
const listname = 'miscUpcomingMovies'
getTmdbMovieList(res, listname, page);
}
exports.nowPlayingShows = (req, res) => {
const { page } = req.query;
const listname = 'tvOnTheAir'
getTmdbShowList(res, listname, page);
}
exports.popularShows = (req, res) => {
const { page } = req.query;
const listname = 'miscPopularTvs'
getTmdbShowList(res, listname, page);
}
exports.topRatedShows = (req, res) => {
const { page } = req.query;
const listname = 'miscTopRatedTvs'
getTmdbShowList(res, listname, page);
} }

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

@@ -13,11 +13,12 @@ const tmdb = new TMDB(cache, configuration.get('tmdb', 'apiKey'));
function personInfoController(req, res) { function personInfoController(req, res) {
const personId = req.params.id; const personId = req.params.id;
tmdb.personInfo(personId) tmdb.personInfo(personId)
.then((person) => { .then(person => res.send(person.createJsonResponse()))
res.send(person); .catch(error => {
}).catch((error) => { res.status(404).send({ success: false, message: error.message });
res.status(404).send({ success: false, error: error.message });
}); });
} }

View File

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

View File

@@ -21,8 +21,8 @@ function updateRequested(req, res) {
.then((result) => { .then((result) => {
res.send({ success: true, results: result }); res.send({ success: true, results: result });
}) })
.catch((error) => { .catch(error => {
res.status(401).send({ success: false, error: error.message }); 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 }); res.send({ success: true, results: requestedItems, total_results: requestedItems.length });
}) })
.catch((error) => { .catch((error) => {
res.status(401).send({ success: false, error: error.message }); res.status(401).send({ success: false, message: error.message });
}); });
} }

View File

@@ -5,11 +5,11 @@ const plexRepository = new PlexRepository(configuration.get('plex', 'ip'));
function playingController(req, res) { function playingController(req, res) {
plexRepository.nowPlaying() plexRepository.nowPlaying()
.then((movies) => { .then(movies => {
res.send(movies); res.send(movies);
}) })
.catch((error) => { .catch(error => {
res.status(500).send({ success: false, error: error.message }); 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 mediaId = req.params.mediaId;
const { type } = req.query; const { type } = req.query;
requestRepository.lookup(mediaId, type) requestRepository.lookup(mediaId, type)
.then((movies) => { .then(movies => {
res.send(movies); res.send(movies);
}).catch((error) => { }).catch(error => {
res.status(404).send({ success: false, error: error.message }); res.status(404).send({ success: false, message: error.message });
}); });
} }

View File

@@ -11,14 +11,14 @@ const plex = new Plex(configuration.get('plex', 'ip'));
function searchPlexController(req, res) { function searchPlexController(req, res) {
const { query, type } = req.query; const { query, type } = req.query;
plex.search(query, type) plex.search(query, type)
.then((movies) => { .then(movies => {
if (movies.length > 0) { if (movies.length > 0) {
res.send(movies); res.send(movies);
} else { } else {
res.status(404).send({ success: false, error: 'Search query did not give any results from plex.'}) res.status(404).send({ success: false, message: 'Search query did not give any results from plex.'})
} }
}).catch((error) => { }).catch(error => {
res.status(500).send({ success: false, error: error.message }); res.status(500).send({ success: false, message: error.message });
}); });
} }

View File

@@ -14,15 +14,15 @@ function searchMediaController(req, res) {
const { query } = req.query; const { query } = req.query;
plexRepository.search(query) plexRepository.search(query)
.then((media) => { .then(media => {
if (media !== undefined || media.length > 0) { if (media !== undefined || media.length > 0) {
res.send(media); res.send(media);
} else { } 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) => { .catch(error => {
res.status(500).send({ success: false, error: error.message }); res.status(500).send({ success: false, message: error.message });
}); });
} }

View File

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

View File

@@ -37,7 +37,7 @@ function submitRequestController(req, res) {
console.log('show') console.log('show')
mediaFunction = tmdbShowInfo mediaFunction = tmdbShowInfo
} else { } else {
res.status(422).send({ success: false, error: 'Incorrect type. Allowed types: "movie" or "show"'}) res.status(422).send({ success: false, message: 'Incorrect type. Allowed types: "movie" or "show"'})
} }
if (mediaFunction === undefined) { res.status(200); return } if (mediaFunction === undefined) { res.status(200); return }
@@ -45,7 +45,7 @@ function submitRequestController(req, res) {
mediaFunction(id) mediaFunction(id)
.then(tmdbMedia => request.requestFromTmdb(tmdbMedia, ip, user_agent, user)) .then(tmdbMedia => request.requestFromTmdb(tmdbMedia, ip, user_agent, user))
.then(() => res.send({ success: true, message: 'Media item successfully requested' })) .then(() => res.send({ success: true, message: 'Media item successfully requested' }))
.catch(err => res.status(500).send({ success: false, error: err.message })) .catch(err => res.status(500).send({ success: false, message: err.message }))
} }
module.exports = submitRequestController; module.exports = submitRequestController;

View File

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

View File

@@ -18,9 +18,9 @@ function fetchAllRequests(req, res) {
Promise.resolve() Promise.resolve()
.then(() => request.fetchAll(page, sort_by, sort_direction, filter, query)) .then(() => request.fetchAll(page, sort_by, sort_direction, filter, query))
.then((result) => res.send(result)) .then(result => res.send(result))
.catch((error) => { .catch(error => {
res.status(404).send({ success: false, error: error.message }); res.status(404).send({ success: false, message: error.message });
}); });
} }

View File

@@ -11,11 +11,10 @@ function fetchAllRequests(req, res) {
const id = req.params.id; const id = req.params.id;
const { type } = req.query; const { type } = req.query;
Promise.resolve() request.getRequestByIdAndType(id, type)
.then(() => request.getRequestByIdAndType(id, type)) .then(result => res.send(result))
.then((result) => res.send(result)) .catch(error => {
.catch((error) => { res.status(404).send({ success: false, message: error.message });
res.status(404).send({ success: false, error: error.message });
}); });
} }

View File

@@ -22,30 +22,31 @@ const tmdbShowInfo = (id) => {
*/ */
function requestTmdbIdController(req, res) { function requestTmdbIdController(req, res) {
const { id, type } = req.body const { id, type } = req.body
console.log('body', req.body)
console.log('id & type', id, type)
const ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress; const ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress;
const user_agent = req.headers['user-agent']; const user_agent = req.headers['user-agent'];
const user = req.loggedInUser; const user = req.loggedInUser;
let mediaFunction = undefined let mediaFunction = undefined
if (id === undefined || type === undefined) {
res.status(422).send({ success: false, message: "'Missing parameteres: 'id' and/or 'type'"})
}
if (type === 'movie') { if (type === 'movie') {
console.log('movie')
mediaFunction = tmdbMovieInfo mediaFunction = tmdbMovieInfo
} else if (type === 'show') { } else if (type === 'show') {
console.log('show')
mediaFunction = tmdbShowInfo mediaFunction = tmdbShowInfo
} else { } else {
res.status(422).send({ success: false, error: 'Incorrect type. Allowed types: "movie" or "show"'}) res.status(422).send({ success: false, message: 'Incorrect type. Allowed types: "movie" or "show"'})
} }
mediaFunction(id) mediaFunction(id)
.catch((error) => { console.error(error); res.status(404).send({ success: false, error: 'Id not found' }) }) // .catch((error) => { console.error(error); res.status(404).send({ success: false, error: 'Id not found' }) })
.then((tmdbMedia) => request.requestFromTmdb(tmdbMedia, ip, user_agent, user)) .then(tmdbMedia => request.requestFromTmdb(tmdbMedia, ip, user_agent, user))
.then(() => res.send({success: true, message: 'Request has been submitted.'})) .then(() => res.send({success: true, message: 'Request has been submitted.'}))
.catch((error) => { .catch(error => {
res.status(501).send({ success: false, error: error.message }); res.send({ success: false, message: error.message });
}) })
} }

View File

@@ -1,7 +1,7 @@
const SearchHistory = require('src/searchHistory/searchHistory');
const configuration = require('src/config/configuration').getInstance(); const configuration = require('src/config/configuration').getInstance();
const Cache = require('src/tmdb/cache'); const Cache = require('src/tmdb/cache');
const TMDB = require('src/tmdb/tmdb'); const TMDB = require('src/tmdb/tmdb');
const SearchHistory = require('src/searchHistory/searchHistory');
const cache = new Cache(); const cache = new Cache();
const tmdb = new TMDB(cache, configuration.get('tmdb', 'apiKey')); const tmdb = new TMDB(cache, configuration.get('tmdb', 'apiKey'));
const searchHistory = new SearchHistory(); const searchHistory = new SearchHistory();
@@ -16,20 +16,25 @@ function movieSearchController(req, res) {
const user = req.loggedInUser; const user = req.loggedInUser;
const { query, page } = req.query; const { query, page } = req.query;
Promise.resolve() if (user) {
.then(() => { return searchHistory.create(user, query);
if (user) { }
return searchHistory.create(user, query);
} tmdb.movieSearch(query, page)
return null .then(movieSearchResults => res.send(movieSearchResults))
}) .catch(error => {
.then(() => tmdb.movieSearch(query, page)) const { status, message } = error;
.then((movies) => {
res.send(movies); if (status && message) {
}) res.status(status).send({ success: false, message })
.catch((error) => { } else {
res.status(500).send({ success: false, error: error.message }); // 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; module.exports = movieSearchController;

View File

@@ -1,11 +1,18 @@
const SearchHistory = require('src/searchHistory/searchHistory');
const configuration = require('src/config/configuration').getInstance(); const configuration = require('src/config/configuration').getInstance();
const Cache = require('src/tmdb/cache'); const Cache = require('src/tmdb/cache');
const TMDB = require('src/tmdb/tmdb'); const TMDB = require('src/tmdb/tmdb');
const SearchHistory = require('src/searchHistory/searchHistory');
const cache = new Cache(); const cache = new Cache();
const tmdb = new TMDB(cache, configuration.get('tmdb', 'apiKey')); const tmdb = new TMDB(cache, configuration.get('tmdb', 'apiKey'));
const searchHistory = new SearchHistory(); 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 * Controller: Search for multi (movies, shows and people by query and pagey
* @param {Request} req http request variable * @param {Request} req http request variable
@@ -16,20 +23,23 @@ function multiSearchController(req, res) {
const user = req.loggedInUser; const user = req.loggedInUser;
const { query, page } = req.query; const { query, page } = req.query;
Promise.resolve() if (user) {
.then(() => { searchHistory.create(user, query)
if (user) { }
return 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}` })
} }
return null
}) })
.then(() => tmdb.multiSearch(query, page))
.then((result) => {
res.send(result);
})
.catch((error) => {
res.status(500).send({ success: false, error: error.message });
});
} }
module.exports = multiSearchController; module.exports = multiSearchController;

View File

@@ -1,7 +1,7 @@
const SearchHistory = require('src/searchHistory/searchHistory');
const configuration = require('src/config/configuration').getInstance(); const configuration = require('src/config/configuration').getInstance();
const Cache = require('src/tmdb/cache'); const Cache = require('src/tmdb/cache');
const TMDB = require('src/tmdb/tmdb'); const TMDB = require('src/tmdb/tmdb');
const SearchHistory = require('src/searchHistory/searchHistory');
const cache = new Cache(); const cache = new Cache();
const tmdb = new TMDB(cache, configuration.get('tmdb', 'apiKey')); const tmdb = new TMDB(cache, configuration.get('tmdb', 'apiKey'));
const searchHistory = new SearchHistory(); const searchHistory = new SearchHistory();
@@ -16,20 +16,27 @@ function personSearchController(req, res) {
const user = req.loggedInUser; const user = req.loggedInUser;
const { query, page } = req.query; const { query, page } = req.query;
Promise.resolve() if (user) {
.then(() => { return searchHistory.create(user, query);
if (user) { }
return searchHistory.create(user, query);
} tmdb.personSearch(query, page)
return null .then((person) => {
}) res.send(person);
.then(() => tmdb.personSearch(query, page)) })
.then((person) => { .catch(error => {
res.send(person); const { status, message } = error;
})
.catch((error) => { if (status && message) {
res.status(500).send({ success: false, error: error.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; module.exports = personSearchController;

View File

@@ -27,8 +27,8 @@ function showSearchController(req, res) {
.then((shows) => { .then((shows) => {
res.send(shows); res.send(shows);
}) })
.catch((error) => { .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 readStraysController(req, res) {
res.send(strays); res.send(strays);
}) })
.catch((error) => { .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); res.send(stray);
}) })
.catch((error) => { .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' }); res.send({ success: true, message: 'Episode verified' });
}) })
.catch((error) => { .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; const username = user === undefined ? undefined : user.username;
searchHistory.read(username) searchHistory.read(username)
.then((searchQueries) => { .then(searchQueries => {
res.send({ success: true, searchQueries }); res.send({ success: true, searchQueries });
}) })
.catch((error) => { .catch(error => {
res.status(404).send({ success: false, error: error }); res.status(404).send({ success: false, message: error.message });
}); });
} }

View File

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

View File

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

View File

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

View File

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

View File

@@ -8,16 +8,16 @@ const Token = require('src/user/token');
// curl -i -H "Authorization:[token]" localhost:31459/api/v1/user/history // curl -i -H "Authorization:[token]" localhost:31459/api/v1/user/history
const tokenToUser = (req, res, next) => { const tokenToUser = (req, res, next) => {
const rawToken = req.headers.authorization; const rawToken = req.headers.authorization;
if (rawToken) { if (rawToken) {
try { try {
const token = Token.fromString(rawToken, secret); const token = Token.fromString(rawToken, secret);
req.loggedInUser = token.user; req.loggedInUser = token.user;
} catch (error) { } catch (error) {
req.loggedInUser = undefined; req.loggedInUser = undefined;
} }
} }
next(); next();
}; };
module.exports = tokenToUser; module.exports = tokenToUser;

View File

@@ -1 +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} [{"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

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(() => beforeEach(() =>
this.server = require('src/webserver/server')); this.server = require('src/webserver/server'));
it('should listen on port 31459', (done) => { it('should listen on port 31400', (done) => {
net.createConnection(31459, done); net.createConnection(31400, done);
}); });
afterEach(() => afterEach(() =>

View File

@@ -15,6 +15,6 @@ describe('As a user I want error when registering existing username', () => {
.post('/api/v1/user') .post('/api/v1/user')
.send({ username: 'test_user', password: 'password' }) .send({ username: 'test_user', password: 'password' })
.expect(401) .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,17 +7,17 @@ const createToken = require('test/helpers/createToken');
const infoMovieSuccess = require('test/fixtures/blade_runner_2049-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', () => { describe('As a user I want to request a movie', () => {
before(() => { before(async () => {
return resetDatabase() await resetDatabase()
.then(() => createUser('test_user', 'test@gmail.com', 'password')); await createUser('test_user', 'test@gmail.com', 'password')
}) })
before(() => createCacheEntry('mi:335984', infoMovieSuccess)); before(() => createCacheEntry('mi:335984:false', infoMovieSuccess));
it('should return 200 when item is requested', () => it('should return 200 when item is requested', () =>
request(app) request(app)
.post('/api/v2/request') .post('/api/v2/request')
.set('authorization', createToken('test_user', 'secret'))
.send({ id: 335984, type: 'movie' }) .send({ id: 335984, type: 'movie' })
.set('Authorization', createToken('test_user', 'secret'))
.expect(200) .expect(200)
); );
}); });

View File

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

View File

@@ -21,7 +21,7 @@ describe('TMDB', function test() {
it('should return the "Blade Runner 2049" year in the collection of popular movies', () => { it('should return the "Blade Runner 2049" year in the collection of popular movies', () => {
this.mockMoviedb.response = popularMovieSuccessResponse; this.mockMoviedb.response = popularMovieSuccessResponse;
const cache = new Cache(this.database); const cache = new Cache(this.database);
const tmdb = new TMDB(cache, 'bogus-api-key', this.mockMoviedb); const tmdb = new TMDB(cache, 'bogus-pi-key', this.mockMoviedb);
return tmdb.popular() return tmdb.popular()
.then(movies => .then(movies =>
assert.equal(movies[0].title, "Blade Runner 2049") assert.equal(movies[0].title, "Blade Runner 2049")

File diff suppressed because it is too large Load Diff