44 Commits

Author SHA1 Message Date
3424325be4 Refactor: Python packages should be installed as modules, and not use source files (#149)
* Python packages should be installed as modules, and not use source files

* Use child_process spawn to call command and return data

* Consistent error rejection & resolve around spawn calls.

* Updated README to remove submodule ref & added how to install pip pcks.

* Resovled linting issue

* Removed python-shell package

* Disable stray functionality, unused & has dep to python-shell

* Uncommented/disabled python-shell dependency in strayRepository.
2022-10-02 16:05:29 +02:00
d6e5bdbd91 Updated docker image source repo url position within docker file 2022-09-25 17:08:01 +02:00
395dbe92f3 Updated docker image source repo url 2022-09-25 17:01:44 +02:00
212a6864f1 Fix: Redis call chain (#147)
* Moved redis mock client to separate file

* To keep cache response & api response consistent, alter call chain

* Resolved linting issues
2022-09-25 13:39:48 +02:00
c3f041d27f Simplified how we fetch and update requests that have landed in plex (#146) 2022-09-25 13:39:33 +02:00
2b6ad301a8 Resolved exception where readWithoutUserData was not defined (#145) 2022-09-25 13:39:23 +02:00
6732ed988d Register plex instance with token parameter for authenticated calls to plex (#144) 2022-09-25 13:39:07 +02:00
696f9f989c Fix: Plex errors (#141)
* inPlex function returns promise for more consistent return types

* plexUserId should default to null on falsey
2022-08-25 17:28:29 +02:00
93d394c2fe Return parsed json from redis cache (#142) 2022-08-25 17:20:52 +02:00
13470d7372 Custom errors for view hsitory/tautulli exceptions (#140) 2022-08-25 17:20:39 +02:00
7168950dfe Feat: es modules (#139)
* All file imports change from commonjs to es-module

* Improved plex error responses back from api

* Converted viewHistory to es module

* Es-module requires file extension, updated all imports

* Fix esmodule not having __dirname defined in scope

* Replace dynamic-require with fs readFileSync

* Short message service module function is exported as default

* Resolved lint issues & ignore import/extension rule until typescript

* All tests file imports changed from commonjs to es-module

* Import json fixtures with new helper
2022-08-25 17:19:23 +02:00
628ed52012 Fix: Tests lint and src folder (#138)
* Automaticly fixable eslint issues, mostly 3 -> 2 space indentation

* fix: updated plex_userid to camelcase

* Linted and some consistency refactor on middleware

* eslint uses ecmaversion 2020 & allow empty catch rule

* Started linting source files

* Fixed eslint errors & improved a lot of error handling

* Set 2 eslint rules as warning temporarly

* Updated all import statements to be relative

* Updated mocha & nyc, resolved all lint issues in tests/

* Updated mocha & nyc. Removed production config. Updated gitignore

* Updated test commands to omit system tests, no exit code

* Updated test configuration w/ missing keys

* Chai modules defined in package.json & resolved linting errors

* Dockerfile copies development.example -> production.json. Simplified commands

* All api calls from tests use same chaiHttp implementation

Removes a list of fetch alternatives after being replaced by chaiHttp:
 - request
 - request-promise
 - supertest
 - supertest-as-promised

* Tests should use redis (mock) cache, not tmdb sqlite cache

* Disabled test asADeveloperIWantTheServerToStart

* Re-enable tests/system

* Use chaiHttp in asAUserIWantToRequestAMovie.

* Fixed redis expire & mock implmentation

* Replaced all fetch alternatives from source code and package.json

* Pass error from tmdb api back to client as errorMessage

* Updated authentication middleware to handle checks consitenctly

* Prevent assert error when checking request status, returns success 200

* Resolved merge conflicts

* Only build and publish docker container when branch master
2022-08-20 17:41:46 +02:00
1815a429b0 Fix: Linter warnings (#137)
* Automaticly fixable eslint issues, mostly 3 -> 2 space indentation

* fix: updated plex_userid to camelcase

* Linted and some consistency refactor on middleware

* eslint uses ecmaversion 2020 & allow empty catch rule

* Started linting source files

* Fixed eslint errors & improved a lot of error handling

* Set 2 eslint rules as warning temporarly
2022-08-20 17:21:25 +02:00
cfbd4965db Merge pull request #136 from KevinMidboe/feat/emoji-from-codepoint
Feat: Emoji from code point
2022-08-19 10:18:35 +02:00
31c4b8b7df Replaced travis CI badge with Drone CI 2022-08-19 10:18:16 +02:00
a4b2e8f51c Instead of having a list of emojis, generate from code point 2022-08-19 10:08:34 +02:00
11fb803838 Removed seasoned_api folder 2022-08-19 10:06:18 +02:00
b2d2b0025c Merge pull request #134 from KevinMidboe/chore/seasoned_api-src-files
Chore: Seasoned api src files
2022-08-19 10:05:00 +02:00
ae9f3044bc Removed vue reference in prettierc (#135) 2022-08-19 10:04:01 +02:00
0eab680527 Include yarn lock into docker container build 2022-08-19 01:28:40 +02:00
4204515e91 Updated COPY and RUN commands for new project dir structure 2022-08-19 01:24:55 +02:00
19e99e8c43 Updated eslint config & prepared ts config 2022-08-19 01:14:18 +02:00
007d34f994 Updated dockerfile with new config dir in root 2022-08-19 01:10:16 +02:00
5a1e18b839 All entrypoints updated after moving seasoned_api contents to root dir 2022-08-19 01:04:45 +02:00
56262a45c8 Moved contents of seasoned_api up to root folder 2022-08-19 01:03:27 +02:00
0efc109992 Moved old webpage, app & client into .archive folder 2022-08-19 00:44:25 +02:00
851af204ab Remove python submodules 2022-08-19 00:43:41 +02:00
ccebf0d7b0 Merge pull request #132 from KevinMidboe/feat/drone-ci
Feat Drone CI & dockerize
2022-08-18 20:44:35 +02:00
76fe986f39 Merge branch 'feat/drone-ci' of github.com:KevinMidboe/seasonedShows into feat/drone-ci 2022-08-17 01:00:33 +02:00
fbf1ae8dec Moved github files to .github/ 2022-08-17 01:00:09 +02:00
9164b592bd Copy folder seasoned_api not just contents, matches project structure 2022-08-17 00:59:34 +02:00
d77b427c3c Copy folder seasoned_api not just contents, matches project structure 2022-08-17 00:40:58 +02:00
b56b1f6c0c Environment variables now take presedence over local config 2022-08-17 00:20:17 +02:00
c8477fabaa If redis fails to connect use a mock client to not crash 2022-08-17 00:19:22 +02:00
014cac8b06 Remove installing redis-server from apt on build 2022-08-17 00:18:34 +02:00
20d74cafb9 gchr repo name all lowercase 2022-08-16 01:22:35 +02:00
1690bbcdd0 Temporarly build and publish docker container on all push 2022-08-16 01:15:58 +02:00
7094aa2bb5 Updated all imports to be relative to itself 2022-08-16 01:15:36 +02:00
3660e88acf Moved package.json up to root folder 2022-08-16 01:13:07 +02:00
d8f7f82127 Drone config w/ install and build docker container steps 2022-08-16 00:42:23 +02:00
0a4248bf30 Setup dockerfile for seasoned api project
TODO remove apt installing redis and running service internally
2022-08-16 00:41:33 +02:00
92d139a156 Prevent foreign key to user, no user crashes server 2022-08-16 00:40:56 +02:00
5578bf854a Updated default config 2022-08-16 00:39:01 +02:00
5648b8fffa Merge pull request #130 from KevinMidboe/feat/cookie-authentication
Feat/cookie authentication
2022-08-15 23:59:12 +02:00
308 changed files with 12917 additions and 11973 deletions

View File

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

1
.dockerignore Normal file
View File

@@ -0,0 +1 @@
**/node_modules

111
.drone.yml Normal file
View File

@@ -0,0 +1,111 @@
---
kind: pipeline
type: docker
name: seasoned api build
platform:
os: linux
arch: amd64
volumes:
- name: cache
host:
path: /tmp/cache
steps:
- name: Load cached packages
image: sinlead/drone-cache:1.0.0
settings:
action: load
key: yarn.lock
mount: node_modules
prefix: yarn-modules-seasoned_api
volumes:
- name: cache
path: /cache
- name: Install dependencies
image: node:18.2.0
commands:
- node -v
- yarn --version
- yarn
- name: Cache packages
image: sinlead/drone-cache:1.0.0
settings:
action: save
key: yarn.lock
mount: node_modules
prefix: yarn-modules-seasoned_api
volumes:
- name: cache
path: /cache
# - name: Compile typescript
# image: node:18.2.0
# commands:
# - yarn build:ts
- name: Run test suite
image: node:18.2.0
commands:
- yarn test
failure: ignore
- name: Lint project using eslint
image: node:18.2.0
commands:
- yarn lint
failure: ignore
- name: Build and publish docker image
image: plugins/docker
settings:
registry: ghcr.io
repo: ghcr.io/kevinmidboe/seasoned_shows
dockerfile: Dockerfile
username:
from_secret: GITHUB_USERNAME
password:
from_secret: GITHUB_PASSWORD
tags: latest
environment:
TMDB_APIKEY:
from_secret: TMDB_APIKEY
PLEX_IP:
from_secret: PLEX_IP
PLEX_TOKEN:
from_secret: PLEX_TOKEN
when:
event:
- push
branch:
- master
# - name: deploy
# image: appleboy/drone-ssh
# pull: true
# secrets:
# - ssh_key
# when:
# event:
# - push
# branch:
# - master
# - drone-test
# status: success
# settings:
# host: 10.0.0.54
# username: root
# key:
# from_secret: ssh_key
# command_timeout: 600s
# script:
# - /home/kevin/deploy/seasoned.sh
trigger:
event:
include:
- push
# - pull_request

25
.eslintrc.json Normal file
View File

@@ -0,0 +1,25 @@
{
"root": true,
"parserOptions": {
"ecmaVersion": 2020,
"sourceType": "module"
},
"extends": ["eslint-config-airbnb-base", "plugin:prettier/recommended"],
"plugins": ["mocha"],
"rules": {
"import/extensions": 0,
"max-classes-per-file": 1,
"no-empty": [
2,
{
"allowEmptyCatch": true
}
],
"no-promise-executor-return": 1,
"no-shadow": "off",
"no-underscore-dangle": "off"
},
"env": {
"mocha": true
}
}

18
.eslintrc.ts.json Normal file
View File

@@ -0,0 +1,18 @@
{
"root": true,
"parserOptions": {
"parser": "@typescript-eslint/parser",
"sourceType": "module"
},
"plugins": ["@typescript-eslint"],
"extends": [
"eslint-config-airbnb-base",
"plugin:@typescript-eslint/recommended",
"plugin:prettier/recommended"
],
"rules": {
"no-underscore-dangle": "off",
"no-shadow": "off",
"@typescript-eslint/no-shadow": ["error"]
}
}

View File

8
.gitignore vendored
View File

@@ -1,7 +1,11 @@
.DS_Store
development.json
env
configurations/development.json
configurations/production.json
.env
shows.db
node_modules
*/package-lock.json
.nyc_output
yarn-error.log

10
.gitmodules vendored
View File

@@ -1,10 +0,0 @@
# Docs : https://git-scm.com/book/en/v2/Git-Tools-Submodules
[submodule "torrent_search"]
path = torrent_search
url = https://github.com/KevinMidboe/torrent_search.git
branch = master
[submodule "delugeClient"]
path = delugeClient
url = https://github.com/KevinMidboe/delugeClient.git

View File

@@ -5,6 +5,5 @@
"singleQuote": false,
"bracketSpacing": true,
"arrowParens": "avoid",
"vueIndentScriptAndStyle": false,
"trailingComma": "none"
}

20
Dockerfile Normal file
View File

@@ -0,0 +1,20 @@
FROM node:18
LABEL org.opencontainers.image.source https://github.com/kevinmidboe/seasonedShows
RUN mkdir -p /opt/seasonedShows/src
WORKDIR /opt/seasonedShows
COPY src/ src
COPY configurations/ configurations
COPY package.json .
COPY yarn.lock .
RUN apt update
RUN apt install node-pre-gyp -y
RUN yarn
RUN cp configurations/development.json.example configurations/production.json
EXPOSE 31459
CMD ["yarn", "start"]

133
README.md
View File

@@ -1,4 +1,3 @@
<h1 align="center">
🌶 seasonedShows
</h1>
@@ -6,16 +5,19 @@
<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 href="https://drone.schleppe.cloud/KevinMidboe/seasonedShows">
<img src="https://drone.schleppe.cloud/api/badges/KevinMidboe/seasonedShows/status.svg"
alt="Drone 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 href="https://snyk.io/test/github/KevinMidboe/seasonedShows?targetFile=package.json">
<img src="https://snyk.io/test/github/KevinMidboe/seasonedShows/badge.svg?targetFile=package.json" alt="">
</a>
<a href="https://opensource.org/licenses/MIT">
<img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="">
</a>
@@ -33,63 +35,73 @@
</p>
## <a name="demo-documentation"></a> Demo & Documentation
📺 [DEMO](https://kevinmidboe.com/request)
📺 [DEMO](https://request.movie)
📝 Documentation of the api.
💖 Checkout my [fancy vue.js page](https://github.com/KevinMidboe/seasonedRequest) for interfacing the api.
💖 Checkout my [fancy vue.js page](https://github.com/KevinMidboe/seasoned) 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.
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
- 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
- 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: `git clone git@github.com:KevinMidboe/seasonedShows.git`
- Type: `cd seasonedShows/`
- Install required packages
* yarn: `yarn install`
* npm: `npm install`
- yarn: `yarn install`
- npm: `npm install`
- Start server:
* yarn: `yarn start`
* npm: `npm run start`
- 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`
- Ubuntu/Debian: `sudo apt-get install git-core`
- Fedora: `sudo yum install git`
- Type: `git clone git@github.com:KevinMidboe/seasonedShows.git`
- Type: `cd seasonedShows/`
- Install required packages
* yarn: `yarn install`
* npm: `npm install`
- yarn: `yarn install`
- npm: `npm install`
- Start server:
* yarn: `yarn start`
* npm: `npm run start`
- 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].
@@ -98,42 +110,73 @@ After you have downloaded a package manager and node.js javascript engine, the f
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.
- Node 18 < [wiki page]
- Plex library
- Optional:
- redis
- deluge
- jackett
## <a name="setup"></a> Setup and configuration
Make a copy of configuration file in `configurations/` folder.
For use during development and with `yarn dev` command:
```bash
cp configurations/development.json.example configurations/development.json
```
For use during production and with `yarn start` command:
```bash
cp configurations/development.json.example configurations/production.json
```
Most important values to change here is adding [TMDB api key](https://developers.themoviedb.org/3/getting-started/introduction) and plex server IP.
### Optional setup
To allow authenticated or admin users add items [delugeClient](https://github.com/kevinmidboe/delugeClient) & [torrentSearch](https://github.com/KevinMidboe/torrent_search) can be setup to fetch and add magnet files. Both require python version >= 3.8 and can be downloaded using following pip command:
```bash
pip3 install delugeClient_kevin torrent_search
```
Both of these need to be configured, view their separate README's or find configuration files under `$HOME/.config/`.
## <a name="running"></a> Running/using
yarn/npm start. (can also say this above)
How to create service on linux. This means that
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.*
_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
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.
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.
So this is a multipart system that lets your plex users request movies, and then from the admin page the owner can.
## Installation
There are two main ways of
There are two main ways of
## Architecture
The flow of the system will first check for new folders in your tv shows directory, if a new file is found it's contents are analyzed, stored and tweets suggested changes to it's contents to use_admin.
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.

View File

@@ -1,6 +1,10 @@
{
"database": {
"host": "../shows.db"
"host": "./shows.db"
},
"redis": {
"host": "localhost",
"port": 6379
},
"webserver": {
"port": 31459,
@@ -10,7 +14,8 @@
"apiKey": ""
},
"plex": {
"ip": ""
"ip": "localhost",
"token": ""
},
"tautulli": {
"apiKey": "",

31
configurations/test.json Normal file
View File

@@ -0,0 +1,31 @@
{
"database": {
"host": ":memory:"
},
"redis": {
"host": "localhost",
"port": 6379
},
"webserver": {
"port": 31400,
"origins": []
},
"tmdb": {
"apiKey": "bogus-api-key"
},
"plex": {
"ip": "localhost",
"token": ""
},
"tautulli": {
"apiKey": "",
"ip": "",
"port": ""
},
"raven": {
"DSN": ""
},
"authentication": {
"secret": "secret"
}
}

55
package.json Normal file
View File

@@ -0,0 +1,55 @@
{
"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",
"type": "module",
"scripts": {
"start": "SEASONED_CONFIG=configurations/production.json NODE_ENV=production node src/webserver/server.js",
"dev": "SEASONED_CONFIG=configurations/development.json NODE_ENV=development node src/webserver/server.js",
"test": "SEASONED_CONFIG=configurations/test.json mocha --recursive tests/unit tests/system",
"coverage:upload": "SEASONED_CONFIG=configurations/test.json mocha --recursive tests/unit && nyc report --reporter=text-lcov | coveralls",
"coverage": "SEASONED_CONFIG=configurations/test.json mocha --recursive tests/unit && nyc report",
"lint": "eslint src tests",
"update": "SEASONED_CONFIG=configurations/development.json node scripts/updateRequestsInPlex.js",
"docs": "yarn apiDocs; yarn classDocs",
"apiDocs": "",
"classDocs": "scripts/generate-class-docs.sh"
},
"dependencies": {
"bcrypt": "^5.0.1",
"body-parser": "~1.18.2",
"cookie-parser": "^1.4.6",
"express": "~4.16.0",
"form-data": "^2.5.1",
"jsonwebtoken": "^8.5.1",
"km-moviedb": "^0.2.12",
"raven": "^2.4.2",
"redis": "^3.0.2",
"sqlite3": "^5.0.1"
},
"devDependencies": {
"@babel/core": "^7.5.5",
"@babel/preset-env": "^7.5.5",
"@babel/register": "^7.5.5",
"@types/node": "^12.6.8",
"chai": "^4.3.6",
"chai-http": "^4.3.0",
"coveralls": "^3.0.5",
"documentation": "^12.0.3",
"eslint": "^8.22.0",
"eslint-config-airbnb-base": "^15.0.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-import": "^2.8.0",
"eslint-plugin-mocha": "10.1.0",
"eslint-plugin-prettier": "^4.2.1",
"istanbul": "^0.4.5",
"mocha": "8.4.0",
"mocha-lcov-reporter": "^1.3.0",
"nyc": "15.1.0",
"prettier": "^2.7.1"
}
}

View File

@@ -0,0 +1,41 @@
import Plex from "../src/plex/plex.js";
import establishedDatabase from "../src/database/database.js";
import Configuration from "../src/config/configuration.js";
const configuration = Configuration.getInstance();
const plex = new Plex(
configuration.get("plex", "ip"),
configuration.get("plex", "token")
);
const queries = {
getRequestsNotYetInPlex: `SELECT * FROM requests WHERE status = 'requested' OR status = 'downloading'`,
saveNewStatus: `UPDATE requests SET status = ? WHERE id IS ? and type IS ?`
};
const getRequestsNotYetInPlex = () =>
establishedDatabase.all(queries.getRequestsNotYetInPlex);
async function getNewRequestMatchesInPlex() {
const requests = await getRequestsNotYetInPlex();
const exists = await Promise.all(
requests.map(request => plex.existsInPlex(request))
);
return requests.filter(() => exists.shift());
}
function commitNewStatus(status, id, type, title) {
console.log(`${type} ${title} updated to: ${status}`);
return establishedDatabase.run(queries.saveNewStatus, [status, id, type]);
}
function updateMatchInDb(match, status) {
return commitNewStatus(status, match.id, match.type, match.title);
}
getNewRequestMatchesInPlex()
.then(newMatches =>
Promise.all(newMatches.map(match => updateMatchInDb(match, "downloaded")))
)
.finally(() => process.exit(0));

View File

@@ -1,14 +0,0 @@
{
"extends": [
"airbnb-base"
],
"rules": {
"indent": ["error", 3],
"prefer-destructuring": 0,
"camelcase": 0,
"import/no-unresolved": 0,
"import/no-extraneous-dependencies": 0,
"object-shorthand": 0,
"comma-dangle": 0
}
}

View File

@@ -1,66 +0,0 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# nyc test coverage
.nyc_output
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Typescript v1 declaration files
typings/
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
# - - - - -
# My own gitignore files and folders
shows.db
conf/development.json
# conf/development-prod.json

View File

@@ -1,20 +0,0 @@
{
"database": {
"host": ":memory:"
},
"webserver": {
"port": 31400
},
"tmdb": {
"apiKey": "bogus-api-key"
},
"plex": {
"ip": "0.0.0.0"
},
"raven": {
"DSN": ""
},
"authentication": {
"secret": "secret"
}
}

View File

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

View File

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

View File

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

View File

@@ -1,43 +0,0 @@
const path = require('path');
const Field = require('./field.js');
let instance = null;
class Config {
constructor() {
this.location = Config.determineLocation();
this.fields = require(`${this.location}`);
}
static getInstance() {
if (instance == null) {
instance = new Config();
}
return instance;
}
static determineLocation() {
return path.join(__dirname, '..', '..', process.env.SEASONED_CONFIG);
}
get(section, option) {
if (this.fields[section] === undefined || this.fields[section][option] === undefined) {
throw new Error(`Field "${section} => ${option}" does not exist.`);
}
const field = new Field(this.fields[section][option]);
if (field.value === '') {
const envField = process.env[[section.toUpperCase(), option.toUpperCase()].join('_')];
if (envField !== undefined && envField.length !== 0) { return envField; }
}
if (field.value === undefined) {
throw new Error(`${section} => ${option} is empty.`);
}
return field.value;
}
}
module.exports = Config;

View File

@@ -1,15 +0,0 @@
class EnvironmentVariables {
constructor(variables) {
this.variables = variables || process.env;
}
get(variable) {
return this.variables[variable];
}
has(variable) {
return this.get(variable) !== undefined;
}
}
module.exports = EnvironmentVariables;

View File

@@ -1,49 +0,0 @@
const Filters = require('./filters.js');
const EnvironmentVariables = require('./environmentVariables.js');
class Field {
constructor(rawValue, environmentVariables) {
this.rawValue = rawValue;
this.filters = new Filters(rawValue);
this.valueWithoutFilters = this.filters.removeFiltersFromValue();
this.environmentVariables = new EnvironmentVariables(environmentVariables);
}
get value() {
if (this.filters.isEmpty()) {
return this.valueWithoutFilters;
}
if (this.filters.has('base64') && !this.filters.has('env')) {
return Field.base64Decode(this.valueWithoutFilters);
}
if (this.environmentVariables.has(this.valueWithoutFilters) &&
this.environmentVariables.get(this.valueWithoutFilters) === '') {
return undefined;
}
if (!this.filters.has('base64') && this.filters.has('env')) {
if (this.environmentVariables.has(this.valueWithoutFilters)) {
return this.environmentVariables.get(this.valueWithoutFilters);
}
return undefined;
}
if (this.filters.has('env') && this.filters.has('base64')) {
if (this.environmentVariables.has(this.valueWithoutFilters)) {
const encodedEnvironmentVariable = this.environmentVariables.get(this.valueWithoutFilters);
return Field.base64Decode(encodedEnvironmentVariable);
}
return undefined;
}
return this.valueWithoutFilters;
}
static base64Decode(string) {
return new Buffer(string, 'base64').toString('utf-8');
}
}
module.exports = Field;

View File

@@ -1,34 +0,0 @@
class Filters {
constructor(value) {
this.value = value;
this.delimiter = '|';
}
get filters() {
return this.value.split(this.delimiter).slice(0, -1);
}
isEmpty() {
return !this.hasValidType() || this.value.length === 0;
}
has(filter) {
return this.filters.includes(filter);
}
hasValidType() {
return (typeof this.value === 'string');
}
removeFiltersFromValue() {
if (this.hasValidType() === false) {
return this.value;
}
let filtersCombined = this.filters.join(this.delimiter);
filtersCombined += this.filters.length >= 1 ? this.delimiter : '';
return this.value.replace(filtersCombined, '');
}
}
module.exports = Filters;

View File

@@ -1,14 +0,0 @@
const configuration = require('src/config/configuration').getInstance();
const SqliteDatabase = require('src/database/sqliteDatabase');
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
* the required tables after successfully connecting.
* If the tables already exists, it simply proceeds.
*/
Promise.resolve()
.then(() => database.setUp());
module.exports = database;

View File

@@ -1,120 +0,0 @@
const fs = require('fs');
const path = require('path');
const sqlite3 = require('sqlite3').verbose();
class SqliteDatabase {
constructor(host) {
this.host = host;
this.connection = new sqlite3.Database(this.host);
this.execute('pragma foreign_keys = on;');
this.schemaDirectory = path.join(__dirname, 'schemas');
}
/**
* Connect to the database.
* @returns {Promise} succeeds if connection was established
*/
// connect() {
// let database = ;
// this.connection = database;
// return database;
// }
/**
* Run a SQL query against the database.
* @param {String} sql SQL query
* @param {Array} parameters in the SQL query
* @returns {Promise}
*/
run(sql, parameters) {
return new Promise((resolve, reject) => {
this.connection.run(sql, parameters, (error, result) => {
if (error)
reject(error);
resolve(result)
});
});
}
/**
* Run a SQL query against the database and retrieve all the rows.
* @param {String} sql SQL query
* @param {Array} parameters in the SQL query
* @returns {Promise}
*/
all(sql, parameters) {
return new Promise((resolve, reject) => {
this.connection.all(sql, parameters, (err, rows) => {
if (err) {
reject(err);
}
resolve(rows);
})
})
}
/**
* Run a SQL query against the database and retrieve one row.
* @param {String} sql SQL query
* @param {Array} parameters in the SQL query
* @returns {Promise}
*/
get(sql, parameters) {
return new Promise((resolve, reject) => {
this.connection.get(sql, parameters, (err, rows) => {
if (err) {
reject(err);
}
resolve(rows);
})
})
}
/**
* Run a SQL query against the database and retrieve the status.
* @param {String} sql SQL query
* @param {Array} parameters in the SQL query
* @returns {Promise}
*/
execute(sql) {
return new Promise(resolve => {
this.connection.exec(sql, (err, database) => {
if (err) {
console.log('ERROR: ', err);
reject(err);
}
resolve();
})
})
}
/**
* Setup the database by running setup.sql file in schemas/.
* @returns {Promise}
*/
setUp() {
const setupSchema = this.readSqlFile('setup.sql');
return Promise.resolve(this.execute(setupSchema));
}
/**
* Tears down the database by running tearDown.sql file in schemas/.
* @returns {Promise}
*/
tearDown() {
const tearDownSchema = this.readSqlFile('teardown.sql');
return Promise.resolve(this.execute(tearDownSchema));
}
/**
* Returns the file contents of a SQL file in schemas/.
* @returns {String}
*/
readSqlFile(filename) {
const schemaPath = path.join(this.schemaDirectory, filename);
const schema = fs.readFileSync(schemaPath).toString('utf-8');
return schema;
}
}
module.exports = SqliteDatabase;

View File

@@ -1,9 +0,0 @@
class GitRepository {
static dumpHook(body) {
/* eslint-disable no-console */
console.log(body);
}
}
module.exports = GitRepository;

View File

@@ -1,19 +0,0 @@
class Media {
constructor(title, year, type) {
this.title = title;
this.year = year;
this.type = type;
}
toString() {
return `N: ${this.title} | Y: ${this.year} | T: ${this.type}`;
}
print() {
/* eslint-disable no-console */
console.log(this.toString());
}
}
module.exports = Media;

View File

@@ -1,15 +0,0 @@
class MediaInfo {
constructor() {
this.duration = undefined;
this.height = undefined;
this.width = undefined;
this.bitrate = undefined;
this.resolution = undefined;
this.framerate = undefined;
this.protocol = undefined;
this.container = undefined;
this.audioCodec = undefined;
}
}
module.exports = MediaInfo;

View File

@@ -1,12 +0,0 @@
class Player {
constructor(device, address) {
this.device = device;
this.ip = address;
this.platform = undefined;
this.product = undefined;
this.title = undefined;
this.state = undefined;
}
}
module.exports = Player;

View File

@@ -1,22 +0,0 @@
const Media = require('src/media_classes/media');
class Plex extends Media {
constructor(title, year, type, summary, poster_path, background_path, added, seasons, episodes) {
super(title, year, type);
this.summary = summary;
this.poster_path = poster_path;
this.background_path = background_path;
this.added = added;
this.seasons = seasons;
this.episodes = episodes;
}
print() {
super.print();
}
}
module.exports = Plex;

View File

@@ -1,33 +0,0 @@
const Media = require('src/media_classes/media');
class TMDB extends Media {
// constructor(...args) {
constructor(title, year, type, id, summary, poster_path, background_path, popularity, score, release_status, tagline, seasons, episodes) {
super(title, year, type);
this.id = id;
this.summary = summary;
this.poster_path = poster_path;
this.background_path = background_path;
this.popularity = popularity;
this.score = score;
this.release_status = release_status;
this.tagline = tagline;
this.seasons = seasons;
this.episodes = episodes;
}
toString() {
return `${super.toString()} | ID: ${this.id}`;
}
print() {
/* eslint-disable no-console */
console.log(this.toString());
}
}
module.exports = TMDB;

View File

@@ -1,8 +0,0 @@
class User {
constructor(id, title) {
this.id = id;
this.title = title;
}
}
module.exports = User;

Some files were not shown because too many files have changed in this diff Show More