Compare commits
173 Commits
v1.0.0
...
research/g
| Author | SHA1 | Date | |
|---|---|---|---|
| 488da889d8 | |||
| 8da7c159b1 | |||
| ea5bc36956 | |||
| fd475265c1 | |||
| b0804f8a08 | |||
| 6b737b8ab4 | |||
| 5623344666 | |||
| f8cc19b510 | |||
| c589457a6c | |||
| b802a7b62b | |||
| 879a02b388 | |||
| bc3d4881bd | |||
| ef8d4d90b2 | |||
| d2d396bb7a | |||
| 500b75eaf6 | |||
| 9308d4ea9b | |||
| 6c2c81a1a1 | |||
| 90aa4d2485 | |||
| 0ca3f81bf8 | |||
| b9831c6b3d | |||
| 4781e9ae65 | |||
|
|
eb0881f19e | ||
| bc4d73821d | |||
| ab6144eb81 | |||
| c3d87e2200 | |||
| e391ce7ef9 | |||
| ca707078d9 | |||
|
|
53228a2662 | ||
| 2a9fa27341 | |||
| 3068281461 | |||
| 81e9fe5b15 | |||
| 5d2e375213 | |||
| 7ede37039a | |||
| 8e23ae5a27 | |||
| 04ba094a14 | |||
| 23f9911237 | |||
| 3b27af1f83 | |||
| afb7af46b8 | |||
| 6ba8ca2add | |||
| 135375cb94 | |||
| e5d5bdefd6 | |||
| 6f9ca9e067 | |||
| c42195d242 | |||
| a5aaf1bfca | |||
| af7b1f2424 | |||
| 6aba9774c6 | |||
| e19cfb5870 | |||
| 144b27f128 | |||
| 12afbf6364 | |||
| 8a5ab204e1 | |||
| 3f04d9bc56 | |||
| de50805d1e | |||
| 3a9131a022 | |||
| 77433e8505 | |||
| 3845000b3f | |||
| 071fd54825 | |||
| 537f237e83 | |||
| d3bc854e03 | |||
| 15826a00ba | |||
| 4019d63f3b | |||
| 91dcfaccb9 | |||
| 270a259cee | |||
| 162d20ae52 | |||
| 9f1badc1b1 | |||
| ac027a97d6 | |||
| 127db88ded | |||
| 4b07434615 | |||
| 5d6f2baa34 | |||
| 1a1a7328a3 | |||
| b9dec2344e | |||
| 476a34fb69 | |||
| e3ed08e8dd | |||
| 70f6497404 | |||
| 99bab3fb73 | |||
| e6796aff8b | |||
|
|
4eaa60b044 | ||
|
|
7db8f752c5 | ||
| 784aa2616a | |||
| 7cb55ce054 | |||
| 87eb6de802 | |||
| 840816c930 | |||
| 91d238de7c | |||
| 0ac17d3d0a | |||
| 87c76e3f1d | |||
| e64c4d5d01 | |||
| 22e57c03de | |||
| d80386da40 | |||
| e7c66af3f6 | |||
| 8ece7b84c4 | |||
| 4250b1bd17 | |||
| 7e46d32e30 | |||
| 5a48158f07 | |||
| 161a466ab7 | |||
| 8f5bd44e4d | |||
| 5d8869e042 | |||
| 90b8ee005e | |||
| 1b0525063f | |||
| 41d6bba743 | |||
| 8977a4b195 | |||
| 7e0da028de | |||
| 2250cf2c4b | |||
| b2bd7b6a1f | |||
| a2ad7f5628 | |||
| f85d31991f | |||
| 08dc2153ae | |||
| bc64e69b3e | |||
| a29bca7361 | |||
| d84aa5f173 | |||
| 48ebd398bc | |||
| 1b95103acd | |||
| 6a1d6687eb | |||
| e849864bc2 | |||
| ecc2a67d48 | |||
| bfe0d55f71 | |||
| 634d4513eb | |||
| 0a1276a474 | |||
| 3a34d8995e | |||
| 918e629a06 | |||
| 7dd016a56e | |||
| c10bbcf518 | |||
| 3402a52633 | |||
| 86e9188a5c | |||
| 8918b7906e | |||
| 7e028a461d | |||
| fe5f0c815e | |||
| d02e79e59e | |||
| 5b49216c9d | |||
| 657ab10034 | |||
| ed07c77b13 | |||
| 4fe85d9fae | |||
| b99b5b32ec | |||
| e8058c5e4c | |||
| 7332b7d474 | |||
| 7980f14426 | |||
| 65540fafbd | |||
| 64ede43dec | |||
| bed12cff72 | |||
| 59e7f96643 | |||
| 71e9a5a46e | |||
| fce6dc7658 | |||
| baff59181c | |||
| 0592cca16b | |||
| e4d5f5085c | |||
| 66a2a06f9b | |||
| e984feeb8d | |||
| 490d015f80 | |||
| f1cc2c4ebe | |||
| 2f4421d9e0 | |||
| 92cc094787 | |||
| f30b46c384 | |||
| d9f679603a | |||
| 64bd9d1e14 | |||
| 721826d454 | |||
| 242fe3515c | |||
| ccf40d2161 | |||
| 832b8ba539 | |||
|
|
0477e49eca | ||
| 451b67630a | |||
| 096bbdf085 | |||
| e914e4ab45 | |||
| c1461e1f41 | |||
| 91bf2c1e2a | |||
| da3df383ed | |||
| 9816b978d3 | |||
| 8e22b0f6ea | |||
| 18359f442c | |||
| 42b8b5ea0e | |||
| 996295b1fe | |||
| 1b08c8d3d1 | |||
| 9e145f7068 | |||
| 7051edb212 | |||
| 0581813ee3 | |||
| edf1de223e |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,5 +1,6 @@
|
||||
.DS_Store
|
||||
|
||||
development.json
|
||||
env
|
||||
shows.db
|
||||
|
||||
|
||||
9
.gitmodules
vendored
9
.gitmodules
vendored
@@ -1,3 +1,10 @@
|
||||
# Docs : https://git-scm.com/book/en/v2/Git-Tools-Submodules
|
||||
|
||||
[submodule "torrent_search"]
|
||||
path = torrent_search
|
||||
url = git@github.com:KevinMidboe/torrent_search.git
|
||||
url = https://github.com/KevinMidboe/torrent_search.git
|
||||
branch = master
|
||||
|
||||
[submodule "delugeClient"]
|
||||
path = delugeClient
|
||||
url = https://github.com/KevinMidboe/delugeClient.git
|
||||
|
||||
15
.travis.yml
15
.travis.yml
@@ -1,11 +1,18 @@
|
||||
language: node_js
|
||||
node_js: '8.7.0'
|
||||
node_js: '11.9.0'
|
||||
git:
|
||||
submodules: false
|
||||
submodules: true
|
||||
script:
|
||||
yarn test
|
||||
- yarn test
|
||||
- yarn coverage
|
||||
before_install:
|
||||
- cd seasoned_api
|
||||
before_script: yarn
|
||||
before_script:
|
||||
- yarn
|
||||
- curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter
|
||||
- chmod +x ./cc-test-reporter
|
||||
- ./cc-test-reporter before-build
|
||||
after-script:
|
||||
- ./cc-test-resporter after-build --exit-code $TRAVIS_TEST_RESULT
|
||||
cache: false
|
||||
os: linux
|
||||
|
||||
149
README.md
149
README.md
@@ -1,11 +1,130 @@
|
||||
# 🌶 seasonedShows
|
||||
[](https://travis-ci.org/KevinMidboe/seasonedShows)
|
||||
[](https://snyk.io/test/github/KevinMidboe/seasonedShows?targetFile=seasoned_api/package.json)
|
||||
[]()
|
||||
|
||||
Your customly *seasoned* movie and show requester, downloader and organizer. Demo page can be viewed [here](https://kevinmidboe.com/request)
|
||||
<h1 align="center">
|
||||
🌶 seasonedShows
|
||||
</h1>
|
||||
|
||||
<h4 align="center"> Season your media library with the shows and movies that you and your friends want.</h4>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://travis-ci.org/KevinMidboe/seasonedShows">
|
||||
<img src="https://travis-ci.org/KevinMidboe/seasonedShows.svg?branch=master"
|
||||
alt="Travis CI">
|
||||
</a>
|
||||
<a href="https://coveralls.io/github/KevinMidboe/seasonedShows?branch=api/v2">
|
||||
<img src="https://coveralls.io/repos/github/KevinMidboe/seasonedShows/badge.svg?branch=api/v2" alt="">
|
||||
</a>
|
||||
<a href="https://snyk.io/test/github/KevinMidboe/seasonedShows?targetFile=seasoned_api/package.json">
|
||||
<img src="https://snyk.io/test/github/KevinMidboe/seasonedShows/badge.svg?targetFile=seasoned_api/package.json" alt="">
|
||||
</a>
|
||||
<a href="https://opensource.org/licenses/MIT">
|
||||
<img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="#demo-documentation">D & D</a> •
|
||||
<a href="#about">About</a> •
|
||||
<a href="#key-features">Key features</a> •
|
||||
<a href="#installation">Installation</a> •
|
||||
<a href="#setup">Setup</a> •
|
||||
<a href="#running">Running</a> •
|
||||
<a href="#daemon">Setup daemon</a> •
|
||||
<a href="#contributing">Contributing</a>
|
||||
</p>
|
||||
|
||||
## <a name="demo-documentation"></a> Demo & Documentation
|
||||
📺 [DEMO](https://kevinmidboe.com/request)
|
||||
📝 Documentation of the api.
|
||||
💖 Checkout my [fancy vue.js page](https://github.com/KevinMidboe/seasonedRequest) for interfacing the api.
|
||||
|
||||
## <a name="about"></a> About
|
||||
This is the backend api for [seasoned request] that allows for uesrs to request movies and shows by fetching movies from themoviedb api and checks them with your plex library to identify if a movie is already present or not. This api allows to search my query, get themoviedb movie lists like popular and now playing, all while checking if the item is already in your plex library. Your friends can create users to see what movies or shows they have requested and searched for.
|
||||
|
||||
The api also uses torrent_search to search for matching torrents and returns results from any site or service available from torrent_search. As a admin of the site you can query torrent_search and return a magnet link that can be added to a autoadd folder of your favorite torrent client.
|
||||
|
||||
## <a name="key-features"></a> Key features
|
||||
### Code
|
||||
- Uses [tmdb api](https://www.themoviedb.org/documentation/api) with over 350k movies and 70k tv shows
|
||||
- Written asynchronously
|
||||
- Uses caching for external requests
|
||||
- Test coverage
|
||||
- CI and dependency integrated
|
||||
- Use either config file or env_variables
|
||||
### Functionality
|
||||
- Queries plex library to check if items exists
|
||||
- Create admin and normal user accounts
|
||||
- [torrent_search](https://github.com/KevinMidboe/torrent_search) to search for torrents
|
||||
- Fetch curated lists from tmdb
|
||||
|
||||
## <a name="installation"></a> Installation
|
||||
Before we can use seasonedShows we need to download node and a package manager. For instructions on how to install [yarn](https://yarnpkg.com/en/) or [npm](https://www.npmjs.com) package managers refer to [wiki: install package manager](https://github.com/KevinMidboe/seasonedShows/wiki/Install-package-manager). This api is written with express using node.js as the JavaScript runtime engine. To download node.js head over the the official [node.js download page](https://nodejs.org/en/download/).
|
||||
|
||||
### Install seasonedShows
|
||||
After you have downloaded a package manager and node.js javascript engine, the following will guide you through how to download, install and run seasonedShows.
|
||||
|
||||
### macOS
|
||||
- Open terminal
|
||||
- Install git. This can be done by running `xcode-select --install` in your favorite terminal.
|
||||
- Install a package manager, refer to this [wiki page] for yarn or [wiki page] for npm
|
||||
- Type: `git clone --recurse-submodules git@github.com:KevinMidboe/seasonedShows.git`
|
||||
- Type: `cd seasonedShows/`
|
||||
- Install required packages
|
||||
* yarn: `yarn install`
|
||||
* npm: `npm install`
|
||||
- Start server:
|
||||
* yarn: `yarn start`
|
||||
* npm: `npm run start`
|
||||
- seasonedShows will now be running at http://localhost:31459
|
||||
- To have seasonedShows run headless on startup, check out this wiki page to [install as a daemon].
|
||||
|
||||
### Linux
|
||||
- Open terminal
|
||||
- Install git
|
||||
* Ubuntu/Debian: `sudo apt-get install git-core`
|
||||
* Fedora: `sudo yum install git`
|
||||
- Type: `git clone --recurse-submodules git@github.com:KevinMidboe/seasonedShows.git`
|
||||
- Type: `cd seasonedShows/`
|
||||
- Install required packages
|
||||
* yarn: `yarn install`
|
||||
* npm: `npm install`
|
||||
- Start server:
|
||||
* yarn: `yarn start`
|
||||
* npm: `npm run start`
|
||||
- seasonedShows will now be running at http://localhost:31459
|
||||
- To have seasonedShows run headless on startup, check out this wiki page to [install as a daemon].
|
||||
|
||||
-- same --
|
||||
(install yarn or npm in a different way)
|
||||
After you have installed the required packages you will have a node_modules directory with all the packages required in packages.json.
|
||||
|
||||
### Requirements
|
||||
- Node 7.6 < [wiki page]
|
||||
- Plex library
|
||||
|
||||
## <a name="setup"></a> Setup and/ configuration
|
||||
There is a config file template, what the values mean and how to change them.
|
||||
Also show how to hide file from git if not want to show up as uncommitted file.
|
||||
Also set variables in environment.
|
||||
|
||||
## <a name="running"></a> Running/using
|
||||
yarn/npm start. (can also say this above)
|
||||
How to create service on linux. This means that
|
||||
|
||||
## <a name="daemon"></a> Setup a daemon
|
||||
The next step is to setup seasonedShows api to run in the background as a daemon. I have written a [wiki page](https://github.com/KevinMidboe/seasonedShows/wiki/Install-as-a-daemon) on how to create a daemon on several unix distors and macOS.
|
||||
*Please don't hesitate to add your own system if you get it running on something that is not yet lists on the formentioned wiki page.*
|
||||
|
||||
## <a name="contributing"></a> Contributing
|
||||
- Fork it!
|
||||
- Create your feature branch: git checkout -b my-new-feature
|
||||
- Commit your changes: git commit -am 'Add some feature'
|
||||
- Push to the branch: git push origin my-new-feature
|
||||
- Submit a pull request
|
||||
|
||||
|
||||
## Api documentation
|
||||
|
||||
|
||||
## About
|
||||
The goal of this project is to create a full custom stack that can to everything surround downloading, organizing and notifiyng of new media. From the top down we have a website using [tmdb](https://www.themoviedb.com) api to search for from over 350k movies and 70k tv shows. Using [hjone72](https://github.com/hjone72/PlexAuth) great PHP reverse proxy we can have a secure way of allowing users to login with their plex credentials which limits request capabilites to only users that are authenticated to use your plex library.
|
||||
seasonedShows is a intelligent organizer for your tv show episodes. It is made to automate and simplify to process of renaming and moving newly downloaded tv show episodes following Plex file naming and placement.
|
||||
|
||||
@@ -20,21 +139,3 @@ The flow of the system will first check for new folders in your tv shows directo
|
||||
Then there is a script for looking for replies on twitter by user_admin, if caanges are needed, it handles the changes specified and updates dtabbase.
|
||||
|
||||
After approval by user the files are modified and moved to folders in resptected area. If error occours, pasteee link if log is sent to user.
|
||||
|
||||
#### External
|
||||
+ Seasoned: request, discover and manage.
|
||||
+ Stray: Overview of downloaded episodes before they are organized.
|
||||
+ (+) Admin Panel: Overview of all stray episodes/movies.
|
||||
|
||||
#### Api
|
||||
+ All communication between public website to server.
|
||||
+ Plex: All querying to what is localy available in your plex library.
|
||||
+ Stray (seasoned) -> also calls services (moveStray) through api.
|
||||
+ Tmdb: Requesting information from tmdb.
|
||||
+ (+) Admin Panel: Use secure login and session tokens to handle logged in viewer.
|
||||
|
||||
#### Services
|
||||
+ Parse directories for new content.
|
||||
+ Extract and save in db information about stray item.
|
||||
+ Move a confirmed stray item.
|
||||
+ (+) Search for torrents matching new content.
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
# @Author: KevinMidboe
|
||||
# @Date: 2017-04-05 18:40:11
|
||||
# @Last Modified by: KevinMidboe
|
||||
# @Last Modified time: 2017-06-18 21:49:33
|
||||
# @Last Modified time: 2018-04-03 22:58:20
|
||||
import os.path, hashlib, time, glob, sqlite3, re, json, tweepy
|
||||
import logging
|
||||
from functools import reduce
|
||||
@@ -61,7 +61,7 @@ class strayEpisode(object):
|
||||
return hashlib.md5("b'{}'".format(self.parent).encode()).hexdigest()[:8]
|
||||
|
||||
def findSeriesName(self):
|
||||
find = re.compile("^[a-zA-Z. ]*")
|
||||
find = re.compile("^[a-zA-Z0-9. ]*")
|
||||
m = re.match(find, self.parent)
|
||||
if m:
|
||||
name, hit = process.extractOne(m.group(0), getShowNames().keys())
|
||||
|
||||
@@ -3,13 +3,15 @@
|
||||
# @Author: KevinMidboe
|
||||
# @Date: 2017-04-12 23:27:51
|
||||
# @Last Modified by: KevinMidboe
|
||||
# @Last Modified time: 2017-06-27 15:58:09
|
||||
# @Last Modified time: 2018-05-13 19:17:17
|
||||
|
||||
import sys, sqlite3, json, os.path
|
||||
import logging
|
||||
import env_variables as env
|
||||
import shutil
|
||||
|
||||
import delugeClient.deluge_cli as delugeCli
|
||||
|
||||
class episode(object):
|
||||
def __init__(self, id):
|
||||
self.id = id
|
||||
@@ -91,8 +93,18 @@ def moveStray(strayId):
|
||||
except FileNotFoundError:
|
||||
logging.warning('Cannot remove ' + ep.typeDir('parent_input') + ', file no longer exists.')
|
||||
|
||||
# Remove from deluge client
|
||||
logging.info('Removing {} for deluge'.format(ep.parent))
|
||||
deluge = delugeCli.Deluge()
|
||||
response = deluge.remove(ep.parent)
|
||||
logging.info('Deluge response after delete: {}'.format(response))
|
||||
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if (os.path.exists(env.logfile)):
|
||||
abspath = os.path.abspath(__file__)
|
||||
dname = os.path.dirname(abspath)
|
||||
if (os.path.exists(os.path.join(dname, env.logfile))):
|
||||
logging.basicConfig(filename=env.logfile, level=logging.INFO)
|
||||
else:
|
||||
print('Logfile could not be found at ' + env.logfile + '. Verifiy presence or disable logging in config.')
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"clean-webpack-plugin": "^0.1.17",
|
||||
"css-loader": "^0.28.4",
|
||||
"css-loader": "^1.0.0",
|
||||
"html-webpack-plugin": "^2.28.0",
|
||||
"path": "^0.12.7",
|
||||
"react": "^15.6.1",
|
||||
@@ -30,8 +30,8 @@
|
||||
"redux-thunk": "^2.2.0",
|
||||
"urijs": "^1.18.12",
|
||||
"webfontloader": "^1.6.28",
|
||||
"webpack": "^3.5.5",
|
||||
"webpack-dev-server": "^2.4.5",
|
||||
"webpack": "^4.0.0",
|
||||
"webpack-dev-server": "^3.1.11",
|
||||
"webpack-merge": "^4.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
"prefer-destructuring": 0,
|
||||
"camelcase": 0,
|
||||
"import/no-unresolved": 0,
|
||||
"import/no-extraneous-dependencies": 0
|
||||
"import/no-extraneous-dependencies": 0,
|
||||
"object-shorthand": 0,
|
||||
"comma-dangle": 0
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,17 +8,13 @@
|
||||
"tmdb": {
|
||||
"apiKey": ""
|
||||
},
|
||||
"plex": {
|
||||
"ip": ""
|
||||
},
|
||||
"raven": {
|
||||
"DSN": ""
|
||||
},
|
||||
"mail": {
|
||||
"host": "",
|
||||
"user": "",
|
||||
"password": "",
|
||||
"user_pi": "",
|
||||
"password_pi": ""
|
||||
},
|
||||
"authentication": {
|
||||
"secret": "secret"
|
||||
}
|
||||
"secret": "secret"
|
||||
}
|
||||
}
|
||||
20
seasoned_api/conf/test.json
Normal file
20
seasoned_api/conf/test.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"database": {
|
||||
"host": ":memory:"
|
||||
},
|
||||
"webserver": {
|
||||
"port": 31400
|
||||
},
|
||||
"tmdb": {
|
||||
"apiKey": "bogus-api-key"
|
||||
},
|
||||
"plex": {
|
||||
"ip": "0.0.0.0"
|
||||
},
|
||||
"raven": {
|
||||
"DSN": ""
|
||||
},
|
||||
"authentication": {
|
||||
"secret": "secret"
|
||||
}
|
||||
}
|
||||
@@ -1,35 +1,57 @@
|
||||
{
|
||||
"name": "seasoned-api",
|
||||
"main": "webserver/server.js",
|
||||
"scripts": {
|
||||
"start": "cross-env SEASONED_CONFIG=conf/development.json NODE_PATH=. node src/webserver/server.js",
|
||||
"test": "cross-env SEASONED_CONFIG=conf/development.json TESTING=true NODE_PATH=. mocha --recursive test/system",
|
||||
"coverage": "cross-env SEASONED_CONFIG=conf/test.json NODE_PATH=. istanbul cover -x script/autogenerate-documentation.js --include-all-sources --dir test/.coverage node_modules/mocha/bin/_mocha --recursive test/**/* -- --report lcovonly && cat test/.coverage/lcov.info | coveralls && rm -rf test/.coverage",
|
||||
"lint": "./node_modules/.bin/eslint src/"
|
||||
},
|
||||
"dependencies": {
|
||||
"bcrypt-nodejs": "^0.0.3",
|
||||
"body-parser": "~1.0.1",
|
||||
"cross-env": "^3.1.3",
|
||||
"express": "~4.11.0",
|
||||
"jsonwebtoken": "^8.0.1",
|
||||
"mongoose": "^3.6.13",
|
||||
"moviedb": "^0.2.10",
|
||||
"node-cache": "^4.1.1",
|
||||
"nodemailer": "^4.0.1",
|
||||
"python-shell": "^0.4.0",
|
||||
"raven": "^2.2.1",
|
||||
"request": "^2.81.0",
|
||||
"request-promise": "^4.2",
|
||||
"sqlite3": "3.1.13"
|
||||
},
|
||||
"devDependencies": {
|
||||
"eslint": "^4.9.0",
|
||||
"eslint-config-airbnb-base": "^12.1.0",
|
||||
"eslint-plugin-import": "^2.8.0",
|
||||
"istanbul": "^0.4.5",
|
||||
"mocha": "^3.1.0",
|
||||
"supertest": "^2.0.1",
|
||||
"supertest-as-promised": "^4.0.1"
|
||||
}
|
||||
"name": "seasoned-api",
|
||||
"description": "Packages needed to build and commands to run seasoned api node server.",
|
||||
"license": {
|
||||
"type": "MIT",
|
||||
"url": "https://www.opensource.org/licenses/mit-license.php"
|
||||
},
|
||||
"main": "webserver/server.js",
|
||||
"scripts": {
|
||||
"start": "cross-env SEASONED_CONFIG=conf/development.json PROD=true NODE_PATH=. babel-node src/webserver/server.js",
|
||||
"test": "cross-env SEASONED_CONFIG=conf/test.json NODE_PATH=. mocha --require @babel/register --recursive test/unit test/system",
|
||||
"coverage": "cross-env SEASONED_CONFIG=conf/test.json NODE_PATH=. nyc mocha --require @babel/register --recursive test && nyc report --reporter=text-lcov | coveralls",
|
||||
"lint": "./node_modules/.bin/eslint src/",
|
||||
"update": "cross-env SEASONED_CONFIG=conf/development.json NODE_PATH=. node src/plex/updateRequestsInPlex.js",
|
||||
"docs": "yarn apiDocs; yarn classDocs",
|
||||
"apiDocs": "",
|
||||
"classDocs": "./script/generate-class-docs.sh"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^0.18.0",
|
||||
"bcrypt": "^3.0.6",
|
||||
"body-parser": "~1.18.2",
|
||||
"cross-env": "~5.1.4",
|
||||
"express": "~4.16.0",
|
||||
"express-graphql": "^0.9.0",
|
||||
"express-reload": "^1.2.0",
|
||||
"graphql": "^14.5.8",
|
||||
"jsonwebtoken": "^8.2.0",
|
||||
"km-moviedb": "^0.2.12",
|
||||
"node-cache": "^4.1.1",
|
||||
"node-fetch": "^2.6.0",
|
||||
"python-shell": "^0.5.0",
|
||||
"raven": "^2.4.2",
|
||||
"request": "^2.87.0",
|
||||
"request-promise": "^4.2",
|
||||
"sqlite3": "^4.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.5.5",
|
||||
"@babel/node": "^7.5.5",
|
||||
"@babel/preset-env": "^7.5.5",
|
||||
"@babel/register": "^7.5.5",
|
||||
"@types/node": "^12.6.8",
|
||||
"coveralls": "^3.0.5",
|
||||
"documentation": "^12.0.3",
|
||||
"eslint": "^4.9.0",
|
||||
"eslint-config-airbnb-base": "^12.1.0",
|
||||
"eslint-plugin-import": "^2.8.0",
|
||||
"istanbul": "^0.4.5",
|
||||
"mocha": "^6.2.0",
|
||||
"mocha-lcov-reporter": "^1.3.0",
|
||||
"nyc": "^11.6.0",
|
||||
"supertest": "^3.0.0",
|
||||
"supertest-as-promised": "^4.0.1",
|
||||
"typescript": "^3.5.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ class Config {
|
||||
const field = new Field(this.fields[section][option]);
|
||||
|
||||
if (field.value === '') {
|
||||
const envField = process.env[[section.toUpperCase(), option.toUpperCase()].join('_')];
|
||||
const envField = process.env[['SEASONED', section.toUpperCase(), option.toUpperCase()].join('_')];
|
||||
if (envField !== undefined && envField.length !== 0) { return envField; }
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
const configuration = require('src/config/configuration').getInstance();
|
||||
const SqliteDatabase = require('src/database/sqliteDatabase');
|
||||
|
||||
const host = process.env.TESTING ? ':memory:' : configuration.get('database', 'host');
|
||||
const database = new SqliteDatabase(host);
|
||||
const database = new SqliteDatabase(configuration.get('database', 'host'));
|
||||
/**
|
||||
* This module establishes a connection to the database
|
||||
* specified in the confgiuration file. It tries to setup
|
||||
|
||||
@@ -36,6 +36,14 @@ CREATE TABLE IF NOT EXISTS requests(
|
||||
type CHAR(50) DEFAULT 'movie'
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS request(
|
||||
id int not null,
|
||||
title text not null,
|
||||
year int not null,
|
||||
type char(10) not null,
|
||||
date timestamp default (strftime('%s', 'now'))
|
||||
);
|
||||
|
||||
|
||||
CREATE TABLE IF NOT EXISTS stray_eps(
|
||||
id TEXT UNIQUE,
|
||||
@@ -56,3 +64,23 @@ CREATE TABLE IF NOT EXISTS shows(
|
||||
date_added DATE,
|
||||
date_modified DATE DEFUALT CURRENT_DATE NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS requested_torrent (
|
||||
magnet TEXT UNIQUE,
|
||||
torrent_name TEXT,
|
||||
tmdb_id TEXT
|
||||
date_added DATE DEFAULT (datetime('now','localtime'))
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS deluge_torrent (
|
||||
key TEXT UNIQUE,
|
||||
name TEXT,
|
||||
progress TEXT,
|
||||
eta NUMBER,
|
||||
save_path TEXT,
|
||||
state TEXT,
|
||||
paused BOOLEAN,
|
||||
finished BOOLEAN,
|
||||
files TEXT,
|
||||
is_folder BOOLEAN
|
||||
)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
DROP TABLE IF EXISTS user;
|
||||
DROP TABLE IF EXISTS search_history;
|
||||
DROP TABLE IF EXISTS requests;
|
||||
DROP TABLE IF EXISTS request;
|
||||
|
||||
@@ -25,7 +25,7 @@ class SqliteDatabase {
|
||||
* @param {Array} parameters in the SQL query
|
||||
* @returns {Promise}
|
||||
*/
|
||||
async run(sql, parameters) {
|
||||
run(sql, parameters) {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.connection.run(sql, parameters, (error, result) => {
|
||||
if (error)
|
||||
@@ -41,7 +41,7 @@ class SqliteDatabase {
|
||||
* @param {Array} parameters in the SQL query
|
||||
* @returns {Promise}
|
||||
*/
|
||||
async all(sql, parameters) {
|
||||
all(sql, parameters) {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.connection.all(sql, parameters, (err, rows) => {
|
||||
if (err) {
|
||||
@@ -58,7 +58,7 @@ class SqliteDatabase {
|
||||
* @param {Array} parameters in the SQL query
|
||||
* @returns {Promise}
|
||||
*/
|
||||
async get(sql, parameters) {
|
||||
get(sql, parameters) {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.connection.get(sql, parameters, (err, rows) => {
|
||||
if (err) {
|
||||
@@ -75,7 +75,7 @@ class SqliteDatabase {
|
||||
* @param {Array} parameters in the SQL query
|
||||
* @returns {Promise}
|
||||
*/
|
||||
async execute(sql) {
|
||||
execute(sql) {
|
||||
return new Promise(resolve => {
|
||||
this.connection.exec(sql, (err, database) => {
|
||||
if (err) {
|
||||
|
||||
@@ -3,6 +3,8 @@ const http = require('http');
|
||||
const { URL } = require('url');
|
||||
const PythonShell = require('python-shell');
|
||||
|
||||
const establishedDatabase = require('src/database/database');
|
||||
|
||||
function getMagnetFromURL(url) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const options = new URL(url);
|
||||
@@ -10,7 +12,7 @@ function getMagnetFromURL(url) {
|
||||
resolve(url)
|
||||
|
||||
http.get(options, (res) => {
|
||||
if (res.statusCode == 301) {
|
||||
if (res.statusCode == 301 || res.statusCode == 302) {
|
||||
resolve(res.headers.location)
|
||||
}
|
||||
});
|
||||
@@ -19,12 +21,12 @@ function getMagnetFromURL(url) {
|
||||
|
||||
async function find(searchterm, callback) {
|
||||
const options = {
|
||||
pythonPath: '/usr/bin/python3',
|
||||
// pythonPath: '/Library/Frameworks/Python.framework/Versions/3.6/bin/python3',
|
||||
args: [searchterm, '-s', 'jackett', '-f', '--print'],
|
||||
};
|
||||
pythonPath: '../torrent_search/env/bin/python3',
|
||||
scriptPath: '../torrent_search',
|
||||
args: [searchterm, '-s', 'jackett', '-f', '--print']
|
||||
}
|
||||
|
||||
PythonShell.run('../torrent_search/torrentSearch/search.py', options, callback);
|
||||
PythonShell.run('torrentSearch/search.py', options, callback);
|
||||
// PythonShell does not support return
|
||||
}
|
||||
|
||||
@@ -33,12 +35,12 @@ async function callPythonAddMagnet(url, callback) {
|
||||
getMagnetFromURL(url)
|
||||
.then((magnet) => {
|
||||
const options = {
|
||||
pythonPath: '/usr/bin/python',
|
||||
// pythonPath: '/Library/Frameworks/Python.framework/Versions/3.6/bin/python3',
|
||||
args: [magnet],
|
||||
pythonPath: '../delugeClient/env/bin/python3',
|
||||
scriptPath: '../delugeClient',
|
||||
args: ['add', magnet]
|
||||
};
|
||||
|
||||
PythonShell.run('../app/magnet.py', options, callback);
|
||||
PythonShell.run('deluge_cli.py', options, callback);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err);
|
||||
@@ -49,27 +51,32 @@ async function callPythonAddMagnet(url, callback) {
|
||||
async function SearchPiratebay(query) {
|
||||
return await new Promise((resolve, reject) => find(query, (err, results) => {
|
||||
if (err) {
|
||||
/* eslint-disable no-console */
|
||||
console.log('THERE WAS A FUCKING ERROR!\n', err);
|
||||
reject(Error('There was a error when searching for torrents'));
|
||||
}
|
||||
if (results) {
|
||||
/* eslint-disable no-console */
|
||||
console.log('result', results);
|
||||
resolve(JSON.parse(results, null, '\t'));
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
async function AddMagnet(magnet) {
|
||||
async function AddMagnet(magnet, name, tmdb_id) {
|
||||
return await new Promise((resolve, reject) => callPythonAddMagnet(magnet, (err, results) => {
|
||||
if (err) {
|
||||
/* eslint-disable no-console */
|
||||
console.log(err);
|
||||
reject(Error('Enable to add torrent', err))
|
||||
reject(Error('Enable to add torrent', err))
|
||||
}
|
||||
/* eslint-disable no-console */
|
||||
console.log('result/error:', err, results);
|
||||
|
||||
database = establishedDatabase;
|
||||
insert_query = "INSERT INTO requested_torrent(magnet,torrent_name,tmdb_id) \
|
||||
VALUES (?,?,?)";
|
||||
|
||||
let response = database.run(insert_query, [magnet, name, tmdb_id]);
|
||||
console.log('Response from requsted_torrent insert: ' + response);
|
||||
|
||||
resolve({ success: true });
|
||||
}));
|
||||
}
|
||||
|
||||
20
seasoned_api/src/plex/convertPlexToEpisode.js
Normal file
20
seasoned_api/src/plex/convertPlexToEpisode.js
Normal file
@@ -0,0 +1,20 @@
|
||||
const Episode = require('src/plex/types/episode');
|
||||
|
||||
function convertPlexToEpisode(plexEpisode) {
|
||||
const episode = new Episode(plexEpisode.title, plexEpisode.grandparentTitle, plexEpisode.year);
|
||||
episode.season = plexEpisode.parentIndex;
|
||||
episode.episode = plexEpisode.index;
|
||||
episode.summary = plexEpisode.summary;
|
||||
episode.rating = plexEpisode.rating;
|
||||
|
||||
if (plexEpisode.viewCount !== undefined) {
|
||||
episode.views = plexEpisode.viewCount;
|
||||
}
|
||||
|
||||
if (plexEpisode.originallyAvailableAt !== undefined) {
|
||||
episode.airdate = new Date(plexEpisode.originallyAvailableAt)
|
||||
}
|
||||
|
||||
return episode;
|
||||
}
|
||||
module.exports = convertPlexToEpisode;
|
||||
15
seasoned_api/src/plex/convertPlexToMovie.js
Normal file
15
seasoned_api/src/plex/convertPlexToMovie.js
Normal file
@@ -0,0 +1,15 @@
|
||||
const Movie = require('src/plex/types/movie');
|
||||
|
||||
function convertPlexToMovie(plexMovie) {
|
||||
const movie = new Movie(plexMovie.title, plexMovie.year);
|
||||
movie.rating = plexMovie.rating;
|
||||
movie.tagline = plexMovie.tagline;
|
||||
|
||||
if (plexMovie.summary !== undefined) {
|
||||
movie.summary = plexMovie.summary;
|
||||
}
|
||||
|
||||
return movie;
|
||||
}
|
||||
|
||||
module.exports = convertPlexToMovie;
|
||||
13
seasoned_api/src/plex/convertPlexToShow.js
Normal file
13
seasoned_api/src/plex/convertPlexToShow.js
Normal file
@@ -0,0 +1,13 @@
|
||||
const Show = require('src/plex/types/show');
|
||||
|
||||
function convertPlexToShow(plexShow) {
|
||||
const show = new Show(plexShow.title, plexShow.year);
|
||||
show.summary = plexShow.summary;
|
||||
show.rating = plexShow.rating;
|
||||
show.seasons = plexShow.childCount;
|
||||
show.episodes = plexShow.leafCount;
|
||||
|
||||
return show;
|
||||
}
|
||||
|
||||
module.exports = convertPlexToShow;
|
||||
90
seasoned_api/src/plex/plex.js
Normal file
90
seasoned_api/src/plex/plex.js
Normal file
@@ -0,0 +1,90 @@
|
||||
const fetch = require('node-fetch')
|
||||
const convertPlexToMovie = require('src/plex/convertPlexToMovie')
|
||||
const convertPlexToShow = require('src/plex/convertPlexToShow')
|
||||
const convertPlexToEpisode = require('src/plex/convertPlexToEpisode')
|
||||
|
||||
|
||||
const { Movie, Show, Person } = require('src/tmdb/types');
|
||||
|
||||
// const { Movie, }
|
||||
// TODO? import class definitions to compare types ?
|
||||
// what would typescript do?
|
||||
|
||||
class Plex {
|
||||
constructor(ip, port=32400) {
|
||||
this.plexIP = ip
|
||||
this.plexPort = port
|
||||
}
|
||||
|
||||
matchTmdbAndPlexMedia(plex, tmdb) {
|
||||
if (plex === undefined || tmdb === undefined)
|
||||
return false
|
||||
|
||||
const sanitize = (string) => string.toLowerCase()
|
||||
|
||||
const matchTitle = sanitize(plex.title) === sanitize(tmdb.title)
|
||||
const matchYear = plex.year === tmdb.year
|
||||
|
||||
return matchTitle && matchYear
|
||||
}
|
||||
|
||||
existsInPlex(tmdbMovie) {
|
||||
return this.search(tmdbMovie.title)
|
||||
.then(plexMovies => plexMovies.some(plex => this.matchTmdbAndPlexMedia(plex, tmdbMovie)))
|
||||
}
|
||||
|
||||
successfullResponse(response) {
|
||||
const { status, statusText } = response
|
||||
|
||||
if (status === 200) {
|
||||
return response.json()
|
||||
} else {
|
||||
throw { message: statusText, status: status }
|
||||
}
|
||||
}
|
||||
|
||||
search(query) {
|
||||
const url = `http://${this.plexIP}:${this.plexPort}/hubs/search?query=${query}`
|
||||
const options = {
|
||||
timeout: 2000,
|
||||
headers: { 'Accept': 'application/json' }
|
||||
}
|
||||
|
||||
return fetch(url, options)
|
||||
.then(this.successfullResponse)
|
||||
.then(this.mapResults)
|
||||
.catch(error => {
|
||||
if (error.type === 'request-timeout') {
|
||||
throw { message: 'Plex did not respond', status: 408, success: false }
|
||||
}
|
||||
|
||||
throw error
|
||||
})
|
||||
}
|
||||
|
||||
mapResults(response) {
|
||||
if (response === undefined || response.MediaContainer === undefined) {
|
||||
console.log('response was not valid to map', response)
|
||||
return []
|
||||
}
|
||||
|
||||
return response.MediaContainer.Hub
|
||||
.filter(category => category.size > 0)
|
||||
.map(category => {
|
||||
if (category.type === 'movie') {
|
||||
return category.Metadata.map(movie => {
|
||||
const ovie = Movie.convertFromPlexResponse(movie)
|
||||
return ovie.createJsonResponse()
|
||||
})
|
||||
} else if (category.type === 'show') {
|
||||
return category.Metadata.map(convertPlexToShow)
|
||||
} else if (category.type === 'episode') {
|
||||
return category.Metadata.map(convertPlexToEpisode)
|
||||
}
|
||||
})
|
||||
.filter(result => result !== undefined)
|
||||
.flat()
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Plex;
|
||||
@@ -3,6 +3,10 @@ const convertPlexToStream = require('src/plex/convertPlexToStream');
|
||||
const rp = require('request-promise');
|
||||
|
||||
class PlexRepository {
|
||||
constructor(plexIP) {
|
||||
this.plexIP = plexIP;
|
||||
}
|
||||
|
||||
inPlex(tmdbResult) {
|
||||
return Promise.resolve()
|
||||
.then(() => this.search(tmdbResult.title))
|
||||
@@ -15,8 +19,10 @@ class PlexRepository {
|
||||
}
|
||||
|
||||
search(query) {
|
||||
const queryUri = encodeURIComponent(query)
|
||||
const uri = encodeURI(`http://${this.plexIP}:32400/search?query=${queryUri}`)
|
||||
const options = {
|
||||
uri: `http://10.0.0.44:32400/search?query=${query}`,
|
||||
uri: uri,
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
},
|
||||
@@ -25,6 +31,7 @@ class PlexRepository {
|
||||
|
||||
return rp(options)
|
||||
.catch((error) => {
|
||||
console.log(error)
|
||||
throw new Error('Unable to search plex.')
|
||||
})
|
||||
.then(result => this.mapResults(result))
|
||||
@@ -38,8 +45,10 @@ class PlexRepository {
|
||||
tmdb.matchedInPlex = false
|
||||
}
|
||||
else {
|
||||
// console.log('plex and tmdb:', plexResult, '\n', tmdb)
|
||||
plexResult.results.map((plexItem) => {
|
||||
if (tmdb.title === plexItem.title && tmdb.year === plexItem.year) { tmdb.matchedInPlex = true; }
|
||||
if (tmdb.title === plexItem.title && tmdb.year === plexItem.year)
|
||||
tmdb.matchedInPlex = true;
|
||||
return tmdb;
|
||||
});
|
||||
}
|
||||
@@ -62,7 +71,7 @@ class PlexRepository {
|
||||
|
||||
nowPlaying() {
|
||||
const options = {
|
||||
uri: 'http://10.0.0.44:32400/status/sessions',
|
||||
uri: `http://${this.plexIP}:32400/status/sessions`,
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
},
|
||||
@@ -72,7 +81,7 @@ class PlexRepository {
|
||||
return rp(options)
|
||||
.then((result) => {
|
||||
if (result.MediaContainer.size > 0) {
|
||||
const playing = result.MediaContainer.Video.map(convertPlexToStream);
|
||||
const playing = result.MediaContainer.Metadata.map(convertPlexToStream);
|
||||
return { size: Object.keys(playing).length, video: playing };
|
||||
}
|
||||
return { size: 0, video: [] };
|
||||
|
||||
@@ -4,25 +4,21 @@ const configuration = require('src/config/configuration').getInstance();
|
||||
const TMDB = require('src/tmdb/tmdb');
|
||||
const establishedDatabase = require('src/database/database');
|
||||
|
||||
const plexRepository = new PlexRepository();
|
||||
const plexRepository = new PlexRepository(configuration.get('plex', 'ip'));
|
||||
const cache = new Cache();
|
||||
const tmdb = new TMDB(cache, configuration.get('tmdb', 'apiKey'));
|
||||
|
||||
const MailTemplate = require('src/plex/mailTemplate');
|
||||
const nodemailer = require('nodemailer');
|
||||
|
||||
|
||||
class RequestRepository {
|
||||
constructor(cache, database) {
|
||||
constructor(database) {
|
||||
this.database = database || establishedDatabase;
|
||||
this.queries = {
|
||||
insertRequest: `INSERT INTO requests(id,title,year,poster_path,background_path,requested_by,ip,user_agent,type)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
fetchRequestedItems: 'SELECT * FROM requests ORDER BY date DESC',
|
||||
fetchRequestedItemsByStatus: 'SELECT * FROM requests WHERE status IS ? AND type LIKE ?',
|
||||
fetchRequestedItems: 'SELECT * FROM requests ORDER BY date DESC LIMIT 25 OFFSET ?*25-25',
|
||||
fetchRequestedItemsByStatus: 'SELECT * FROM requests WHERE status IS ? AND type LIKE ? ORDER BY date DESC LIMIT 25 OFFSET ?*25-25',
|
||||
updateRequestedById: 'UPDATE requests SET status = ? WHERE id is ? AND type is ?',
|
||||
checkIfIdRequested: 'SELECT * FROM requests WHERE id IS ? AND type IS ?',
|
||||
userRequests: 'SELECT * FROM requests WHERE requested_by IS ?'
|
||||
userRequests: 'SELECT * FROM requests WHERE requested_by IS ? ORDER BY date DESC',
|
||||
};
|
||||
this.cacheTags = {
|
||||
search: 'se',
|
||||
@@ -51,10 +47,7 @@ class RequestRepository {
|
||||
.then(() => this.database.get(this.queries.checkIfIdRequested, [tmdbMovie.id, tmdbMovie.type]))
|
||||
.then((result, error) => {
|
||||
if (error) { throw new Error(error); }
|
||||
let already_requested = false;
|
||||
if (result) { already_requested = true; }
|
||||
|
||||
tmdbMovie.requested = already_requested;
|
||||
tmdbMovie.requested = result ? true : false;
|
||||
return tmdbMovie;
|
||||
});
|
||||
}
|
||||
@@ -68,30 +61,36 @@ class RequestRepository {
|
||||
return Promise.resolve()
|
||||
.then(() => tmdb.lookup(identifier, type))
|
||||
.then((movie) => {
|
||||
const username = user == undefined ? undefined : user.username;
|
||||
const username = user === undefined ? undefined : user.username;
|
||||
// Add request to database
|
||||
return this.database.run(this.queries.insertRequest, [movie.id, movie.title, movie.year, movie.poster_path, movie.background_path, username, ip, user_agent, movie.type]);
|
||||
});
|
||||
}
|
||||
|
||||
fetchRequested(status, type = '%') {
|
||||
fetchRequested(status, page = '1', type = '%') {
|
||||
return Promise.resolve()
|
||||
.then(() => {
|
||||
if (status === 'requested' || status === 'downloading' || status === 'downloaded')
|
||||
return this.database.all(this.queries.fetchRequestedItemsByStatus, [status, type]);
|
||||
return this.database.all(this.queries.fetchRequestedItemsByStatus, [status, type, page]);
|
||||
else
|
||||
return this.database.all(this.queries.fetchRequestedItems);
|
||||
return this.database.all(this.queries.fetchRequestedItems, page);
|
||||
})
|
||||
}
|
||||
|
||||
userRequests(user) {
|
||||
return Promise.resolve()
|
||||
return Promise.resolve()
|
||||
.then(() => this.database.all(this.queries.userRequests, user.username))
|
||||
.catch((error) => {
|
||||
if (String(error).includes('no such column')) { throw new Error('Username not found'); }
|
||||
else { throw new Error('Unable to fetch your requests')}
|
||||
if (String(error).includes('no such column')) {
|
||||
throw new Error('Username not found');
|
||||
}
|
||||
throw new Error('Unable to fetch your requests');
|
||||
})
|
||||
.then((result) => { return result })
|
||||
.then((result) => {
|
||||
// TODO do a correct mapping before sending, not just a dump of the database
|
||||
result.map(item => item.poster = item.poster_path)
|
||||
return result
|
||||
});
|
||||
}
|
||||
|
||||
updateRequestedById(id, type, status) {
|
||||
|
||||
16
seasoned_api/src/plex/types/episode.js
Normal file
16
seasoned_api/src/plex/types/episode.js
Normal file
@@ -0,0 +1,16 @@
|
||||
class Episode {
|
||||
constructor(title, show, year) {
|
||||
this.title = title;
|
||||
this.show = show;
|
||||
this.year = year;
|
||||
this.season = null;
|
||||
this.episode = null;
|
||||
this.summary = null;
|
||||
this.rating = null;
|
||||
this.views = null;
|
||||
this.aired = null;
|
||||
this.type = 'episode';
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Episode;
|
||||
12
seasoned_api/src/plex/types/movie.js
Normal file
12
seasoned_api/src/plex/types/movie.js
Normal file
@@ -0,0 +1,12 @@
|
||||
class Movie {
|
||||
constructor(title, year) {
|
||||
this.title = title;
|
||||
this.year = year;
|
||||
this.summary = null;
|
||||
this.rating = null;
|
||||
this.tagline = null;
|
||||
this.type = 'movie';
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Movie;
|
||||
12
seasoned_api/src/plex/types/show.js
Normal file
12
seasoned_api/src/plex/types/show.js
Normal file
@@ -0,0 +1,12 @@
|
||||
class Show {
|
||||
constructor(title, year) {
|
||||
this.title = title;
|
||||
this.year = year;
|
||||
this.summary = null;
|
||||
this.rating = null;
|
||||
this.seasons = null;
|
||||
this.episodes = null;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Show;
|
||||
39
seasoned_api/src/plex/updateRequestsInPlex.js
Normal file
39
seasoned_api/src/plex/updateRequestsInPlex.js
Normal file
@@ -0,0 +1,39 @@
|
||||
const Plex = require('src/plex/plex')
|
||||
const configuration = require('src/config/configuration').getInstance();
|
||||
const plex = new Plex(configuration.get('plex', 'ip'))
|
||||
const establishedDatabase = require('src/database/database');
|
||||
|
||||
class UpdateRequestsInPlex {
|
||||
constructor() {
|
||||
this.database = establishedDatabase;
|
||||
this.queries = {
|
||||
getMovies: `SELECT * FROM requests WHERE status = 'requested' OR status = 'downloading'`,
|
||||
// getMovies: "select * from requests where status is 'reset'",
|
||||
saveNewStatus: `UPDATE requests SET status = ? WHERE id IS ? and type IS ?`,
|
||||
}
|
||||
}
|
||||
getByStatus() {
|
||||
return this.database.all(this.queries.getMovies);
|
||||
}
|
||||
scrub() {
|
||||
return this.getByStatus()
|
||||
.then((requests) => Promise.all(requests.map(movie => plex.existsInPlex(movie))))
|
||||
}
|
||||
|
||||
commitNewStatus(status, id, type, title) {
|
||||
console.log(type, title, 'updated to:', status)
|
||||
this.database.run(this.queries.saveNewStatus, [status, id, type])
|
||||
}
|
||||
|
||||
|
||||
updateStatus(status) {
|
||||
this.getByStatus()
|
||||
.then(requests => Promise.all(requests.map(request => plex.existsInPlex(request))))
|
||||
.then(matchedRequests => matchedRequests.filter(request => request.existsInPlex))
|
||||
.then(newMatches => newMatches.map(match => this.commitNewStatus(status, match.id, match.type, match.title)))
|
||||
}
|
||||
}
|
||||
var requestsUpdater = new UpdateRequestsInPlex();
|
||||
requestsUpdater.updateStatus('downloaded')
|
||||
|
||||
module.exports = UpdateRequestsInPlex
|
||||
169
seasoned_api/src/request/request.js
Normal file
169
seasoned_api/src/request/request.js
Normal file
@@ -0,0 +1,169 @@
|
||||
const assert = require('assert')
|
||||
const configuration = require('src/config/configuration').getInstance();
|
||||
const Cache = require('src/tmdb/cache');
|
||||
const TMDB = require('src/tmdb/tmdb');
|
||||
const cache = new Cache();
|
||||
const tmdb = new TMDB(cache, configuration.get('tmdb', 'apiKey'));
|
||||
const establishedDatabase = require('src/database/database');
|
||||
const utils = require('./utils');
|
||||
|
||||
class RequestRepository {
|
||||
constructor(database) {
|
||||
this.database = database || establishedDatabase;
|
||||
this.queries = {
|
||||
add: 'insert into requests (id,title,year,poster_path,background_path,requested_by,ip,user_agent,type) values(?,?,?,?,?,?,?,?,?)',
|
||||
fetchAll: 'select * from requests where status != "downloaded" order by date desc LIMIT 25 OFFSET ?*25-25',
|
||||
fetchAllFilteredStatus: 'select * from requests where status = ? order by date desc LIMIT 25 offset ?*25-25',
|
||||
totalRequests: 'select count(*) as totalRequests from requests where status != "downloaded"',
|
||||
totalRequestsFilteredStatus: 'select count(*) as totalRequests from requests where status = ?',
|
||||
fetchAllSort: `select id, type from request order by ? ?`,
|
||||
fetchAllFilter: `select id, type from request where ? is "?"`,
|
||||
fetchAllQuery: `select id, type from request where title like "%?%" or year like "%?%"`,
|
||||
fetchAllFilterAndSort: `select id, type from request where ? is "?" order by ? ?`,
|
||||
downloaded: '(select status from requests where id is request.id and type is request.type limit 1)',
|
||||
// deluge: '(select status from deluge_torrent where id is request.id and type is request.type limit 1)',
|
||||
// fetchAllFilterStatus: 'select * from request where '
|
||||
readWithoutUserData: 'select id, title, year, type, status, date from requests where id is ? and type is ?',
|
||||
read: 'select id, title, year, type, status, requested_by, ip, date, user_agent from requests where id is ? and type is ?'
|
||||
};
|
||||
}
|
||||
|
||||
sortAndFilterToDbQuery(by, direction, filter, query) {
|
||||
let dbQuery = undefined;
|
||||
|
||||
if (query !== undefined) {
|
||||
const dbParams = [query, query];
|
||||
const dbquery = this.queries.fetchAllQuery
|
||||
|
||||
dbQuery = dbquery.split('').map((char) => char === '?' ? dbParams.shift() : char).join('')
|
||||
}
|
||||
else if (by !== undefined && filter !== undefined) {
|
||||
const paramToColumnAndValue = {
|
||||
movie: ['type', 'movie'],
|
||||
show: ['type', 'show']
|
||||
}
|
||||
const dbParams = paramToColumnAndValue[filter].concat([by, direction]);
|
||||
const query = this.queries.fetchAllFilterAndSort;
|
||||
|
||||
dbQuery = query.split('').map((char) => char === '?' ? dbParams.shift() : char).join('')
|
||||
}
|
||||
else if (by !== undefined) {
|
||||
const dbParams = [by, direction];
|
||||
const query = this.queries.fetchAllSort;
|
||||
|
||||
dbQuery = query.split('').map((char) => char === '?' ? dbParams.shift() : char).join('')
|
||||
}
|
||||
else if (filter !== undefined) {
|
||||
const paramToColumnAndValue = {
|
||||
movie: ['type', 'movie'],
|
||||
show: ['type', 'show'],
|
||||
downloaded: [this.queries.downloaded, 'downloaded']
|
||||
// downloading: [this.database.delugeStatus, 'downloading']
|
||||
}
|
||||
const dbParams = paramToColumnAndValue[filter]
|
||||
const query = this.queries.fetchAllFilter;
|
||||
|
||||
dbQuery = query.split('').map((char) => char === '?' ? dbParams.shift() : char).join('')
|
||||
}
|
||||
else {
|
||||
dbQuery = this.queries.fetchAll;
|
||||
}
|
||||
|
||||
return dbQuery
|
||||
}
|
||||
|
||||
mapToTmdbByType(rows) {
|
||||
return rows.map((row) => {
|
||||
if (row.type === 'movie')
|
||||
return tmdb.movieInfo(row.id)
|
||||
else if (row.type === 'show')
|
||||
return tmdb.showInfo(row.id)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Add tmdb movie|show to requests
|
||||
* @param {tmdb} tmdb class of movie|show to add
|
||||
* @returns {Promise}
|
||||
*/
|
||||
requestFromTmdb(tmdb, ip, user_agent, user) {
|
||||
return Promise.resolve()
|
||||
.then(() => this.database.get(this.queries.read, [tmdb.id, tmdb.type]))
|
||||
.then(row => assert.equal(row, undefined, 'Id has already been requested'))
|
||||
.then(() => this.database.run(this.queries.add, [tmdb.id, tmdb.title, tmdb.year, tmdb.poster, tmdb.backdrop, user, ip, user_agent, tmdb.type]))
|
||||
.catch((error) => {
|
||||
if (error.name === 'AssertionError' || error.message.endsWith('been requested')) {
|
||||
throw new Error('This id is already requested', error.message);
|
||||
}
|
||||
console.log('Error @ request.addTmdb:', error);
|
||||
throw new Error('Could not add request');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get request item by id
|
||||
* @param {String} id
|
||||
* @param {String} type
|
||||
* @returns {Promise}
|
||||
*/
|
||||
getRequestByIdAndType(id, type) {
|
||||
return this.database.get(this.queries.readWithoutUserData, [id, type])
|
||||
.then(row => {
|
||||
assert(row, 'Could not find request item with that id and type')
|
||||
return {
|
||||
id: row.id,
|
||||
title: row.title,
|
||||
year: row.year,
|
||||
type: row.type,
|
||||
status: row.status,
|
||||
requested_date: new Date(row.date)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch all requests with optional sort and filter params
|
||||
* @param {String} what we are sorting by
|
||||
* @param {String} direction that can be either 'asc' or 'desc', default 'asc'.
|
||||
* @param {String} params to filter by
|
||||
* @param {String} query param to filter result on. Filters on title and year
|
||||
* @returns {Promise}
|
||||
*/
|
||||
fetchAll(page=1, sort_by=undefined, sort_direction='asc', filter=undefined, query=undefined) {
|
||||
// TODO implemented sort and filter
|
||||
page = parseInt(page)
|
||||
let fetchQuery = this.queries.fetchAll
|
||||
let fetchTotalResults = this.queries.totalRequests
|
||||
let fetchParams = [page]
|
||||
|
||||
if (filter && (filter === 'downloading' || filter === 'downloaded' || filter === 'requested')) {
|
||||
console.log('tes')
|
||||
fetchQuery = this.queries.fetchAllFilteredStatus
|
||||
fetchTotalResults = this.queries.totalRequestsFilteredStatus
|
||||
fetchParams = [filter, page]
|
||||
} else {
|
||||
filter = undefined
|
||||
}
|
||||
|
||||
return Promise.resolve()
|
||||
.then((dbQuery) => this.database.all(fetchQuery, fetchParams))
|
||||
.then(async (rows) => {
|
||||
const sqliteResponse = await this.database.get(fetchTotalResults, filter ? filter : undefined)
|
||||
const totalRequests = sqliteResponse['totalRequests']
|
||||
const totalPages = Math.ceil(totalRequests / 26)
|
||||
|
||||
return [ rows.map(item => {
|
||||
item.poster = item.poster_path; delete item.poster_path;
|
||||
item.backdrop = item.background_path; delete item.background_path;
|
||||
return item
|
||||
}), totalPages, totalRequests ]
|
||||
return Promise.all(this.mapToTmdbByType(rows))
|
||||
})
|
||||
.then(([result, totalPages, totalRequests]) => Promise.resolve({
|
||||
results: result, total_results: totalRequests, page: page, total_pages: totalPages
|
||||
}))
|
||||
.catch(error => { console.log(error);throw error })
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = RequestRepository;
|
||||
34
seasoned_api/src/request/utils.js
Normal file
34
seasoned_api/src/request/utils.js
Normal file
@@ -0,0 +1,34 @@
|
||||
// TODO : test title and date are valid matches to columns in the database
|
||||
const validSortParams = ['title', 'date']
|
||||
const validSortDirs = ['asc', 'desc']
|
||||
const validFilterParams = ['movie', 'show', 'seeding', 'downloading', 'paused', 'finished', 'downloaded']
|
||||
|
||||
function validSort(by, direction) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (by === undefined) {
|
||||
resolve()
|
||||
}
|
||||
|
||||
if (validSortParams.includes(by) && validSortDirs.includes(direction)) {
|
||||
resolve()
|
||||
} else {
|
||||
reject(new Error(`invalid sort parameter, must be of: ${validSortParams} with optional sort directions: ${validSortDirs} appended with ':'`))
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function validFilter(filter_param) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (filter_param === undefined) {
|
||||
resolve()
|
||||
}
|
||||
|
||||
if (filter_param && validFilterParams.includes(filter_param)) {
|
||||
resolve()
|
||||
} else {
|
||||
reject(new Error(`filter parameteres must be of type: ${validFilterParams}`))
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = { validSort, validFilter }
|
||||
@@ -43,7 +43,7 @@ class StrayRepository {
|
||||
assert.notEqual(row, undefined, `Stray '${strayId}' already verified.`);
|
||||
|
||||
const options = {
|
||||
pythonPath: '/usr/bin/python3',
|
||||
pythonPath: '../app/env/bin/python3',
|
||||
args: [strayId],
|
||||
};
|
||||
|
||||
|
||||
3
seasoned_api/src/tmdb/.babelrc
Normal file
3
seasoned_api/src/tmdb/.babelrc
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"presets": ["@babel/preset-env"]
|
||||
}
|
||||
@@ -18,12 +18,12 @@ class Cache {
|
||||
* @returns {Object}
|
||||
*/
|
||||
get(key) {
|
||||
return Promise.resolve()
|
||||
.then(() => this.database.get(this.queries.read, [key]))
|
||||
.then((row) => {
|
||||
assert(row, 'Could not find cache enrty with that key.');
|
||||
return JSON.parse(row.value);
|
||||
});
|
||||
return Promise.resolve()
|
||||
.then(() => this.database.get(this.queries.read, [key]))
|
||||
.then(row => {
|
||||
assert(row, 'Could not find cache entry with that key.');
|
||||
return JSON.parse(row.value);
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
|
||||
const TMDB = require('src/media_classes/tmdb');
|
||||
|
||||
function translateYear(tmdbReleaseDate) {
|
||||
return new Date(tmdbReleaseDate).getFullYear();
|
||||
}
|
||||
|
||||
function translateGenre(tmdbGenres) {
|
||||
return tmdbGenres.map(genre => genre.name);
|
||||
}
|
||||
|
||||
function convertType(tmdbType) {
|
||||
if (tmdbType === 'tv') return 'show';
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function convertTmdbToSeasoned(tmdb, manualType = undefined) {
|
||||
const title = tmdb.title || tmdb.name;
|
||||
const year = translateYear(tmdb.release_date || tmdb.first_air_date);
|
||||
const type = manualType || convertType(tmdb.media_type) || 'movie';
|
||||
|
||||
const id = tmdb.id;
|
||||
const summary = tmdb.overview;
|
||||
const poster_path = tmdb.poster_path;
|
||||
const background_path = tmdb.backdrop_path;
|
||||
const popularity = tmdb.popularity;
|
||||
const score = tmdb.vote_average;
|
||||
// const genres = translateGenre(tmdb.genres);
|
||||
const release_status = tmdb.status;
|
||||
const tagline = tmdb.tagline;
|
||||
|
||||
const seasons = tmdb.number_of_seasons;
|
||||
const episodes = tmdb.episodes;
|
||||
|
||||
const seasoned = new TMDB(
|
||||
title, year, type, id, summary, poster_path, background_path,
|
||||
popularity, score, release_status, tagline, seasons, episodes
|
||||
);
|
||||
|
||||
// seasoned.print()
|
||||
return seasoned;
|
||||
}
|
||||
|
||||
module.exports = convertTmdbToSeasoned;
|
||||
@@ -1,126 +1,255 @@
|
||||
const moviedb = require('moviedb');
|
||||
const convertTmdbToSeasoned = require('src/tmdb/convertTmdbToSeasoned');
|
||||
const moviedb = require('km-moviedb');
|
||||
|
||||
const TMDB_METHODS = {
|
||||
upcoming: { movie: 'miscUpcomingMovies' },
|
||||
discover: { movie: 'discoverMovie', show: 'discoverTv' },
|
||||
popular: { movie: 'miscPopularMovies', show: 'miscPopularTvs' },
|
||||
nowplaying: { movie: 'miscNowPlayingMovies', show: 'tvOnTheAir' },
|
||||
similar: { movie: 'movieSimilar', show: 'tvSimilar' },
|
||||
search: { movie: 'searchMovie', show: 'searchTv', multi: 'searchMulti' },
|
||||
info: { movie: 'movieInfo', show: 'tvInfo' },
|
||||
};
|
||||
const { Movie, Show, Person, Credits, ReleaseDates } = require('src/tmdb/types');
|
||||
// const { tmdbInfo } = require('src/tmdb/types')
|
||||
|
||||
class TMDB {
|
||||
constructor(cache, apiKey, tmdbLibrary) {
|
||||
this.cache = cache;
|
||||
this.tmdbLibrary = tmdbLibrary || moviedb(apiKey);
|
||||
this.cacheTags = {
|
||||
search: 'se',
|
||||
info: 'i',
|
||||
upcoming: 'u',
|
||||
discover: 'd',
|
||||
popular: 'p',
|
||||
nowplaying: 'n',
|
||||
similar: 'si',
|
||||
};
|
||||
}
|
||||
constructor(cache, apiKey, tmdbLibrary) {
|
||||
this.cache = cache;
|
||||
this.tmdbLibrary = tmdbLibrary || moviedb(apiKey);
|
||||
this.cacheTags = {
|
||||
multiSearch: 'mus',
|
||||
movieSearch: 'mos',
|
||||
showSearch: 'ss',
|
||||
personSearch: 'ps',
|
||||
movieInfo: 'mi',
|
||||
movieCredits: 'mc',
|
||||
movieReleaseDates: 'mrd',
|
||||
showInfo: 'si',
|
||||
showCredits: 'sc',
|
||||
personInfo: 'pi',
|
||||
miscNowPlayingMovies: 'npm',
|
||||
miscPopularMovies: 'pm',
|
||||
miscTopRatedMovies: 'tpm',
|
||||
miscUpcomingMovies: 'um',
|
||||
tvOnTheAir: 'toa',
|
||||
miscPopularTvs: 'pt',
|
||||
miscTopRatedTvs: 'trt',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* Retrieve a specific movie by id from TMDB.
|
||||
* @param {Number} identifier of the movie you want to retrieve
|
||||
* @param {Boolean} add credits (cast & crew) for movie
|
||||
* @param {Boolean} add release dates for every country
|
||||
* @returns {Promise} succeeds if movie was found
|
||||
*/
|
||||
lookup(identifier, type = 'movie') {
|
||||
const query = { id: identifier };
|
||||
const cacheKey = `${this.cacheTags.info}:${type}:${identifier}`;
|
||||
return Promise.resolve()
|
||||
.then(() => this.cache.get(cacheKey))
|
||||
.catch(() => this.tmdb(this.tmdbMethod('info', type), query))
|
||||
.catch(() => { throw new Error('Could not find a movie with that id.'); })
|
||||
.then(response => this.cache.set(cacheKey, response))
|
||||
.then((response) => {
|
||||
try {
|
||||
return convertTmdbToSeasoned(response, type);
|
||||
} catch (parseError) {
|
||||
console.error(parseError);
|
||||
throw new Error('Could not parse movie.');
|
||||
}
|
||||
});
|
||||
}
|
||||
movieInfo(identifier) {
|
||||
const query = { id: identifier };
|
||||
const cacheKey = `${this.cacheTags.movieInfo}:${identifier}`;
|
||||
|
||||
/**
|
||||
* Retrive list of of items from TMDB matching the query and/or type given.
|
||||
* @param {queryText, page, type} the page number to specify in the request for discover,
|
||||
return this.cache.get(cacheKey)
|
||||
.catch(() => this.tmdb('movieInfo', query))
|
||||
.catch(tmdbError => tmdbErrorResponse(tmdbError, 'movie info'))
|
||||
.then(movie => this.cache.set(cacheKey, movie, 1))
|
||||
.then(movie => Movie.convertFromTmdbResponse(movie))
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve credits for a movie
|
||||
* @param {Number} identifier of the movie to get credits for
|
||||
* @returns {Promise} movie cast object
|
||||
*/
|
||||
movieCredits(identifier) {
|
||||
const query = { id: identifier }
|
||||
const cacheKey = `${this.cacheTags.movieCredits}:${identifier}`
|
||||
|
||||
return this.cache.get(cacheKey)
|
||||
.catch(() => this.tmdb('movieCredits', query))
|
||||
.catch(tmdbError => tmdbErrorResponse(tmdbError, 'movie credits'))
|
||||
.then(credits => this.cache.set(cacheKey, credits, 1))
|
||||
.then(credits => Credits.convertFromTmdbResponse(credits))
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve release dates for a movie
|
||||
* @param {Number} identifier of the movie to get release dates for
|
||||
* @returns {Promise} movie release dates object
|
||||
*/
|
||||
movieReleaseDates(identifier) {
|
||||
const query = { id: identifier }
|
||||
const cacheKey = `${this.cacheTags.movieReleaseDates}:${identifier}`
|
||||
|
||||
return this.cache.get(cacheKey)
|
||||
.catch(() => this.tmdb('movieReleaseDates', query))
|
||||
.catch(tmdbError => tmdbErrorResponse(tmdbError, 'movie release dates'))
|
||||
.then(releaseDates => this.cache.set(cacheKey, releaseDates, 1))
|
||||
.then(releaseDates => ReleaseDates.convertFromTmdbResponse(releaseDates))
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a specific show by id from TMDB.
|
||||
* @param {Number} identifier of the show you want to retrieve
|
||||
* @param {String} type filter results by type (default show).
|
||||
* @returns {Promise} succeeds if show was found
|
||||
*/
|
||||
showInfo(identifier) {
|
||||
const query = { id: identifier };
|
||||
const cacheKey = `${this.cacheTags.showInfo}:${identifier}`;
|
||||
|
||||
return this.cache.get(cacheKey)
|
||||
.catch(() => this.tmdb('tvInfo', query))
|
||||
.catch(tmdbError => tmdbErrorResponse(tmdbError, 'tv info'))
|
||||
.then(show => this.cache.set(cacheKey, show, 1))
|
||||
.then(show => Show.convertFromTmdbResponse(show))
|
||||
}
|
||||
|
||||
showCredits(identifier) {
|
||||
const query = { id: identifier }
|
||||
const cacheKey = `${this.cacheTags.showCredits}:${identifier}`
|
||||
|
||||
return this.cache.get(cacheKey)
|
||||
.catch(() => this.tmdb('tvCredits', query))
|
||||
.catch(tmdbError => tmdbErrorResponse(tmdbError, 'show credits'))
|
||||
.then(credits => this.cache.set(cacheKey, credits, 1))
|
||||
.then(credits => Credits.convertFromTmdbResponse(credits))
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a specific person id from TMDB.
|
||||
* @param {Number} identifier of the person you want to retrieve
|
||||
* @param {String} type filter results by type (default person).
|
||||
* @returns {Promise} succeeds if person was found
|
||||
*/
|
||||
personInfo(identifier) {
|
||||
const query = { id: identifier };
|
||||
const cacheKey = `${this.cacheTags.personInfo}:${identifier}`;
|
||||
|
||||
return this.cache.get(cacheKey)
|
||||
.catch(() => this.tmdb('personInfo', query))
|
||||
.catch(tmdbError => tmdbErrorResponse(tmdbError, 'person info'))
|
||||
.then(person => this.cache.set(cacheKey, person, 1))
|
||||
.then(person => Person.convertFromTmdbResponse(person))
|
||||
}
|
||||
|
||||
multiSearch(search_query, page=1) {
|
||||
const query = { query: search_query, page: page };
|
||||
const cacheKey = `${this.cacheTags.multiSearch}:${page}:${search_query}`;
|
||||
return this.cache.get(cacheKey)
|
||||
.catch(() => this.tmdb('searchMulti', query))
|
||||
.catch(tmdbError => tmdbErrorResponse(tmdbError, 'search results'))
|
||||
.then(response => this.cache.set(cacheKey, response, 1))
|
||||
.then(response => this.mapResults(response));
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrive movie search results from TMDB.
|
||||
* @param {String} text query you want to search for
|
||||
* @param {Number} page representing pagination of results
|
||||
* @returns {Promise} dict with query results, current page and total_pages
|
||||
*/
|
||||
search(text, page = 1, type = 'multi') {
|
||||
const query = { query: text, page };
|
||||
const cacheKey = `${this.cacheTags.search}:${page}:${type}:${text}`;
|
||||
return Promise.resolve()
|
||||
.then(() => this.cache.get(cacheKey))
|
||||
.catch(() => this.tmdb(this.tmdbMethod('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))
|
||||
.catch((error) => { throw new Error(error); })
|
||||
.then(([mappedResults, pagenumber, totalpages, total_results]) => ({
|
||||
results: mappedResults, page: pagenumber, total_results, total_pages: totalpages,
|
||||
}));
|
||||
}
|
||||
movieSearch(query, page=1) {
|
||||
const tmdbquery = { query: query, page: page };
|
||||
const cacheKey = `${this.cacheTags.movieSearch}:${page}:${query}`;
|
||||
|
||||
/**
|
||||
* Fetches a given list from tmdb.
|
||||
* @param {listName} List we want to fetch.
|
||||
* @param {type} The to specify in the request for discover (default 'movie').
|
||||
* @param {id} When finding similar a id can be added to query
|
||||
* @param {page} Page number we want to fetch.
|
||||
return this.cache.get(cacheKey)
|
||||
.catch(() => this.tmdb('searchMovie', tmdbquery))
|
||||
.catch(tmdbError => tmdbErrorResponse(tmdbError, 'movie search results'))
|
||||
.then(response => this.cache.set(cacheKey, response, 1))
|
||||
.then(response => this.mapResults(response, 'movie'))
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrive show search results from TMDB.
|
||||
* @param {String} text query you want to search for
|
||||
* @param {Number} page representing pagination of results
|
||||
* @returns {Promise} dict with query results, current page and total_pages
|
||||
*/
|
||||
listSearch(listName, type = 'movie', id, page = '1') {
|
||||
const params = { id, page };
|
||||
const cacheKey = `${this.cacheTags[listName]}:${type}:${id}:${page}`;
|
||||
return Promise.resolve()
|
||||
.then(() => this.cache.get(cacheKey))
|
||||
.catch(() => this.tmdb(this.tmdbMethod(listName, type), params))
|
||||
.then(response => this.cache.set(cacheKey, response))
|
||||
.then(response => this.mapResults(response, type))
|
||||
.catch((error) => { throw new Error(error); })
|
||||
.then(([mappedResults, pagenumber, totalpages, total_results]) => ({
|
||||
results: mappedResults, page: pagenumber, total_pages: totalpages, total_results,
|
||||
}));
|
||||
}
|
||||
showSearch(query, page=1) {
|
||||
const tmdbquery = { query: query, page: page };
|
||||
const cacheKey = `${this.cacheTags.showSearch}:${page}:${query}`;
|
||||
|
||||
tmdbMethod(apiMethod, type) {
|
||||
const method = TMDB_METHODS[apiMethod][type];
|
||||
if (method !== undefined) return method;
|
||||
throw new Error('Could not find tmdb api method.');
|
||||
}
|
||||
return this.cache.get(cacheKey)
|
||||
.catch(() => this.tmdb('searchTv', tmdbquery))
|
||||
.catch(tmdbError => tmdbErrorResponse(tmdbError, 'tv search results'))
|
||||
.then(response => this.cache.set(cacheKey, response, 1))
|
||||
.then(response => this.mapResults(response, 'show'))
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* Retrive person search results from TMDB.
|
||||
* @param {String} text query you want to search for
|
||||
* @param {Number} page representing pagination of results
|
||||
* @returns {Promise} dict with query results, current page and total_pages
|
||||
*/
|
||||
personSearch(query, page=1) {
|
||||
|
||||
const tmdbquery = { query: query, page: page, include_adult: true };
|
||||
const cacheKey = `${this.cacheTags.personSearch}:${page}:${query}`;
|
||||
|
||||
return this.cache.get(cacheKey)
|
||||
.catch(() => this.tmdb('searchPerson', tmdbquery))
|
||||
.catch(tmdbError => tmdbErrorResponse(tmdbError, 'person search results'))
|
||||
.then(response => this.cache.set(cacheKey, response, 1))
|
||||
.then(response => this.mapResults(response, 'person'))
|
||||
}
|
||||
|
||||
movieList(listname, page = 1) {
|
||||
const query = { page: page };
|
||||
const cacheKey = `${this.cacheTags[listname]}:${page}`;
|
||||
return this.cache.get(cacheKey)
|
||||
.catch(() => this.tmdb(listname, query))
|
||||
.catch(tmdbError => this.tmdbErrorResponse(tmdbError, 'movie list ' + listname))
|
||||
.then(response => this.cache.set(cacheKey, response, 1))
|
||||
.then(response => this.mapResults(response, 'movie'))
|
||||
}
|
||||
|
||||
showList(listname, page = 1) {
|
||||
const query = { page: page };
|
||||
const cacheKey = `${this.cacheTags[listname]}:${page}`;
|
||||
|
||||
return this.cache.get(cacheKey)
|
||||
.catch(() => this.tmdb(listname, query))
|
||||
.catch(tmdbError => this.tmdbErrorResponse(tmdbError, 'show list ' + listname))
|
||||
.then(response => this.cache.set(cacheKey, response, 1))
|
||||
.then(response => this.mapResults(response, 'show'))
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps our response from tmdb api to a movie/show object.
|
||||
* @param {response} JSON response from tmdb.
|
||||
* @param {type} The type declared in listSearch.
|
||||
* @param {String} response from tmdb.
|
||||
* @param {String} The type declared in listSearch.
|
||||
* @returns {Promise} dict with tmdb results, mapped as movie/show objects.
|
||||
*/
|
||||
mapResults(response, type) {
|
||||
return Promise.resolve()
|
||||
.then(() => {
|
||||
const mappedResults = response.results.filter((element) => {
|
||||
return (element.media_type === 'movie' || element.media_type === 'tv' || element.media_type === undefined);
|
||||
}).map((element) => convertTmdbToSeasoned(element, type));
|
||||
return [mappedResults, response.page, response.total_pages, response.total_results];
|
||||
})
|
||||
.catch((error) => { throw new Error(error); });
|
||||
}
|
||||
mapResults(response, type=undefined) {
|
||||
// console.log(response.results)
|
||||
// response.results.map(te => console.table(te))
|
||||
|
||||
let results = response.results.map(result => {
|
||||
if (type === 'movie' || result.media_type === 'movie') {
|
||||
const movie = Movie.convertFromTmdbResponse(result)
|
||||
return movie.createJsonResponse()
|
||||
} else if (type === 'show' || result.media_type === 'tv') {
|
||||
const show = Show.convertFromTmdbResponse(result)
|
||||
return show.createJsonResponse()
|
||||
} else if (type === 'person' || result.media_type === 'person') {
|
||||
const person = Person.convertFromTmdbResponse(result)
|
||||
return person.createJsonResponse()
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
results: results,
|
||||
page: response.page,
|
||||
total_results: response.total_results,
|
||||
total_pages: response.total_pages
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps moviedb library to support Promises.
|
||||
* @param {String} method function name in the library
|
||||
* @param {Object} argument argument to function being called
|
||||
* @returns {Promise} succeeds if callback succeeds
|
||||
*/
|
||||
tmdb(method, argument) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const callback = (error, reponse) => {
|
||||
if (error) {
|
||||
return reject(error);
|
||||
}
|
||||
return resolve(reponse);
|
||||
resolve(reponse);
|
||||
};
|
||||
|
||||
if (!argument) {
|
||||
@@ -130,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;
|
||||
|
||||
7
seasoned_api/src/tmdb/tmdb.ts
Normal file
7
seasoned_api/src/tmdb/tmdb.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { Movie } from './types'
|
||||
|
||||
Movie('str', 123)
|
||||
|
||||
|
||||
|
||||
module.exports = TMDB;
|
||||
7
seasoned_api/src/tmdb/types.js
Normal file
7
seasoned_api/src/tmdb/types.js
Normal 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 }
|
||||
64
seasoned_api/src/tmdb/types.ts
Normal file
64
seasoned_api/src/tmdb/types.ts
Normal 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 }
|
||||
75
seasoned_api/src/tmdb/types/credits.js
Normal file
75
seasoned_api/src/tmdb/types/credits.js
Normal 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;
|
||||
62
seasoned_api/src/tmdb/types/movie.js
Normal file
62
seasoned_api/src/tmdb/types/movie.js
Normal file
@@ -0,0 +1,62 @@
|
||||
class Movie {
|
||||
constructor(id, title, year=undefined, overview=undefined, poster=undefined, backdrop=undefined,
|
||||
releaseDate=undefined, rating=undefined, genres=undefined, productionStatus=undefined,
|
||||
tagline=undefined, runtime=undefined, imdb_id=undefined, popularity=undefined) {
|
||||
this.id = id;
|
||||
this.title = title;
|
||||
this.year = year;
|
||||
this.overview = overview;
|
||||
this.poster = poster;
|
||||
this.backdrop = backdrop;
|
||||
this.releaseDate = releaseDate;
|
||||
this.rating = rating;
|
||||
this.genres = genres;
|
||||
this.productionStatus = productionStatus;
|
||||
this.tagline = tagline;
|
||||
this.runtime = runtime;
|
||||
this.imdb_id = imdb_id;
|
||||
this.popularity = popularity;
|
||||
this.type = 'movie';
|
||||
}
|
||||
|
||||
static convertFromTmdbResponse(response) {
|
||||
const { id, title, release_date, overview, poster_path, backdrop_path, vote_average, genres, status,
|
||||
tagline, runtime, imdb_id, popularity } = response;
|
||||
|
||||
const releaseDate = new Date(release_date);
|
||||
const year = releaseDate.getFullYear();
|
||||
const genreNames = genres ? genres.map(g => g.name) : undefined
|
||||
|
||||
return new Movie(id, title, year, overview, poster_path, backdrop_path, releaseDate, vote_average, genreNames, status,
|
||||
tagline, runtime, imdb_id, popularity)
|
||||
}
|
||||
|
||||
static convertFromPlexResponse(response) {
|
||||
// console.log('response', response)
|
||||
const { title, year, rating, tagline, summary } = response;
|
||||
const _ = undefined
|
||||
|
||||
return new Movie(null, title, year, summary, _, _, _, rating, _, _, tagline)
|
||||
}
|
||||
|
||||
createJsonResponse() {
|
||||
return {
|
||||
id: this.id,
|
||||
title: this.title,
|
||||
year: this.year,
|
||||
overview: this.overview,
|
||||
poster: this.poster,
|
||||
backdrop: this.backdrop,
|
||||
release_date: this.releaseDate,
|
||||
rating: this.rating,
|
||||
genres: this.genres,
|
||||
production_status: this.productionStatus,
|
||||
tagline: this.tagline,
|
||||
runtime: this.runtime,
|
||||
imdb_id: this.imdb_id,
|
||||
type: this.type
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Movie;
|
||||
37
seasoned_api/src/tmdb/types/person.js
Normal file
37
seasoned_api/src/tmdb/types/person.js
Normal file
@@ -0,0 +1,37 @@
|
||||
class Person {
|
||||
constructor(id, name, poster=undefined, birthday=undefined, deathday=undefined,
|
||||
adult=undefined, knownForDepartment=undefined) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.poster = poster;
|
||||
this.birthday = birthday;
|
||||
this.deathday = deathday;
|
||||
this.adult = adult;
|
||||
this.knownForDepartment = knownForDepartment;
|
||||
this.type = 'person';
|
||||
}
|
||||
|
||||
static convertFromTmdbResponse(response) {
|
||||
const { id, name, poster, birthday, deathday, adult, known_for_department } = response;
|
||||
|
||||
const birthDay = new Date(birthday)
|
||||
const deathDay = deathday ? new Date(deathday) : null
|
||||
|
||||
return new Person(id, name, poster, birthDay, deathDay, adult, known_for_department)
|
||||
}
|
||||
|
||||
createJsonResponse() {
|
||||
return {
|
||||
id: this.id,
|
||||
name: this.name,
|
||||
poster: this.poster,
|
||||
birthday: this.birthday,
|
||||
deathday: this.deathday,
|
||||
known_for_department: this.knownForDepartment,
|
||||
adult: this.adult,
|
||||
type: this.type
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Person;
|
||||
78
seasoned_api/src/tmdb/types/releaseDates.js
Normal file
78
seasoned_api/src/tmdb/types/releaseDates.js
Normal 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;
|
||||
50
seasoned_api/src/tmdb/types/show.js
Normal file
50
seasoned_api/src/tmdb/types/show.js
Normal file
@@ -0,0 +1,50 @@
|
||||
class Show {
|
||||
constructor(id, title, year=undefined, overview=undefined, poster=undefined, backdrop=undefined,
|
||||
seasons=undefined, episodes=undefined, rank=undefined, genres=undefined, status=undefined,
|
||||
runtime=undefined) {
|
||||
this.id = id;
|
||||
this.title = title;
|
||||
this.year = year;
|
||||
this.overview = overview;
|
||||
this.poster = poster;
|
||||
this.backdrop = backdrop;
|
||||
this.seasons = seasons;
|
||||
this.episodes = episodes;
|
||||
this.rank = rank;
|
||||
this.genres = genres;
|
||||
this.productionStatus = status;
|
||||
this.runtime = runtime;
|
||||
this.type = 'show';
|
||||
}
|
||||
|
||||
static convertFromTmdbResponse(response) {
|
||||
const { id, name, first_air_date, overview, poster_path, backdrop_path, number_of_seasons, number_of_episodes,
|
||||
rank, genres, status, episode_run_time, popularity } = response;
|
||||
|
||||
const year = new Date(first_air_date).getFullYear()
|
||||
const genreNames = genres ? genres.map(g => g.name) : undefined
|
||||
|
||||
return new Show(id, name, year, overview, poster_path, backdrop_path, number_of_seasons, number_of_episodes,
|
||||
rank, genreNames, status, episode_run_time, popularity)
|
||||
}
|
||||
|
||||
createJsonResponse() {
|
||||
return {
|
||||
id: this.id,
|
||||
title: this.title,
|
||||
year: this.year,
|
||||
overview: this.overview,
|
||||
poster: this.poster,
|
||||
backdrop: this.backdrop,
|
||||
seasons: this.seasons,
|
||||
episodes: this.episodes,
|
||||
rank: this.rank,
|
||||
genres: this.genres,
|
||||
production_status: this.productionStatus,
|
||||
runtime: this.runtime,
|
||||
type: this.type
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Show;
|
||||
@@ -2,36 +2,44 @@ const User = require('src/user/user');
|
||||
const jwt = require('jsonwebtoken');
|
||||
|
||||
class Token {
|
||||
constructor(user) {
|
||||
this.user = user;
|
||||
}
|
||||
constructor(user, admin=false) {
|
||||
this.user = user;
|
||||
this.admin = admin;
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* Generate a new token.
|
||||
* @param {String} secret a cipher of the token
|
||||
* @returns {String}
|
||||
*/
|
||||
toString(secret) {
|
||||
return jwt.sign({ username: this.user.username }, secret);
|
||||
}
|
||||
toString(secret) {
|
||||
const username = this.user.username;
|
||||
const admin = this.admin;
|
||||
let data = { username }
|
||||
|
||||
/**
|
||||
if (admin)
|
||||
data = { ...data, admin }
|
||||
|
||||
return jwt.sign(data, secret, { expiresIn: '90d' });
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode a token.
|
||||
* @param {Token} jwtToken an encrypted token
|
||||
* @param {String} secret a cipher of the token
|
||||
* @returns {Token}
|
||||
*/
|
||||
static fromString(jwtToken, secret) {
|
||||
let username = null;
|
||||
static fromString(jwtToken, secret) {
|
||||
let username = null;
|
||||
|
||||
try {
|
||||
username = jwt.verify(jwtToken, secret).username;
|
||||
} catch (error) {
|
||||
throw new Error('The token is invalid.');
|
||||
}
|
||||
const user = new User(username);
|
||||
return new Token(user);
|
||||
}
|
||||
const token = jwt.verify(jwtToken, secret, { clockTolerance: 10000 })
|
||||
if (token.username === undefined || token.username === null)
|
||||
throw new Error('Malformed token')
|
||||
|
||||
username = token.username
|
||||
const user = new User(username)
|
||||
return new Token(user)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Token;
|
||||
|
||||
@@ -26,6 +26,7 @@ class UserRepository {
|
||||
if (error.name === 'AssertionError' || error.message.endsWith('user_name')) {
|
||||
throw new Error('That username is already registered');
|
||||
}
|
||||
throw Error(error)
|
||||
});
|
||||
}
|
||||
|
||||
@@ -41,7 +42,7 @@ class UserRepository {
|
||||
assert(row, 'The user does not exist.');
|
||||
return row.password;
|
||||
})
|
||||
.catch((err) => console.log('there was a error when getting hash', err));
|
||||
.catch((err) => { console.log(error); throw new Error('Unable to find your user.'); });
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -57,7 +58,7 @@ class UserRepository {
|
||||
checkAdmin(user) {
|
||||
return this.database.get(this.queries.getAdminStateByUser, user.username).then((row) => {
|
||||
return row.admin;
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,73 +1,72 @@
|
||||
const bcrypt = require('bcrypt-nodejs');
|
||||
const bcrypt = require('bcrypt');
|
||||
const UserRepository = require('src/user/userRepository');
|
||||
|
||||
class UserSecurity {
|
||||
constructor(database) {
|
||||
this.userRepository = new UserRepository(database);
|
||||
}
|
||||
constructor(database) {
|
||||
this.userRepository = new UserRepository(database);
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* Create a new user in PlanFlix.
|
||||
* @param {User} user the new user you want to create
|
||||
* @param {String} clearPassword a password of the user
|
||||
* @returns {Promise}
|
||||
*/
|
||||
createNewUser(user, clearPassword) {
|
||||
if (user.username.trim() === '') {
|
||||
throw new Error('The username is empty.');
|
||||
} else if (clearPassword.trim() === '') {
|
||||
throw new Error('The password is empty.');
|
||||
} else {
|
||||
return Promise.resolve()
|
||||
.then(() => this.userRepository.create(user))
|
||||
.then(() => UserSecurity.hashPassword(clearPassword))
|
||||
.then(hash => this.userRepository.changePassword(user, hash));
|
||||
}
|
||||
}
|
||||
createNewUser(user, clearPassword) {
|
||||
if (user.username.trim() === '') {
|
||||
throw new Error('The username is empty.');
|
||||
} else if (clearPassword.trim() === '') {
|
||||
throw new Error('The password is empty.');
|
||||
} else {
|
||||
return Promise.resolve()
|
||||
.then(() => this.userRepository.create(user))
|
||||
.then(() => UserSecurity.hashPassword(clearPassword))
|
||||
.then(hash => this.userRepository.changePassword(user, hash))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* Login into PlanFlix.
|
||||
* @param {User} user the user you want to login
|
||||
* @param {String} clearPassword the user's password
|
||||
* @returns {Promise}
|
||||
*/
|
||||
login(user, clearPassword) {
|
||||
return Promise.resolve()
|
||||
.then(() => this.userRepository.retrieveHash(user))
|
||||
.then(hash => UserSecurity.compareHashes(hash, clearPassword))
|
||||
.catch(() => { throw new Error('Wrong username or password.'); });
|
||||
}
|
||||
login(user, clearPassword) {
|
||||
return Promise.resolve()
|
||||
.then(() => this.userRepository.retrieveHash(user))
|
||||
.then(hash => UserSecurity.compareHashes(hash, clearPassword))
|
||||
.catch(() => { throw new Error('Incorrect username or password.'); });
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare between a password and a hash password from database.
|
||||
* @param {String} hash the hash password from database
|
||||
* @param {String} clearPassword the user's password
|
||||
* @returns {Promise}
|
||||
*/
|
||||
static compareHashes(hash, clearPassword) {
|
||||
return new Promise((resolve, reject) => {
|
||||
bcrypt.compare(clearPassword, hash, (error, matches) => {
|
||||
if (matches === true) {
|
||||
resolve();
|
||||
} else {
|
||||
reject();
|
||||
}
|
||||
});
|
||||
* Compare between a password and a hash password from database.
|
||||
* @param {String} hash the hash password from database
|
||||
* @param {String} clearPassword the user's password
|
||||
* @returns {Promise}
|
||||
*/
|
||||
static compareHashes(hash, clearPassword) {
|
||||
return new Promise((resolve, reject) => {
|
||||
bcrypt.compare(clearPassword, hash, (error, match) => {
|
||||
if (match)
|
||||
resolve()
|
||||
reject()
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* Hashes a password.
|
||||
* @param {String} clearPassword the user's password
|
||||
* @returns {Promise}
|
||||
*/
|
||||
static hashPassword(clearPassword) {
|
||||
return new Promise((resolve) => {
|
||||
bcrypt.hash(clearPassword, null, null, (error, hash) => {
|
||||
resolve(hash);
|
||||
});
|
||||
static hashPassword(clearPassword) {
|
||||
return new Promise((resolve) => {
|
||||
const saltRounds = 10;
|
||||
bcrypt.hash(clearPassword, saltRounds, (error, hash) => {
|
||||
resolve(hash);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = UserSecurity;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
const express = require('express');
|
||||
// const reload = require('express-reload');
|
||||
const Raven = require('raven');
|
||||
const bodyParser = require('body-parser');
|
||||
const tokenToUser = require('./middleware/tokenToUser');
|
||||
@@ -6,19 +7,14 @@ const mustBeAuthenticated = require('./middleware/mustBeAuthenticated');
|
||||
const mustBeAdmin = require('./middleware/mustBeAdmin');
|
||||
const configuration = require('src/config/configuration').getInstance();
|
||||
|
||||
const listController = require('./controllers/list/listController');
|
||||
|
||||
// TODO: Have our raven router check if there is a value, if not don't enable raven.
|
||||
Raven.config(configuration.get('raven', 'DSN')).install();
|
||||
|
||||
const app = express(); // define our app using express
|
||||
app.use(Raven.requestHandler());
|
||||
// this will let us get the data from a POST
|
||||
// configure app to use bodyParser()
|
||||
app.use(bodyParser.json());
|
||||
// router.use(bodyParser.urlencoded({ extended: true }));
|
||||
|
||||
|
||||
/* Decode the Authorization header if provided */
|
||||
// router.use(tokenToUser);
|
||||
|
||||
const router = express.Router();
|
||||
const allowedOrigins = ['https://kevinmidboe.com', 'http://localhost:8080'];
|
||||
@@ -27,19 +23,15 @@ const allowedOrigins = ['https://kevinmidboe.com', 'http://localhost:8080'];
|
||||
// router.use(bodyParser.json());
|
||||
app.use(bodyParser.urlencoded({ extended: true }));
|
||||
|
||||
|
||||
// This is probably a correct middleware/router setup
|
||||
/* Translate the user token to a user name */
|
||||
router.use(tokenToUser);
|
||||
/* Decode the Authorization header if provided */
|
||||
app.use(tokenToUser);
|
||||
|
||||
// TODO: Should have a separate middleware/router for handling headers.
|
||||
router.use((req, res, next) => {
|
||||
// TODO add logging of all incoming
|
||||
console.log('Request: ', req.originalUrl);
|
||||
const origin = req.headers.origin;
|
||||
const origin = req.headers.origin;
|
||||
if (allowedOrigins.indexOf(origin) > -1) {
|
||||
console.log('allowed');
|
||||
res.setHeader('Access-Control-Allow-Origin', origin);
|
||||
res.setHeader('Access-Control-Allow-Origin', origin);
|
||||
}
|
||||
res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization, loggedinuser');
|
||||
res.header('Access-Control-Allow-Methods', 'POST, GET, PUT');
|
||||
@@ -57,6 +49,32 @@ app.use(function onError(err, req, res, next) {
|
||||
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
|
||||
*/
|
||||
@@ -72,19 +90,50 @@ router.get('/v1/seasoned/all', require('./controllers/seasoned/readStrays.js'));
|
||||
router.get('/v1/seasoned/:strayId', require('./controllers/seasoned/strayById.js'));
|
||||
router.post('/v1/seasoned/verify/:strayId', require('./controllers/seasoned/verifyStray.js'));
|
||||
|
||||
router.get('/v2/search/', require('./controllers/search/multiSearch.js'));
|
||||
router.get('/v2/search/movie', require('./controllers/search/movieSearch.js'));
|
||||
router.get('/v2/search/show', require('./controllers/search/showSearch.js'));
|
||||
router.get('/v2/search/person', require('./controllers/search/personSearch.js'));
|
||||
|
||||
router.get('/v2/movie/now_playing', listController.nowPlayingMovies);
|
||||
router.get('/v2/movie/popular', listController.popularMovies);
|
||||
router.get('/v2/movie/top_rated', listController.topRatedMovies);
|
||||
router.get('/v2/movie/upcoming', listController.upcomingMovies);
|
||||
|
||||
router.get('/v2/show/now_playing', listController.nowPlayingShows);
|
||||
router.get('/v2/show/popular', listController.popularShows);
|
||||
router.get('/v2/show/top_rated', listController.topRatedShows);
|
||||
|
||||
router.get('/v2/movie/:id/credits', require('./controllers/movie/credits.js'));
|
||||
router.get('/v2/movie/:id/release_dates', require('./controllers/movie/releaseDates.js'));
|
||||
router.get('/v2/show/:id/credits', require('./controllers/show/credits.js'));
|
||||
|
||||
router.get('/v2/movie/:id', require('./controllers/movie/info.js'));
|
||||
router.get('/v2/show/:id', require('./controllers/show/info.js'));
|
||||
router.get('/v2/person/:id', require('./controllers/person/info.js'));
|
||||
|
||||
/**
|
||||
* Plex
|
||||
*/
|
||||
router.get('/v2/plex/search', require('./controllers/plex/search'));
|
||||
|
||||
/**
|
||||
* List
|
||||
*/
|
||||
router.get('/v1/plex/search', require('./controllers/plex/searchMedia.js'));
|
||||
router.get('/v1/plex/playing', require('./controllers/plex/plexPlaying.js'));
|
||||
router.get('/v1/plex/request', require('./controllers/plex/searchRequest.js'));
|
||||
router.get('/v1/plex/request/:mediaId', require('./controllers/plex/readRequest.js'));
|
||||
router.post('/v1/plex/request/:mediaId', require('./controllers/plex/submitRequest.js'));
|
||||
router.get('/v1/plex/hook', require('./controllers/plex/hookDump.js'));
|
||||
router.post('/v1/plex/hook', require('./controllers/plex/hookDump.js'));
|
||||
|
||||
/**
|
||||
* Requests
|
||||
*/
|
||||
|
||||
router.get('/v2/request', require('./controllers/request/fetchAllRequests.js'));
|
||||
router.get('/v2/request/:id', require('./controllers/request/getRequest.js'));
|
||||
router.post('/v2/request', require('./controllers/request/requestTmdbId.js'));
|
||||
router.get('/v1/plex/requests/all', require('./controllers/plex/fetchRequested.js'));
|
||||
router.put('/v1/plex/request/:requestId', mustBeAuthenticated, require('./controllers/plex/updateRequested.js'));
|
||||
|
||||
@@ -94,13 +143,6 @@ router.put('/v1/plex/request/:requestId', mustBeAuthenticated, require('./contro
|
||||
router.get('/v1/pirate/search', mustBeAuthenticated, require('./controllers/pirate/searchTheBay.js'));
|
||||
router.post('/v1/pirate/add', mustBeAuthenticated, require('./controllers/pirate/addMagnet.js'));
|
||||
|
||||
/**
|
||||
* TMDB
|
||||
*/
|
||||
router.get('/v1/tmdb/search', require('./controllers/tmdb/searchMedia.js'));
|
||||
router.get('/v1/tmdb/list/:listname', require('./controllers/tmdb/listSearch.js'));
|
||||
router.get('/v1/tmdb/:mediaId', require('./controllers/tmdb/readMedia.js'));
|
||||
|
||||
/**
|
||||
* git
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
const configuration = require('src/config/configuration').getInstance();
|
||||
const Cache = require('src/tmdb/cache');
|
||||
const TMDB = require('src/tmdb/tmdb');
|
||||
const cache = new Cache();
|
||||
const tmdb = new TMDB(cache, configuration.get('tmdb', 'apiKey'));
|
||||
|
||||
// there should be a translate function from query params to
|
||||
// tmdb list that is valid. Should it be a helper function or does it
|
||||
// belong in tmdb.
|
||||
// + could also have default value that are sent to the client.
|
||||
// * have the same class create a getListNames() and a fetchList()
|
||||
// * dicover list might be overkill_https://tinyurl.com/y7f8ragw
|
||||
// + trending! https://tinyurl.com/ydywrqox
|
||||
// by all, mediatype, or person. Can also define time periode to
|
||||
// get more trending view of what people are checking out.
|
||||
// + newly created (tv/latest).
|
||||
// + movie/latest
|
||||
//
|
||||
function handleError(error, res) {
|
||||
const { status, message } = error;
|
||||
|
||||
if (status && message) {
|
||||
res.status(status).send({ success: false, message })
|
||||
} else {
|
||||
console.log('caught list controller error', error)
|
||||
res.status(500).send({ message: 'An unexpected error occured while requesting list'})
|
||||
}
|
||||
}
|
||||
|
||||
function handleListResponse(response, res) {
|
||||
return res.send(response)
|
||||
.catch(error => handleError(error, res))
|
||||
}
|
||||
|
||||
function fetchTmdbList(req, res, listname, type) {
|
||||
const { page } = req.query;
|
||||
|
||||
if (type === 'movie') {
|
||||
return tmdb.movieList(listname, page)
|
||||
.then(listResponse => res.send(listResponse))
|
||||
.catch(error => handleError(error, res))
|
||||
} else if (type === 'show') {
|
||||
return tmdb.showList(listname, page)
|
||||
.then(listResponse => res.send(listResponse))
|
||||
.catch(error => handleError(error, res))
|
||||
}
|
||||
|
||||
handleError({
|
||||
status: 400,
|
||||
message: `'${type}' is not a valid list type.`
|
||||
}, res)
|
||||
}
|
||||
|
||||
const nowPlayingMovies = (req, res) => fetchTmdbList(req, res, 'miscNowPlayingMovies', 'movie')
|
||||
const popularMovies = (req, res) => fetchTmdbList(req, res, 'miscPopularMovies', 'movie')
|
||||
const topRatedMovies = (req, res) => fetchTmdbList(req, res, 'miscTopRatedMovies', 'movie')
|
||||
const upcomingMovies = (req, res) => fetchTmdbList(req, res, 'miscUpcomingMovies', 'movie')
|
||||
const nowPlayingShows = (req, res) => fetchTmdbList(req, res, 'tvOnTheAir', 'show')
|
||||
const popularShows = (req, res) => fetchTmdbList(req, res, 'miscPopularTvs', 'show')
|
||||
const topRatedShows = (req, res) => fetchTmdbList(req, res, 'miscTopRatedTvs', 'show')
|
||||
|
||||
module.exports = {
|
||||
nowPlayingMovies,
|
||||
popularMovies,
|
||||
topRatedMovies,
|
||||
upcomingMovies,
|
||||
nowPlayingShows,
|
||||
popularShows,
|
||||
topRatedShows
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
26
seasoned_api/src/webserver/controllers/movie/credits.js
Normal file
26
seasoned_api/src/webserver/controllers/movie/credits.js
Normal 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;
|
||||
58
seasoned_api/src/webserver/controllers/movie/info.js
Normal file
58
seasoned_api/src/webserver/controllers/movie/info.js
Normal 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;
|
||||
26
seasoned_api/src/webserver/controllers/movie/releaseDates.js
Normal file
26
seasoned_api/src/webserver/controllers/movie/releaseDates.js
Normal 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;
|
||||
25
seasoned_api/src/webserver/controllers/person/info.js
Normal file
25
seasoned_api/src/webserver/controllers/person/info.js
Normal file
@@ -0,0 +1,25 @@
|
||||
const configuration = require('src/config/configuration').getInstance();
|
||||
const Cache = require('src/tmdb/cache');
|
||||
const TMDB = require('src/tmdb/tmdb');
|
||||
const cache = new Cache();
|
||||
const tmdb = new TMDB(cache, configuration.get('tmdb', 'apiKey'));
|
||||
|
||||
/**
|
||||
* Controller: Retrieve information for a person
|
||||
* @param {Request} req http request variable
|
||||
* @param {Response} res
|
||||
* @returns {Callback}
|
||||
*/
|
||||
|
||||
function personInfoController(req, res) {
|
||||
const personId = req.params.id;
|
||||
|
||||
|
||||
tmdb.personInfo(personId)
|
||||
.then(person => res.send(person.createJsonResponse()))
|
||||
.catch(error => {
|
||||
res.status(404).send({ success: false, message: error.message });
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = personInfoController;
|
||||
@@ -10,13 +10,15 @@ const PirateRepository = require('src/pirate/pirateRepository');
|
||||
|
||||
function addMagnet(req, res) {
|
||||
const magnet = req.body.magnet;
|
||||
const name = req.body.name;
|
||||
const tmdb_id = req.body.tmdb_id;
|
||||
|
||||
PirateRepository.AddMagnet(magnet)
|
||||
PirateRepository.AddMagnet(magnet, name, tmdb_id)
|
||||
.then((result) => {
|
||||
res.send(result);
|
||||
})
|
||||
.catch((error) => {
|
||||
res.status(500).send({ success: false, error: error.message });
|
||||
.catch(error => {
|
||||
res.status(500).send({ success: false, message: error.message });
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -21,8 +21,8 @@ function updateRequested(req, res) {
|
||||
.then((result) => {
|
||||
res.send({ success: true, results: result });
|
||||
})
|
||||
.catch((error) => {
|
||||
res.status(401).send({ success: false, error: error.message });
|
||||
.catch(error => {
|
||||
res.status(401).send({ success: false, message: error.message });
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -10,14 +10,14 @@ const requestRepository = new RequestRepository();
|
||||
*/
|
||||
function fetchRequestedController(req, res) {
|
||||
// const user = req.loggedInUser;
|
||||
const { status } = req.query;
|
||||
const { status, page } = req.query;
|
||||
|
||||
requestRepository.fetchRequested(status)
|
||||
requestRepository.fetchRequested(status, page)
|
||||
.then((requestedItems) => {
|
||||
res.send({ success: true, results: requestedItems, total_results: requestedItems.length });
|
||||
})
|
||||
.catch((error) => {
|
||||
res.status(401).send({ success: false, error: error.message });
|
||||
res.status(401).send({ success: false, message: error.message });
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
const PlexRepository = require('src/plex/plexRepository');
|
||||
const configuration = require('src/config/configuration').getInstance();
|
||||
|
||||
const plexRepository = new PlexRepository();
|
||||
const plexRepository = new PlexRepository(configuration.get('plex', 'ip'));
|
||||
|
||||
function playingController(req, res) {
|
||||
plexRepository.nowPlaying()
|
||||
.then((movies) => {
|
||||
.then(movies => {
|
||||
res.send(movies);
|
||||
})
|
||||
.catch((error) => {
|
||||
res.status(500).send({ success: false, error: error.message });
|
||||
.catch(error => {
|
||||
res.status(500).send({ success: false, message: error.message });
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -12,10 +12,10 @@ function readRequestController(req, res) {
|
||||
const mediaId = req.params.mediaId;
|
||||
const { type } = req.query;
|
||||
requestRepository.lookup(mediaId, type)
|
||||
.then((movies) => {
|
||||
.then(movies => {
|
||||
res.send(movies);
|
||||
}).catch((error) => {
|
||||
res.status(404).send({ success: false, error: error.message });
|
||||
}).catch(error => {
|
||||
res.status(404).send({ success: false, message: error.message });
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
25
seasoned_api/src/webserver/controllers/plex/search.js
Normal file
25
seasoned_api/src/webserver/controllers/plex/search.js
Normal file
@@ -0,0 +1,25 @@
|
||||
const configuration = require('src/config/configuration').getInstance();
|
||||
const Plex = require('src/plex/plex');
|
||||
const plex = new Plex(configuration.get('plex', 'ip'));
|
||||
|
||||
/**
|
||||
* Controller: Search plex for movies, shows and episodes by query
|
||||
* @param {Request} req http request variable
|
||||
* @param {Response} res
|
||||
* @returns {Callback}
|
||||
*/
|
||||
function searchPlexController(req, res) {
|
||||
const { query, type } = req.query;
|
||||
plex.search(query, type)
|
||||
.then(movies => {
|
||||
if (movies.length > 0) {
|
||||
res.send(movies);
|
||||
} else {
|
||||
res.status(404).send({ success: false, message: 'Search query did not give any results from plex.'})
|
||||
}
|
||||
}).catch(error => {
|
||||
res.status(500).send({ success: false, message: error.message });
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = searchPlexController;
|
||||
@@ -1,6 +1,7 @@
|
||||
const PlexRepository = require('src/plex/plexRepository');
|
||||
const configuration = require('src/config/configuration').getInstance();
|
||||
|
||||
const plexRepository = new PlexRepository();
|
||||
const plexRepository = new PlexRepository(configuration.get('plex', 'ip'));
|
||||
|
||||
/**
|
||||
* Controller: Search for media and check existence
|
||||
@@ -13,15 +14,15 @@ function searchMediaController(req, res) {
|
||||
const { query } = req.query;
|
||||
|
||||
plexRepository.search(query)
|
||||
.then((media) => {
|
||||
.then(media => {
|
||||
if (media !== undefined || media.length > 0) {
|
||||
res.send(media);
|
||||
} else {
|
||||
res.status(404).send({ success: false, error: 'Search query did not return any results.' });
|
||||
res.status(404).send({ success: false, message: 'Search query did not return any results.' });
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
res.status(500).send({ success: false, error: error.message });
|
||||
.catch(error => {
|
||||
res.status(500).send({ success: false, message: error.message });
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ const searchHistory = new SearchHistory();
|
||||
function searchRequestController(req, res) {
|
||||
const user = req.loggedInUser;
|
||||
const { query, page, type } = req.query;
|
||||
const username = user == undefined ? undefined : user.username;
|
||||
const username = user === undefined ? undefined : user.username;
|
||||
|
||||
Promise.resolve()
|
||||
.then(() => searchHistory.create(username, query))
|
||||
@@ -18,8 +18,8 @@ function searchRequestController(req, res) {
|
||||
.then((searchResult) => {
|
||||
res.send(searchResult);
|
||||
})
|
||||
.catch((error) => {
|
||||
res.status(500).send({ success: false, error: error });
|
||||
.catch(error => {
|
||||
res.status(500).send({ success: false, message: error.message });
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,19 @@
|
||||
const RequestRepository = require('src/plex/requestRepository.js');
|
||||
const configuration = require('src/config/configuration').getInstance()
|
||||
const RequestRepository = require('src/request/request');
|
||||
const Cache = require('src/tmdb/cache')
|
||||
const TMDB = require('src/tmdb/tmdb')
|
||||
|
||||
const requestRepository = new RequestRepository();
|
||||
const cache = new Cache()
|
||||
const tmdb = new TMDB(cache, configuration.get('tmdb', 'apiKey'))
|
||||
const request = new RequestRepository()
|
||||
|
||||
const tmdbMovieInfo = (id) => {
|
||||
return tmdb.movieInfo(id)
|
||||
}
|
||||
|
||||
const tmdbShowInfo = (id) => {
|
||||
return tmdb.showInfo(id)
|
||||
}
|
||||
|
||||
/**
|
||||
* Controller: POST a media id to be donwloaded
|
||||
@@ -8,22 +21,31 @@ const requestRepository = new RequestRepository();
|
||||
* @param {Response} res
|
||||
* @returns {Callback}
|
||||
*/
|
||||
|
||||
function submitRequestController(req, res) {
|
||||
// This is the id that is the param of the url
|
||||
const id = req.params.mediaId;
|
||||
const type = req.query.type;
|
||||
const ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress;
|
||||
const user_agent = req.headers['user-agent'];
|
||||
const user = req.loggedInUser;
|
||||
// This is the id that is the param of the url
|
||||
const id = req.params.mediaId;
|
||||
const type = req.query.type ? req.query.type.toLowerCase() : undefined
|
||||
const ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress;
|
||||
const user_agent = req.headers['user-agent'];
|
||||
const user = req.loggedInUser;
|
||||
let mediaFunction = undefined
|
||||
|
||||
requestRepository.sendRequest(id, type, ip, user_agent, user)
|
||||
.then(() => {
|
||||
res.send({ success: true, message: 'Media item sucessfully requested!' });
|
||||
})
|
||||
.catch((error) => {
|
||||
res.status(500).send({ success: false, error: error.message });
|
||||
});
|
||||
if (type === 'movie') {
|
||||
console.log('movie')
|
||||
mediaFunction = tmdbMovieInfo
|
||||
} else if (type === 'show') {
|
||||
console.log('show')
|
||||
mediaFunction = tmdbShowInfo
|
||||
} else {
|
||||
res.status(422).send({ success: false, message: 'Incorrect type. Allowed types: "movie" or "show"'})
|
||||
}
|
||||
|
||||
if (mediaFunction === undefined) { res.status(200); return }
|
||||
|
||||
mediaFunction(id)
|
||||
.then(tmdbMedia => request.requestFromTmdb(tmdbMedia, ip, user_agent, user))
|
||||
.then(() => res.send({ success: true, message: 'Media item successfully requested' }))
|
||||
.catch(err => res.status(500).send({ success: false, message: err.message }))
|
||||
}
|
||||
|
||||
module.exports = submitRequestController;
|
||||
|
||||
@@ -18,7 +18,7 @@ function updateRequested(req, res) {
|
||||
res.send({ success: true });
|
||||
})
|
||||
.catch((error) => {
|
||||
res.status(401).send({ success: false, error: error.message });
|
||||
res.status(401).send({ success: false, message: error.message });
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
const RequestRepository = require('src/request/request');
|
||||
const request = new RequestRepository();
|
||||
|
||||
/**
|
||||
* Controller: Fetch all requested items
|
||||
* @param {Request} req http request variable
|
||||
* @param {Response} res
|
||||
* @returns {Callback}
|
||||
*/
|
||||
function fetchAllRequests(req, res) {
|
||||
let { page, filter, sort, query } = req.query;
|
||||
let sort_by = sort;
|
||||
let sort_direction = undefined;
|
||||
|
||||
if (sort !== undefined && sort.includes(':')) {
|
||||
[sort_by, sort_direction] = sort.split(':')
|
||||
}
|
||||
|
||||
Promise.resolve()
|
||||
.then(() => request.fetchAll(page, sort_by, sort_direction, filter, query))
|
||||
.then(result => res.send(result))
|
||||
.catch(error => {
|
||||
res.status(404).send({ success: false, message: error.message });
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = fetchAllRequests;
|
||||
21
seasoned_api/src/webserver/controllers/request/getRequest.js
Normal file
21
seasoned_api/src/webserver/controllers/request/getRequest.js
Normal file
@@ -0,0 +1,21 @@
|
||||
const RequestRepository = require('src/request/request');
|
||||
const request = new RequestRepository();
|
||||
|
||||
/**
|
||||
* Controller: Get requested item by tmdb id and type
|
||||
* @param {Request} req http request variable
|
||||
* @param {Response} res
|
||||
* @returns {Callback}
|
||||
*/
|
||||
function fetchAllRequests(req, res) {
|
||||
const id = req.params.id;
|
||||
const { type } = req.query;
|
||||
|
||||
request.getRequestByIdAndType(id, type)
|
||||
.then(result => res.send(result))
|
||||
.catch(error => {
|
||||
res.status(404).send({ success: false, message: error.message });
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = fetchAllRequests;
|
||||
@@ -0,0 +1,53 @@
|
||||
const configuration = require('src/config/configuration').getInstance();
|
||||
const Cache = require('src/tmdb/cache');
|
||||
const TMDB = require('src/tmdb/tmdb');
|
||||
const RequestRepository = require('src/request/request');
|
||||
const cache = new Cache();
|
||||
const tmdb = new TMDB(cache, configuration.get('tmdb', 'apiKey'));
|
||||
const request = new RequestRepository();
|
||||
|
||||
const tmdbMovieInfo = (id) => {
|
||||
return tmdb.movieInfo(id)
|
||||
}
|
||||
|
||||
const tmdbShowInfo = (id) => {
|
||||
return tmdb.showInfo(id)
|
||||
}
|
||||
|
||||
/**
|
||||
* Controller: Request by id with type param
|
||||
* @param {Request} req http request variable
|
||||
* @param {Response} res
|
||||
* @returns {Callback}
|
||||
*/
|
||||
function requestTmdbIdController(req, res) {
|
||||
const { id, type } = req.body
|
||||
|
||||
const ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress;
|
||||
const user_agent = req.headers['user-agent'];
|
||||
const user = req.loggedInUser;
|
||||
|
||||
let mediaFunction = undefined
|
||||
|
||||
if (id === undefined || type === undefined) {
|
||||
res.status(422).send({ success: false, message: "'Missing parameteres: 'id' and/or 'type'"})
|
||||
}
|
||||
|
||||
if (type === 'movie') {
|
||||
mediaFunction = tmdbMovieInfo
|
||||
} else if (type === 'show') {
|
||||
mediaFunction = tmdbShowInfo
|
||||
} else {
|
||||
res.status(422).send({ success: false, message: 'Incorrect type. Allowed types: "movie" or "show"'})
|
||||
}
|
||||
|
||||
mediaFunction(id)
|
||||
// .catch((error) => { console.error(error); res.status(404).send({ success: false, error: 'Id not found' }) })
|
||||
.then(tmdbMedia => request.requestFromTmdb(tmdbMedia, ip, user_agent, user))
|
||||
.then(() => res.send({success: true, message: 'Request has been submitted.'}))
|
||||
.catch(error => {
|
||||
res.send({ success: false, message: error.message });
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = requestTmdbIdController;
|
||||
40
seasoned_api/src/webserver/controllers/search/movieSearch.js
Normal file
40
seasoned_api/src/webserver/controllers/search/movieSearch.js
Normal file
@@ -0,0 +1,40 @@
|
||||
const configuration = require('src/config/configuration').getInstance();
|
||||
const Cache = require('src/tmdb/cache');
|
||||
const TMDB = require('src/tmdb/tmdb');
|
||||
const SearchHistory = require('src/searchHistory/searchHistory');
|
||||
const cache = new Cache();
|
||||
const tmdb = new TMDB(cache, configuration.get('tmdb', 'apiKey'));
|
||||
const searchHistory = new SearchHistory();
|
||||
|
||||
/**
|
||||
* Controller: Search for movies by query and pagey
|
||||
* @param {Request} req http request variable
|
||||
* @param {Response} res
|
||||
* @returns {Callback}
|
||||
*/
|
||||
function movieSearchController(req, res) {
|
||||
const user = req.loggedInUser;
|
||||
const { query, page } = req.query;
|
||||
|
||||
if (user) {
|
||||
return searchHistory.create(user, query);
|
||||
}
|
||||
|
||||
tmdb.movieSearch(query, page)
|
||||
.then(movieSearchResults => res.send(movieSearchResults))
|
||||
.catch(error => {
|
||||
const { status, message } = error;
|
||||
|
||||
if (status && message) {
|
||||
res.status(status).send({ success: false, message })
|
||||
} else {
|
||||
// TODO log unhandled errors
|
||||
console.log('caugth movie search controller error', error)
|
||||
res.status(500).send({
|
||||
message: `An unexpected error occured while searching movies with query: ${query}`
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = movieSearchController;
|
||||
45
seasoned_api/src/webserver/controllers/search/multiSearch.js
Normal file
45
seasoned_api/src/webserver/controllers/search/multiSearch.js
Normal file
@@ -0,0 +1,45 @@
|
||||
const configuration = require('src/config/configuration').getInstance();
|
||||
const Cache = require('src/tmdb/cache');
|
||||
const TMDB = require('src/tmdb/tmdb');
|
||||
const SearchHistory = require('src/searchHistory/searchHistory');
|
||||
const cache = new Cache();
|
||||
const tmdb = new TMDB(cache, configuration.get('tmdb', 'apiKey'));
|
||||
const searchHistory = new SearchHistory();
|
||||
|
||||
function checkAndCreateJsonResponse(result) {
|
||||
if (typeof result['createJsonResponse'] === 'function') {
|
||||
return result.createJsonResponse()
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* Controller: Search for multi (movies, shows and people by query and pagey
|
||||
* @param {Request} req http request variable
|
||||
* @param {Response} res
|
||||
* @returns {Callback}
|
||||
*/
|
||||
function multiSearchController(req, res) {
|
||||
const user = req.loggedInUser;
|
||||
const { query, page } = req.query;
|
||||
|
||||
if (user) {
|
||||
searchHistory.create(user, query)
|
||||
}
|
||||
|
||||
return tmdb.multiSearch(query, page)
|
||||
.then(multiSearchResults => res.send(multiSearchResults))
|
||||
.catch(error => {
|
||||
const { status, message } = error;
|
||||
|
||||
if (status && message) {
|
||||
res.status(status).send({ success: false, message })
|
||||
} else {
|
||||
// TODO log unhandled errors
|
||||
console.log('caugth multi search controller error', error)
|
||||
res.status(500).send({ message: `An unexpected error occured while searching with query: ${query}` })
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = multiSearchController;
|
||||
@@ -0,0 +1,42 @@
|
||||
const configuration = require('src/config/configuration').getInstance();
|
||||
const Cache = require('src/tmdb/cache');
|
||||
const TMDB = require('src/tmdb/tmdb');
|
||||
const SearchHistory = require('src/searchHistory/searchHistory');
|
||||
const cache = new Cache();
|
||||
const tmdb = new TMDB(cache, configuration.get('tmdb', 'apiKey'));
|
||||
const searchHistory = new SearchHistory();
|
||||
|
||||
/**
|
||||
* Controller: Search for person by query and pagey
|
||||
* @param {Request} req http request variable
|
||||
* @param {Response} res
|
||||
* @returns {Callback}
|
||||
*/
|
||||
function personSearchController(req, res) {
|
||||
const user = req.loggedInUser;
|
||||
const { query, page } = req.query;
|
||||
|
||||
if (user) {
|
||||
return searchHistory.create(user, query);
|
||||
}
|
||||
|
||||
tmdb.personSearch(query, page)
|
||||
.then((person) => {
|
||||
res.send(person);
|
||||
})
|
||||
.catch(error => {
|
||||
const { status, message } = error;
|
||||
|
||||
if (status && message) {
|
||||
res.status(status).send({ success: false, message })
|
||||
} else {
|
||||
// TODO log unhandled errors
|
||||
console.log('caugth person search controller error', error)
|
||||
res.status(500).send({
|
||||
message: `An unexpected error occured while searching people with query: ${query}`
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = personSearchController;
|
||||
35
seasoned_api/src/webserver/controllers/search/showSearch.js
Normal file
35
seasoned_api/src/webserver/controllers/search/showSearch.js
Normal file
@@ -0,0 +1,35 @@
|
||||
const SearchHistory = require('src/searchHistory/searchHistory');
|
||||
const configuration = require('src/config/configuration').getInstance();
|
||||
const Cache = require('src/tmdb/cache');
|
||||
const TMDB = require('src/tmdb/tmdb');
|
||||
const cache = new Cache();
|
||||
const tmdb = new TMDB(cache, configuration.get('tmdb', 'apiKey'));
|
||||
const searchHistory = new SearchHistory();
|
||||
|
||||
/**
|
||||
* Controller: Search for shows by query and pagey
|
||||
* @param {Request} req http request variable
|
||||
* @param {Response} res
|
||||
* @returns {Callback}
|
||||
*/
|
||||
function showSearchController(req, res) {
|
||||
const user = req.loggedInUser;
|
||||
const { query, page } = req.query;
|
||||
|
||||
Promise.resolve()
|
||||
.then(() => {
|
||||
if (user) {
|
||||
return searchHistory.create(user, query);
|
||||
}
|
||||
return null
|
||||
})
|
||||
.then(() => tmdb.showSearch(query, page))
|
||||
.then((shows) => {
|
||||
res.send(shows);
|
||||
})
|
||||
.catch(error => {
|
||||
res.status(500).send({ success: false, message: error.message });
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = showSearchController;
|
||||
@@ -10,7 +10,7 @@ function readStraysController(req, res) {
|
||||
res.send(strays);
|
||||
})
|
||||
.catch((error) => {
|
||||
res.status(500).send({ success: false, error: error.message });
|
||||
res.status(500).send({ success: false, message: error.message });
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ function strayByIdController(req, res) {
|
||||
res.send(stray);
|
||||
})
|
||||
.catch((error) => {
|
||||
res.status(500).send({ success: false, error: error.message });
|
||||
res.status(500).send({ success: false, message: error.message });
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ function verifyStrayController(req, res) {
|
||||
res.send({ success: true, message: 'Episode verified' });
|
||||
})
|
||||
.catch((error) => {
|
||||
res.status(500).send({ success: false, error: error.message });
|
||||
res.status(500).send({ success: false, message: error.message });
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
26
seasoned_api/src/webserver/controllers/show/credits.js
Normal file
26
seasoned_api/src/webserver/controllers/show/credits.js
Normal 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;
|
||||
56
seasoned_api/src/webserver/controllers/show/info.js
Normal file
56
seasoned_api/src/webserver/controllers/show/info.js
Normal 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;
|
||||
@@ -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, id, page } = req.query;
|
||||
tmdb.listSearch(listname, type, id, page)
|
||||
.then((results) => {
|
||||
res.send(results);
|
||||
}).catch((error) => {
|
||||
res.status(404).send({ success: false, error: error.message });
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = listSearchController;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -10,14 +10,14 @@ const searchHistory = new SearchHistory();
|
||||
*/
|
||||
function historyController(req, res) {
|
||||
const user = req.loggedInUser;
|
||||
const username = user == undefined ? undefined : user.username;
|
||||
const username = user === undefined ? undefined : user.username;
|
||||
|
||||
searchHistory.read(username)
|
||||
.then((searchQueries) => {
|
||||
.then(searchQueries => {
|
||||
res.send({ success: true, searchQueries });
|
||||
})
|
||||
.catch((error) => {
|
||||
res.status(404).send({ success: false, error: error });
|
||||
.catch(error => {
|
||||
res.status(404).send({ success: false, message: error.message });
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -20,13 +20,13 @@ function loginController(req, res) {
|
||||
|
||||
userSecurity.login(user, password)
|
||||
.then(() => userRepository.checkAdmin(user))
|
||||
.then((checkAdmin) => {
|
||||
const token = new Token(user).toString(secret);
|
||||
const admin_state = checkAdmin == 1 ? true : false;
|
||||
res.send({ success: true, token, admin: admin_state });
|
||||
.then(checkAdmin => {
|
||||
const isAdmin = checkAdmin === 1 ? true : false;
|
||||
const token = new Token(user, isAdmin).toString(secret);
|
||||
res.send({ success: true, token });
|
||||
})
|
||||
.catch((error) => {
|
||||
res.status(401).send({ success: false, error: error.message });
|
||||
.catch(error => {
|
||||
res.status(401).send({ success: false, message: error.message });
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -20,13 +20,15 @@ function registerController(req, res) {
|
||||
|
||||
userSecurity.createNewUser(user, password)
|
||||
.then(() => userRepository.checkAdmin(user))
|
||||
.then((checkAdmin) => {
|
||||
const token = new Token(user).toString(secret);
|
||||
const admin_state = checkAdmin == 1 ? true : false;
|
||||
res.send({ success: true, message: 'Welcome to Seasoned!', token, admin: admin_state });
|
||||
.then(checkAdmin => {
|
||||
const isAdmin = checkAdmin === 1 ? true : false;
|
||||
const token = new Token(user, isAdmin).toString(secret);
|
||||
res.send({
|
||||
success: true, message: 'Welcome to Seasoned!', token
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
res.status(401).send({ success: false, error: error.message });
|
||||
.catch(error => {
|
||||
res.status(401).send({ success: false, message: error.message });
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -12,12 +12,11 @@ function requestsController(req, res) {
|
||||
const user = req.loggedInUser;
|
||||
|
||||
requestRepository.userRequests(user)
|
||||
.then((requests) => {
|
||||
.then(requests => {
|
||||
res.send({ success: true, results: requests, total_results: requests.length });
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log(error)
|
||||
res.status(500).send({ success: false, error: error });
|
||||
.catch(error => {
|
||||
res.status(500).send({ success: false, message: error.message });
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
294
seasoned_api/src/webserver/graphql/requests.js
Normal file
294
seasoned_api/src/webserver/graphql/requests.js
Normal file
@@ -0,0 +1,294 @@
|
||||
const graphql = require("graphql");
|
||||
const establishedDatabase = require('src/database/database');
|
||||
const fetch = require('node-fetch');
|
||||
|
||||
|
||||
const TorrentType = new graphql.GraphQLObjectType({
|
||||
name: "Torrent",
|
||||
fields: {
|
||||
magnet: { type: graphql.GraphQLString },
|
||||
torrent_name: { type: graphql.GraphQLString},
|
||||
tmdb_id: { type: graphql.GraphQLString }
|
||||
}
|
||||
});
|
||||
|
||||
const RequestType = new graphql.GraphQLObjectType({
|
||||
name: "Request",
|
||||
fields: {
|
||||
id: { type: graphql.GraphQLID },
|
||||
title: { type: graphql.GraphQLString },
|
||||
year: { type: graphql.GraphQLInt},
|
||||
poster_path: { type: graphql.GraphQLString },
|
||||
background_path: { type: graphql.GraphQLString },
|
||||
requested_by: { type: graphql.GraphQLString },
|
||||
ip: { type: graphql.GraphQLString },
|
||||
date: { type: graphql.GraphQLString },
|
||||
status: { type: graphql.GraphQLString },
|
||||
user_agent: { type: graphql.GraphQLString },
|
||||
type: { type: graphql.GraphQLString }
|
||||
}
|
||||
});
|
||||
|
||||
const RequestsType = new graphql.GraphQLObjectType({
|
||||
name: 'Requests',
|
||||
type: graphql.GraphQLList(RequestType),
|
||||
resolve: (root, args, context, info) => {
|
||||
return establishedDatabase.all("SELECT * FROM requests;")
|
||||
.catch(error => console.error("something went wrong fetching 'all' query. Error:", error))
|
||||
}
|
||||
})
|
||||
|
||||
const ProgressType = new graphql.GraphQLObjectType({
|
||||
name: 'TorrentProgress',
|
||||
fields: {
|
||||
eta: { type: graphql.GraphQLInt },
|
||||
finished: { type: graphql.GraphQLBoolean },
|
||||
key: { type: graphql.GraphQLString },
|
||||
name: { type: graphql.GraphQLString },
|
||||
progress: { type: graphql.GraphQLFloat },
|
||||
state: { type: graphql.GraphQLString },
|
||||
Torrent: {
|
||||
type: TorrentType,
|
||||
resolve(parent) {
|
||||
console.log('prante: ', parent.name)
|
||||
console.log(parent.name.slice(0,10))
|
||||
return establishedDatabase.get(`select magnet, torrent_name, tmdb_id from requested_torrent where torrent_name like (?);`, [parent.name.slice(0, 10) + '%'])
|
||||
}
|
||||
},
|
||||
Requested: {
|
||||
type: graphql.GraphQLList(RequestType),
|
||||
// resolve: () => fetch('https://api.kevinmidboe.com/api/v2/request?page=1/').then(resp => resp.json())
|
||||
// .then(data => {
|
||||
// // console.log('data', data)
|
||||
// return data.results
|
||||
// })
|
||||
resolve: (parent) => {
|
||||
return establishedDatabase.all("SELECT * FROM requests;")
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
const TorrentsRequestedType = new graphql.GraphQLObjectType({
|
||||
name: 'TorrentsRequested',
|
||||
fields: {
|
||||
magnet: { type: graphql.GraphQLString },
|
||||
torrent_name: { type: graphql.GraphQLString },
|
||||
tmdb_id: { type: graphql.GraphQLString },
|
||||
date_added: { type: graphql.GraphQLString },
|
||||
Request: {
|
||||
type: RequestType,
|
||||
// resolve: () => fetch('https://api.kevinmidboe.com/api/v2/request?page=1/').then(resp => resp.json())
|
||||
// .then(data => {
|
||||
// return data.results
|
||||
// })
|
||||
resolve(parentValue, args) {
|
||||
return establishedDatabase.get('select * from requests where id = (?);', [parentValue.tmdb_id])
|
||||
}
|
||||
},
|
||||
Progress: {
|
||||
type: ProgressType,
|
||||
resolve(parentValue, args) {
|
||||
return fetch('http://localhost:5000/')
|
||||
.then(resp => resp.json())
|
||||
// .then(data => { console.log('data', data); return data.filter(download => download.name === parentValue.torrent_name) })
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
// create a graphql query to select all and by id
|
||||
var queryType = new graphql.GraphQLObjectType({
|
||||
name: 'Query',
|
||||
fields: {
|
||||
//first query to select all
|
||||
Requests: {
|
||||
type: graphql.GraphQLList(RequestType),
|
||||
resolve: (root, args, context, info) => {
|
||||
return establishedDatabase.all("SELECT * FROM requests;")
|
||||
.catch(error => console.error("something went wrong fetching 'all' query. Error:", error))
|
||||
}
|
||||
},
|
||||
Progress: {
|
||||
type: graphql.GraphQLList(ProgressType),
|
||||
resolve: (root, args, context, info) => {
|
||||
console.log('user', context.loggedInUser)
|
||||
return fetch('http://localhost:5000')
|
||||
.then(resp => resp.json())
|
||||
}
|
||||
},
|
||||
ProgressRequested: {
|
||||
type: graphql.GraphQLList(ProgressType),
|
||||
resolve: (root, args, context, info) => {
|
||||
console.log('root & args', root, args)
|
||||
}
|
||||
},
|
||||
TorrentsRequested: {
|
||||
type: graphql.GraphQLList(TorrentsRequestedType),
|
||||
resolve: (root, args, context, info) => {
|
||||
return establishedDatabase.all("SELECT * FROM requested_torrent;")
|
||||
.then(data => data.filter(request => { if (request.tmdb_id === '83666') { console.log('request', request, root);}; return request }))
|
||||
.catch(error => console.error("something went wrong fetching 'all' query. Error:", error))
|
||||
}
|
||||
},
|
||||
DownloadingRequestByName: {
|
||||
type: RequestType,
|
||||
args:{
|
||||
name:{
|
||||
type: new graphql.GraphQLNonNull(graphql.GraphQLString)
|
||||
}
|
||||
},
|
||||
resolve: (root, { name }, context, info) => {
|
||||
return establishedDatabase.all("SELECT * FROM requests where requested_by = (?);", [name])
|
||||
.catch(error => console.error("something went wrong fetching 'all' query. Error:", error))
|
||||
}
|
||||
},
|
||||
//second query to select by id
|
||||
Request:{
|
||||
type: RequestType,
|
||||
args:{
|
||||
id:{
|
||||
type: new graphql.GraphQLNonNull(graphql.GraphQLID)
|
||||
}
|
||||
},
|
||||
resolve: (root, {id}, context, info) => {
|
||||
return establishedDatabase.get("SELECT * FROM requests WHERE id = (?);",[id])
|
||||
.catch(error => console.error(`something went wrong fetching by id: '${ id }'. Error: ${ error }`))
|
||||
}
|
||||
},
|
||||
Torrents: {
|
||||
type: graphql.GraphQLList(TorrentType),
|
||||
resolve: (root, {id}, context, info) => {
|
||||
// console.log('parent', parent)
|
||||
return establishedDatabase.all("SELECT * FROM requested_torrent")
|
||||
.catch(error => console.error(`something went wrong fetching all torrents. Error: ${ error }`))
|
||||
}
|
||||
},
|
||||
Torrent: {
|
||||
type: TorrentType,
|
||||
args: {
|
||||
id: {
|
||||
type: new graphql.GraphQLNonNull(graphql.GraphQLID)
|
||||
}
|
||||
},
|
||||
resolve: (parent, {id}, context, info) => {
|
||||
// console.log('searcing from parent', parent)
|
||||
return establishedDatabase.get("SELECT * FROM requested_torrent WHERE tmdb_id = (?);", [id])
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
//mutation type is a type of object to modify data (INSERT,DELETE,UPDATE)
|
||||
// var mutationType = new graphql.GraphQLObjectType({
|
||||
// name: 'Mutation',
|
||||
// fields: {
|
||||
// //mutation for creacte
|
||||
// createPost: {
|
||||
// //type of object to return after create in SQLite
|
||||
// type: PostType,
|
||||
// //argument of mutation creactePost to get from request
|
||||
// args: {
|
||||
// title: {
|
||||
// type: new graphql.GraphQLNonNull(graphql.GraphQLString)
|
||||
// },
|
||||
// description:{
|
||||
// type: new graphql.GraphQLNonNull(graphql.GraphQLString)
|
||||
// },
|
||||
// createDate:{
|
||||
// type: new graphql.GraphQLNonNull(graphql.GraphQLString)
|
||||
// },
|
||||
// author:{
|
||||
// type: new graphql.GraphQLNonNull(graphql.GraphQLString)
|
||||
// }
|
||||
// },
|
||||
// resolve: (root, {title, description, createDate, author}) => {
|
||||
// return new Promise((resolve, reject) => {
|
||||
// //raw SQLite to insert a new post in post table
|
||||
// database.run('INSERT INTO Posts (title, description, createDate, author) VALUES (?,?,?,?);', [title, description, createDate, author], (err) => {
|
||||
// if(err) {
|
||||
// reject(null);
|
||||
// }
|
||||
// database.get("SELECT last_insert_rowid() as id", (err, row) => {
|
||||
|
||||
// resolve({
|
||||
// id: row["id"],
|
||||
// title: title,
|
||||
// description: description,
|
||||
// createDate:createDate,
|
||||
// author: author
|
||||
// });
|
||||
// });
|
||||
// });
|
||||
// })
|
||||
// }
|
||||
// },
|
||||
// //mutation for update
|
||||
// updatePost: {
|
||||
// //type of object to return afater update in SQLite
|
||||
// type: graphql.GraphQLString,
|
||||
// //argument of mutation creactePost to get from request
|
||||
// args:{
|
||||
// id:{
|
||||
// type: new graphql.GraphQLNonNull(graphql.GraphQLID)
|
||||
// },
|
||||
// title: {
|
||||
// type: new graphql.GraphQLNonNull(graphql.GraphQLString)
|
||||
// },
|
||||
// description:{
|
||||
// type: new graphql.GraphQLNonNull(graphql.GraphQLString)
|
||||
// },
|
||||
// createDate:{
|
||||
// type: new graphql.GraphQLNonNull(graphql.GraphQLString)
|
||||
// },
|
||||
// author:{
|
||||
// type: new graphql.GraphQLNonNull(graphql.GraphQLString)
|
||||
// }
|
||||
// },
|
||||
// resolve: (root, {id, title, description, createDate, author}) => {
|
||||
// return new Promise((resolve, reject) => {
|
||||
// //raw SQLite to update a post in post table
|
||||
// database.run('UPDATE Posts SET title = (?), description = (?), createDate = (?), author = (?) WHERE id = (?);', [title, description, createDate, author, id], (err) => {
|
||||
// if(err) {
|
||||
// reject(err);
|
||||
// }
|
||||
// resolve(`Post #${id} updated`);
|
||||
// });
|
||||
// })
|
||||
// }
|
||||
// },
|
||||
// //mutation for update
|
||||
// deletePost: {
|
||||
// //type of object resturn after delete in SQLite
|
||||
// type: graphql.GraphQLString,
|
||||
// args:{
|
||||
// id:{
|
||||
// type: new graphql.GraphQLNonNull(graphql.GraphQLID)
|
||||
// }
|
||||
// },
|
||||
// resolve: (root, {id}) => {
|
||||
// return new Promise((resolve, reject) => {
|
||||
// //raw query to delete from post table by id
|
||||
// database.run('DELETE from Posts WHERE id =(?);', [id], (err) => {
|
||||
// if(err) {
|
||||
// reject(err);
|
||||
// }
|
||||
// resolve(`Post #${id} deleted`);
|
||||
// });
|
||||
// })
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
|
||||
//define schema with post object, queries, and mustation
|
||||
const schema = new graphql.GraphQLSchema({
|
||||
query: queryType,
|
||||
// mutation: mutationType
|
||||
});
|
||||
|
||||
//export schema to use on index.js
|
||||
module.exports = {
|
||||
schema
|
||||
}
|
||||
@@ -8,16 +8,16 @@ const Token = require('src/user/token');
|
||||
// curl -i -H "Authorization:[token]" localhost:31459/api/v1/user/history
|
||||
|
||||
const tokenToUser = (req, res, next) => {
|
||||
const rawToken = req.headers.authorization;
|
||||
if (rawToken) {
|
||||
try {
|
||||
const token = Token.fromString(rawToken, secret);
|
||||
req.loggedInUser = token.user;
|
||||
} catch (error) {
|
||||
req.loggedInUser = undefined;
|
||||
}
|
||||
}
|
||||
next();
|
||||
const rawToken = req.headers.authorization;
|
||||
if (rawToken) {
|
||||
try {
|
||||
const token = Token.fromString(rawToken, secret);
|
||||
req.loggedInUser = token.user;
|
||||
} catch (error) {
|
||||
req.loggedInUser = undefined;
|
||||
}
|
||||
}
|
||||
next();
|
||||
};
|
||||
|
||||
module.exports = tokenToUser;
|
||||
|
||||
13
seasoned_api/test/fixtures/arrival-info-success-response.json
vendored
Normal file
13
seasoned_api/test/fixtures/arrival-info-success-response.json
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"background_path": "/yIZ1xendyqKvY3FGeeUYUd5X9Mm.jpg",
|
||||
"id": 329865,
|
||||
"popularity": 26.978601,
|
||||
"poster_path": "/hLudzvGfpi6JlwUnsNhXwKKg4j.jpg",
|
||||
"release_status": "Released",
|
||||
"score": 7.3,
|
||||
"summary": "Taking place after alien crafts land around the world, an expert linguist is recruited by the military to determine whether they come in peace or are a threat.",
|
||||
"tagline": "Why are they here?",
|
||||
"title": "Arrival",
|
||||
"type": "movie",
|
||||
"year": 2016
|
||||
}
|
||||
1
seasoned_api/test/fixtures/blade_runner_2049-info-success-response.json
vendored
Normal file
1
seasoned_api/test/fixtures/blade_runner_2049-info-success-response.json
vendored
Normal file
@@ -0,0 +1 @@
|
||||
[{"adult":false,"backdrop_path":"/mVr0UiqyltcfqxbAUcLl9zWL8ah.jpg","belongs_to_collection":{"id":422837,"name":"Blade Runner Collection","poster_path":"/cWESb1o9lW2i2Z3Xllv9u40aNIk.jpg","backdrop_path":"/bSHZIvLoPBWyGLeiAudN1mXdvQX.jpg"},"budget":150000000,"genres":[{"id":9648,"name":"Mystery"},{"id":878,"name":"Science Fiction"},{"id":53,"name":"Thriller"}],"homepage":"http://bladerunnermovie.com/","id":335984,"imdb_id":"tt1856101","original_language":"en","original_title":"Blade Runner 2049","overview":"Thirty years after the events of the first film, a new blade runner, LAPD Officer K, unearths a long-buried secret that has the potential to plunge what's left of society into chaos. K's discovery leads him on a quest to find Rick Deckard, a former LAPD blade runner who has been missing for 30 years.","popularity":30.03,"poster_path":"/gajva2L0rPYkEWjzgFlBXCAVBE5.jpg","production_companies":[{"id":79529,"logo_path":"/gVN3k8emmKy4iV4KREWcCtxusZK.png","name":"Torridon Films","origin_country":"US"},{"id":101829,"logo_path":"/8IOjCvgjq0zTrtP91cWD3kL2jMK.png","name":"16:14 Entertainment","origin_country":"US"},{"id":1645,"logo_path":"/6Ry6uNBaa0IbbSs1XYIgX5DkA9r.png","name":"Scott Free Productions","origin_country":""},{"id":5,"logo_path":"/71BqEFAF4V3qjjMPCpLuyJFB9A.png","name":"Columbia Pictures","origin_country":"US"},{"id":1088,"logo_path":"/9WOE5AQUXbOtLU6GTwfjS8OMF0v.png","name":"Alcon Entertainment","origin_country":"US"},{"id":78028,"logo_path":"/sTFcDFfJaSVT3sv3DoaZDE4SlGB.png","name":"Thunderbird Entertainment","origin_country":"CA"},{"id":174,"logo_path":"/ky0xOc5OrhzkZ1N6KyUxacfQsCk.png","name":"Warner Bros. Pictures","origin_country":"US"}],"production_countries":[{"iso_3166_1":"CA","name":"Canada"},{"iso_3166_1":"US","name":"United States of America"},{"iso_3166_1":"HU","name":"Hungary"},{"iso_3166_1":"GB","name":"United Kingdom"}],"release_date":"2017-10-04","revenue":259239658,"runtime":163,"spoken_languages":[{"iso_639_1":"en","name":"English"},{"iso_639_1":"fi","name":"suomi"}],"status":"Released","tagline":"There's still a page left.","title":"Blade Runner 2049","video":false,"vote_average":7.3,"vote_count":5478}]
|
||||
6
seasoned_api/test/fixtures/empty-query-success-response.json
vendored
Normal file
6
seasoned_api/test/fixtures/empty-query-success-response.json
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"page":1,
|
||||
"results":[],
|
||||
"total_results":0,
|
||||
"total_pages":1
|
||||
}
|
||||
1
seasoned_api/test/fixtures/interstellar-query-movie-success-response.json
vendored
Normal file
1
seasoned_api/test/fixtures/interstellar-query-movie-success-response.json
vendored
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
16
seasoned_api/test/helpers/tmdbMock2.js
Normal file
16
seasoned_api/test/helpers/tmdbMock2.js
Normal 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;
|
||||
@@ -5,8 +5,8 @@ xdescribe('As a developer I want the server to start', () => {
|
||||
beforeEach(() =>
|
||||
this.server = require('src/webserver/server'));
|
||||
|
||||
it('should listen on port 31459', (done) => {
|
||||
net.createConnection(31459, done);
|
||||
it('should listen on port 31400', (done) => {
|
||||
net.createConnection(31400, done);
|
||||
});
|
||||
|
||||
afterEach(() =>
|
||||
|
||||
@@ -15,6 +15,6 @@ describe('As a user I want error when registering existing username', () => {
|
||||
.post('/api/v1/user')
|
||||
.send({ username: 'test_user', password: 'password' })
|
||||
.expect(401)
|
||||
.then(response => assert.equal(response.text, '{"success":false,"error":"That username is already registered"}'))
|
||||
.then(response => assert.equal(response.text, '{"success":false,"message":"That username is already registered"}'))
|
||||
);
|
||||
});
|
||||
|
||||
@@ -7,12 +7,12 @@ const popularMoviesSuccess = require('test/fixtures/popular-movies-success-respo
|
||||
|
||||
describe('As a user I want to get popular movies', () => {
|
||||
before(() => resetDatabase());
|
||||
before(() => createCacheEntry('p:movie::1', popularMoviesSuccess));
|
||||
before(() => createCacheEntry('pm:1', popularMoviesSuccess));
|
||||
|
||||
it('should return 200 with the information', () =>
|
||||
request(app)
|
||||
.get('/api/v1/tmdb/list/popular')
|
||||
.get('/api/v2/movie/popular')
|
||||
.expect(200)
|
||||
.then(response => assert.equal(response.body.results.length, 20))
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -7,12 +7,12 @@ const popularShowsSuccess = require('test/fixtures/popular-show-success-response
|
||||
|
||||
describe('As a user I want to get popular shows', () => {
|
||||
before(() => resetDatabase());
|
||||
before(() => createCacheEntry('p:show::1', popularShowsSuccess));
|
||||
before(() => createCacheEntry('pt:1', popularShowsSuccess));
|
||||
|
||||
it('should return 200 with the information', () =>
|
||||
request(app)
|
||||
.get('/api/v1/tmdb/list/popular?type=show')
|
||||
.get('/api/v2/show/popular')
|
||||
.expect(200)
|
||||
.then(response => assert.equal(response.body.results.length, 20))
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,19 +1,23 @@
|
||||
const resetDatabase = require('test/helpers/resetDatabase');
|
||||
const createCacheEntry = require('test/helpers/createCacheEntry');
|
||||
const app = require('src/webserver/app');
|
||||
const request = require('supertest-as-promised');
|
||||
const createUser = require('test/helpers/createUser');
|
||||
const createToken = require('test/helpers/createToken');
|
||||
const infoMovieSuccess = require('test/fixtures/blade_runner_2049-info-success-response.json');
|
||||
|
||||
describe('As a user I want to request a movie', () => {
|
||||
before(() => {
|
||||
return resetDatabase()
|
||||
.then(() => createUser('test_user', 'test@gmail.com', 'password'));
|
||||
before(async () => {
|
||||
await resetDatabase()
|
||||
await createUser('test_user', 'test@gmail.com', 'password')
|
||||
})
|
||||
before(() => createCacheEntry('mi:335984:false', infoMovieSuccess));
|
||||
|
||||
it('should return 200 when item is requested', () =>
|
||||
request(app)
|
||||
.post('/api/v1/plex/request/329865')
|
||||
.set('Authorization', createToken('test_user', 'secret'))
|
||||
.post('/api/v2/request')
|
||||
.set('authorization', createToken('test_user', 'secret'))
|
||||
.send({ id: 335984, type: 'movie' })
|
||||
.expect(200)
|
||||
);
|
||||
});
|
||||
|
||||
@@ -2,15 +2,15 @@ const createCacheEntry = require('test/helpers/createCacheEntry');
|
||||
const resetDatabase = require('test/helpers/resetDatabase');
|
||||
const request = require('supertest-as-promised');
|
||||
const app = require('src/webserver/app');
|
||||
const interstellarQuerySuccess = require('test/fixtures/interstellar-query-success-response.json');
|
||||
const interstellarQuerySuccess = require('test/fixtures/interstellar-query-movie-success-response.json');
|
||||
|
||||
describe('As an anonymous user I want to search for a movie', () => {
|
||||
before(() => resetDatabase());
|
||||
before(() => createCacheEntry('s:1:movie:interstellar', interstellarQuerySuccess));
|
||||
before(() => createCacheEntry('mos:1:interstellar', interstellarQuerySuccess));
|
||||
|
||||
it('should return 200 with the search results even if user is not logged in', () =>
|
||||
request(app)
|
||||
.get('/api/v1/tmdb/search?query=interstellar&page=1')
|
||||
.get('/api/v2/search/movie?query=interstellar&page=1')
|
||||
.expect(200)
|
||||
);
|
||||
});
|
||||
|
||||
63
seasoned_api/test/unit/config/testConfig.js
Normal file
63
seasoned_api/test/unit/config/testConfig.js
Normal file
@@ -0,0 +1,63 @@
|
||||
const assert = require('assert');
|
||||
const Config = require('src/config/configuration.js');
|
||||
|
||||
describe('Config', () => {
|
||||
before(() => {
|
||||
this.backedUpEnvironmentVariables = Object.assign({}, process.env);
|
||||
this.backedUpConfigFields = Object.assign({}, Config.getInstance().fields);
|
||||
});
|
||||
|
||||
after(() => {
|
||||
process.env = this.backedUpEnvironmentVariables;
|
||||
Config.getInstance().fields = this.backedUpConfigFields;
|
||||
});
|
||||
|
||||
it('should retrieve section and option from config file', () => {
|
||||
Config.getInstance().fields = { 'webserver': { 'port': 1337 } };
|
||||
assert.equal(Config.getInstance().get('webserver', 'port'), 1337);
|
||||
});
|
||||
|
||||
it('should resolve to environment variables if option is filtered with env', () => {
|
||||
Config.getInstance().fields = { 'webserver': { 'port': 'env|SEASONED_WEBSERVER_PORT' } };
|
||||
process.env.SEASONED_WEBSERVER_PORT = '1338';
|
||||
assert.equal(Config.getInstance().get('webserver', 'port'), 1338);
|
||||
});
|
||||
|
||||
it('raises an exception if the environment variable does not exist', () => {
|
||||
Config.getInstance().fields = { 'webserver': { 'port': 'env|DOES_NOT_EXIST' } };
|
||||
process.env.SEASONED_WEBSERVER_PORT = '1338';
|
||||
assert.throws(() => Config.getInstance().get('webserver', 'port'), /empty/);
|
||||
});
|
||||
|
||||
it('raises an exception if the environment variable is empty', () => {
|
||||
Config.getInstance().fields = { 'webserver': { 'port': 'env|SEASONED_WEBSERVER_PORT' } };
|
||||
process.env.SEASONED_WEBSERVER_PORT = '';
|
||||
assert.throws(() => Config.getInstance().get('webserver', 'port'), /empty/);
|
||||
});
|
||||
|
||||
it('raises an exception if the section does not exist in the file', () => {
|
||||
Config.getInstance().fields = { 'webserver': { 'port': '1338' } };
|
||||
assert.throws(() => Config.getInstance().get('woops', 'port'), /does not exist/);
|
||||
});
|
||||
|
||||
it('raises an exception if the option does not exist in the file', () => {
|
||||
Config.getInstance().fields = { 'webserver': { 'port': '1338' } };
|
||||
assert.throws(() => Config.getInstance().get('webserver', 'woops'), /does not exist/);
|
||||
});
|
||||
|
||||
it('returns an array if field is an array', () => {
|
||||
Config.getInstance().fields = { 'bouncer': { 'whitelist': [1, 2, 3] } };
|
||||
assert.deepEqual(Config.getInstance().get('bouncer', 'whitelist'), [1, 2, 3]);
|
||||
});
|
||||
|
||||
it('decodes field as base64 if base64| is before the variable', () => {
|
||||
Config.getInstance().fields = { 'webserver': { 'port': 'base64|MTMzOA==' } };
|
||||
assert.equal(Config.getInstance().get('webserver', 'port'), 1338);
|
||||
});
|
||||
|
||||
it('decodes environment variable as base64 if BASE64= is before the variable', () => {
|
||||
Config.getInstance().fields = { 'webserver': { 'port': 'env|base64|SEASONED_WEBSERVER_PORT' } };
|
||||
process.env.SEASONED_WEBSERVER_PORT = 'MTMzOA==';
|
||||
assert.equal(Config.getInstance().get('webserver', 'port'), 1338);
|
||||
});
|
||||
});
|
||||
72
seasoned_api/test/unit/config/testField.js
Normal file
72
seasoned_api/test/unit/config/testField.js
Normal file
@@ -0,0 +1,72 @@
|
||||
const assert = require('assert');
|
||||
const Field = require('src/config/field.js');
|
||||
|
||||
describe('Field', () => {
|
||||
it('should return an array if it is an array', () => {
|
||||
const field = new Field([1, 2, 3]);
|
||||
assert.deepEqual(field.value, [1, 2, 3]);
|
||||
});
|
||||
|
||||
it('should return the plain value if it is an ordinary field', () => {
|
||||
const field = new Field('plain value');
|
||||
assert.equal(field.value, 'plain value');
|
||||
});
|
||||
|
||||
it('should return false if boolean false is field', () => {
|
||||
const field = new Field(false);
|
||||
assert.equal(field.value, false);
|
||||
});
|
||||
|
||||
it('should not include any invalid filters', () => {
|
||||
const field = new Field('invalid-filter|plain value');
|
||||
assert.equal(field.value, 'plain value');
|
||||
});
|
||||
|
||||
it('should return the decoded value if it is filtered through base64', () => {
|
||||
const field = new Field('base64|ZW5jb2RlZCB2YWx1ZQ==');
|
||||
assert.equal(field.value, 'encoded value');
|
||||
});
|
||||
|
||||
it('should not decode the value if it missing the filter', () => {
|
||||
const field = new Field('ZW5jb2RlZCB2YWx1ZQ==');
|
||||
assert.equal(field.value, 'ZW5jb2RlZCB2YWx1ZQ==');
|
||||
});
|
||||
|
||||
it('should retrieve the environment variable if env filter is used', () => {
|
||||
const environmentVariables = { REDIS_URL: 'redis://127.0.0.1:1234' };
|
||||
const field = new Field('env|REDIS_URL', environmentVariables);
|
||||
assert.equal(field.value, 'redis://127.0.0.1:1234');
|
||||
});
|
||||
|
||||
it('should return undefined if the environment variable does not exist', () => {
|
||||
const environmentVariables = { HTTP_PORT: 8080 };
|
||||
const field = new Field('env|REDIS_URL', environmentVariables);
|
||||
assert.equal(field.value, undefined);
|
||||
});
|
||||
|
||||
it('should return undefined if the environment variable is an empty string', () => {
|
||||
const environmentVariables = { REDIS_URL: '' };
|
||||
const field = new Field('env|REDIS_URL', environmentVariables);
|
||||
assert.deepEqual(field.value, undefined);
|
||||
});
|
||||
|
||||
describe('Multiple filters', () => {
|
||||
it('should decode the environment variable if base64 and env filter are used', () => {
|
||||
const environmentVariables = { REDIS_URL: 'cmVkaXM6Ly9kYWdibGFkZXQubm8vMTIzNA==' };
|
||||
const field = new Field('env|base64|REDIS_URL', environmentVariables);
|
||||
assert.equal(field.value, 'redis://dagbladet.no/1234');
|
||||
});
|
||||
|
||||
it('should disregard the order of filters when env and base64 are used', () => {
|
||||
const environmentVariables = { REDIS_URL: 'cmVkaXM6Ly9kYWdibGFkZXQubm8vMTIzNA==' };
|
||||
const field = new Field('base64|env|REDIS_URL', environmentVariables);
|
||||
assert.equal(field.value, 'redis://dagbladet.no/1234');
|
||||
});
|
||||
|
||||
it('should return undefined if both filters are used and env var does not exist', () => {
|
||||
const environmentVariables = { REDIS_URL: 'cmVkaXM6Ly9kYWdibGFkZXQubm8vMTIzNA==' };
|
||||
const field = new Field('base64|env|REDIS_LOL', environmentVariables);
|
||||
assert.equal(field.value, undefined);
|
||||
});
|
||||
});
|
||||
});
|
||||
34
seasoned_api/test/unit/config/testFilters.js
Normal file
34
seasoned_api/test/unit/config/testFilters.js
Normal file
@@ -0,0 +1,34 @@
|
||||
const assert = require('assert');
|
||||
const Filters = require('src/config/filters.js');
|
||||
|
||||
describe('Filters', () => {
|
||||
it('should extract base64 as filter if it is at start of string followed by pipe', () => {
|
||||
const filters = new Filters('base64|');
|
||||
assert.deepEqual(filters.filters, ['base64']);
|
||||
});
|
||||
|
||||
it('should extract base64 and env as filters if both are separated by pipe', () => {
|
||||
const filters = new Filters('base64|env|');
|
||||
assert.deepEqual(filters.filters, ['base64', 'env']);
|
||||
});
|
||||
|
||||
it('should not extract any filters if none are present', () => {
|
||||
const filters = new Filters('base64');
|
||||
assert.deepEqual(filters.filters, []);
|
||||
});
|
||||
|
||||
it('should strip env filter from the value', () => {
|
||||
const filters = new Filters('env|HELLO');
|
||||
assert.deepEqual(filters.removeFiltersFromValue(), 'HELLO');
|
||||
});
|
||||
|
||||
it('should strip env and base64 filter from the value', () => {
|
||||
const filters = new Filters('env|base64|HELLO');
|
||||
assert.deepEqual(filters.removeFiltersFromValue(), 'HELLO');
|
||||
});
|
||||
|
||||
it('should strip no filters from the value if there are no filters', () => {
|
||||
const filters = new Filters('HELLO');
|
||||
assert.deepEqual(filters.removeFiltersFromValue(), 'HELLO');
|
||||
});
|
||||
});
|
||||
31
seasoned_api/test/unit/tmdb/testConvertTmdbToMovie.js
Normal file
31
seasoned_api/test/unit/tmdb/testConvertTmdbToMovie.js
Normal file
@@ -0,0 +1,31 @@
|
||||
const assert = require('assert');
|
||||
// const convertTmdbToMovie = require('src/tmdb/convertTmdbToMovie');
|
||||
const { Movie } = require('src/tmdb/types');
|
||||
const bladeRunnerQuerySuccess = require('test/fixtures/blade_runner_2049-info-success-response.json')
|
||||
|
||||
describe('Convert tmdb movieInfo to movie', () => {
|
||||
beforeEach(() => [this.bladeRunnerTmdbMovie] = bladeRunnerQuerySuccess);
|
||||
|
||||
it('should translate the tmdb release date to movie year', () => {
|
||||
const bladeRunner = Movie.convertFromTmdbResponse(this.bladeRunnerTmdbMovie);
|
||||
assert.strictEqual(bladeRunner.year, 2017);
|
||||
});
|
||||
|
||||
it('should translate the tmdb release date to instance of Date', () => {
|
||||
const bladeRunner = Movie.convertFromTmdbResponse(this.bladeRunnerTmdbMovie);
|
||||
assert(bladeRunner.releaseDate instanceof Date);
|
||||
});
|
||||
|
||||
it('should translate the tmdb title to title', () => {
|
||||
const bladeRunner = Movie.convertFromTmdbResponse(this.bladeRunnerTmdbMovie);
|
||||
assert.equal(bladeRunner.title, 'Blade Runner 2049');
|
||||
});
|
||||
|
||||
it('should translate the tmdb vote_average to rating', () => {
|
||||
const bladeRunner = Movie.convertFromTmdbResponse(this.bladeRunnerTmdbMovie);
|
||||
assert.equal(bladeRunner.rating, 7.3);
|
||||
});
|
||||
|
||||
|
||||
|
||||
});
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user