843 Commits

Author SHA1 Message Date
KevinMidboe 379e025ab6 Added typescript package, updated eslint & defined tsconfig 2022-08-22 14:09:04 +02:00
KevinMidboe 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
KevinMidboe 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
KevinMidboe cfbd4965db Merge pull request #136 from KevinMidboe/feat/emoji-from-codepoint
Feat: Emoji from code point
2022-08-19 10:18:35 +02:00
KevinMidboe 31c4b8b7df Replaced travis CI badge with Drone CI 2022-08-19 10:18:16 +02:00
KevinMidboe a4b2e8f51c Instead of having a list of emojis, generate from code point 2022-08-19 10:08:34 +02:00
KevinMidboe 11fb803838 Removed seasoned_api folder 2022-08-19 10:06:18 +02:00
KevinMidboe 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
KevinMidboe ae9f3044bc Removed vue reference in prettierc (#135) 2022-08-19 10:04:01 +02:00
KevinMidboe 0eab680527 Include yarn lock into docker container build 2022-08-19 01:28:40 +02:00
KevinMidboe 4204515e91 Updated COPY and RUN commands for new project dir structure 2022-08-19 01:24:55 +02:00
KevinMidboe 19e99e8c43 Updated eslint config & prepared ts config 2022-08-19 01:14:18 +02:00
KevinMidboe 007d34f994 Updated dockerfile with new config dir in root 2022-08-19 01:10:16 +02:00
KevinMidboe 5a1e18b839 All entrypoints updated after moving seasoned_api contents to root dir 2022-08-19 01:04:45 +02:00
KevinMidboe 56262a45c8 Moved contents of seasoned_api up to root folder 2022-08-19 01:03:27 +02:00
KevinMidboe 0efc109992 Moved old webpage, app & client into .archive folder 2022-08-19 00:44:25 +02:00
KevinMidboe 851af204ab Remove python submodules 2022-08-19 00:43:41 +02:00
KevinMidboe ccebf0d7b0 Merge pull request #132 from KevinMidboe/feat/drone-ci
Feat Drone CI & dockerize
2022-08-18 20:44:35 +02:00
KevinMidboe 76fe986f39 Merge branch 'feat/drone-ci' of github.com:KevinMidboe/seasonedShows into feat/drone-ci 2022-08-17 01:00:33 +02:00
KevinMidboe fbf1ae8dec Moved github files to .github/ 2022-08-17 01:00:09 +02:00
KevinMidboe 9164b592bd Copy folder seasoned_api not just contents, matches project structure 2022-08-17 00:59:34 +02:00
KevinMidboe d77b427c3c Copy folder seasoned_api not just contents, matches project structure 2022-08-17 00:40:58 +02:00
KevinMidboe b56b1f6c0c Environment variables now take presedence over local config 2022-08-17 00:20:17 +02:00
KevinMidboe c8477fabaa If redis fails to connect use a mock client to not crash 2022-08-17 00:19:22 +02:00
KevinMidboe 014cac8b06 Remove installing redis-server from apt on build 2022-08-17 00:18:34 +02:00
KevinMidboe 20d74cafb9 gchr repo name all lowercase 2022-08-16 01:22:35 +02:00
KevinMidboe 1690bbcdd0 Temporarly build and publish docker container on all push 2022-08-16 01:15:58 +02:00
KevinMidboe 7094aa2bb5 Updated all imports to be relative to itself 2022-08-16 01:15:36 +02:00
KevinMidboe 3660e88acf Moved package.json up to root folder 2022-08-16 01:13:07 +02:00
KevinMidboe d8f7f82127 Drone config w/ install and build docker container steps 2022-08-16 00:42:23 +02:00
KevinMidboe 0a4248bf30 Setup dockerfile for seasoned api project
TODO remove apt installing redis and running service internally
2022-08-16 00:41:33 +02:00
KevinMidboe 92d139a156 Prevent foreign key to user, no user crashes server 2022-08-16 00:40:56 +02:00
KevinMidboe 5578bf854a Updated default config 2022-08-16 00:39:01 +02:00
KevinMidboe 5648b8fffa Merge pull request #130 from KevinMidboe/feat/cookie-authentication
Feat/cookie authentication
2022-08-15 23:59:12 +02:00
KevinMidboe 1b7a754224 Merge pull request #131 from KevinMidboe/feat/credits
Feat/credits
2022-08-15 23:58:54 +02:00
KevinMidboe 5d91f1bae7 TODO sms code 2022-08-15 23:51:18 +02:00
KevinMidboe cdcfae56e7 Allow set-cookie & allow credentials 2022-08-15 23:39:39 +02:00
KevinMidboe f2c77e092d Safer imports in credits 2022-08-15 23:38:53 +02:00
KevinMidboe d6ac7e55e9 Better matching when getting matchin plex title & year 2022-08-15 23:38:35 +02:00
KevinMidboe a3543090f2 Translate plex query params to fixed 2022-08-15 23:37:07 +02:00
KevinMidboe 041e944783 Upgraded bcrypt & sqlite3 2022-08-15 23:36:02 +02:00
KevinMidboe bfd31ebd23 Linting 2022-03-06 11:59:32 +01:00
KevinMidboe 5036f4ca36 Read and pass adult search query param consistently for movie, show & person info 2022-03-06 11:57:45 +01:00
KevinMidboe 61b59ae3ea Person info now handles optional credits query parameter 2022-03-06 11:55:32 +01:00
KevinMidboe 92c49ac523 Registered endpoint for person credits 2022-03-06 11:54:56 +01:00
KevinMidboe f680642f25 When fetching credits for person we get movie & show objects, now handled here. 2022-03-06 11:54:22 +01:00
KevinMidboe f89486ae9e Sepearate controller and tmdb function for fetching personCredits 2022-03-06 11:53:45 +01:00
KevinMidboe 4d853565d1 Person gets biography & place_of_birth attributes 2022-03-06 11:50:20 +01:00
KevinMidboe 91c81e5cf6 Use cookie-parser, updated tokenToUser middleware & set logout endpoint 2022-03-06 10:39:34 +01:00
KevinMidboe 0ecbde9675 Logout endpoint for deleting auth cookie 2022-03-06 10:36:24 +01:00
KevinMidboe d8e951c2ef Can now pass settings to new Token. 2022-03-06 10:34:48 +01:00
KevinMidboe 90f3d86511 Removed unused Promise resolves 2022-03-06 10:34:39 +01:00
KevinMidboe c6791a7027 Login and register builds and sets cookie auth token 2022-03-06 10:31:58 +01:00
KevinMidboe 5b6a2c2651 Linting 2022-03-06 10:27:09 +01:00
KevinMidboe 4f7a22fff1 New tokenToUser middleware checks both header and cookie for Auth token 2022-03-06 10:17:56 +01:00
KevinMidboe 31b0c998a8 Removed filter option when searching jackett 2022-01-03 19:28:50 +01:00
KevinMidboe 9ce5b476ef Prettierrc file 2022-01-03 19:28:42 +01:00
KevinMidboe 554f292e4c Don't fail request when plex failes existance check 2022-01-03 19:28:09 +01:00
KevinMidboe d8985aaff7 Correctly use username from req.loggedInUser 2022-01-03 19:17:27 +01:00
KevinMidboe be889b8100 Lining 2022-01-03 19:06:11 +01:00
KevinMidboe b5bd672f44 More accurate checks for what response contains and throws error if it does not exist. 2022-01-03 18:38:32 +01:00
KevinMidboe 4501bc5302 Updated yarn lock file 2022-01-03 18:33:45 +01:00
KevinMidboe b384e748af Updated and moved update requests to /scripts 2022-01-03 18:32:48 +01:00
KevinMidboe c676f182b4 Cache time in HR & future cache bust function 2022-01-03 18:31:28 +01:00
KevinMidboe 95d2b0095b Linting 2022-01-03 18:23:49 +01:00
KevinMidboe 8165cf8e85 Keep whitespace when sanitizing string 2022-01-03 18:04:10 +01:00
KevinMidboe 14775744b0 Linting 2022-01-03 18:03:53 +01:00
KevinMidboe 559e32c059 Use list index 1 results 2022-01-03 18:02:58 +01:00
KevinMidboe f4dbaf4c58 Translate symbol + to space in query 2022-01-03 18:02:45 +01:00
KevinMidboe 1d25914ae0 Linting 2022-01-03 17:56:00 +01:00
KevinMidboe 4d3d8c874c Moved cors allowed origins to conf file. 2020-04-08 23:17:45 +02:00
KevinMidboe 08433523b7 Correctly export poster_path from person tmdb response object. 2020-04-08 23:09:37 +02:00
KevinMidboe fce8879994 Searching with adult parameter is now handled and also cached correctly. 2020-04-08 23:08:42 +02:00
KevinMidboe 505b126043 Cleanup, removed comments and logs. 2020-04-08 23:07:09 +02:00
KevinMidboe 589bd7b08d Better comparison between plex and tmdb. Now also checks if plexTitle starts with tmdbTitle. 2020-04-08 23:05:56 +02:00
KevinMidboe f0049ffb4e Merge pull request #129 from KevinMidboe/feat/redis-cache
Feat/redis cache
2020-04-08 23:04:06 +02:00
KevinMidboe 2b25397253 Misc updates to caching from plex and tmdb. 2020-04-08 23:01:33 +02:00
KevinMidboe 776f83553a Changed more instances of tmdb to new TMDB constructor not needing cache instance anymore. 2020-04-08 22:59:23 +02:00
KevinMidboe 815aaedffb Changed tmdb constructor to have apiKey as first parameter. If cache is not defined we use redis. 2020-04-08 22:54:19 +02:00
KevinMidboe 578eff30fb Id of request has been incorrectly saved as string, this has dictated the returntype to the frontend. Now set from string to int. 2020-04-08 21:58:37 +02:00
KevinMidboe 943cbe5cb8 Return rejected promise and don't set cache if key or value is null. 2020-04-08 21:57:49 +02:00
KevinMidboe f89db46bf2 findPlexItemByTitleAndYear returns a promise so we await its response. 2020-02-21 23:09:42 +01:00
KevinMidboe 085fb76e11 Cache pirate respons for 3 hours in redis. 2020-02-21 22:09:42 +01:00
KevinMidboe aa4a1c2a57 Registed route watch-link, by title & year
Fetch watch link for plex item matching query parameters title and year.
2020-02-21 21:53:51 +01:00
KevinMidboe 74340afd16 Full re-write. Refactored, cache and watchlink.
- Now the api calls to plex are cached.
- Search function has been refactored.
- Exists in plex has been re-written to use findPlexItemByTitleAndYear
if anything is found we return true. findPlexItemByTitleAndYear is then
also used for our new endpoint to get direct watch link for plex item.
Function parameters are title and year. Title is used when querying plex
and year is used with title to match correct plex search result. The
direct link includes the machine identifier so a function was added to
get system information from plex (read: 'PLEX_URL/' e.g. base path).
2020-02-21 21:53:19 +01:00
KevinMidboe 2672266908 Simplified every function towards tmdb by having the shared functionality separated into a function. 2020-02-21 21:47:00 +01:00
KevinMidboe f37786aa76 Redis cache script with get and set functions.
This replaces old way of caching to sqlite. Still have same
functionality, so users of old cache functions just need to rename their
import.
2020-02-21 18:11:39 +01:00
KevinMidboe 91f64e5cfb Redis new dependency 2020-02-21 18:09:04 +01:00
KevinMidboe a4d3123910 Stricter check for plex search results. 2020-01-16 21:25:45 +01:00
KevinMidboe bc6fe3ed48 Encode search query before searching plex. 2020-01-16 21:25:13 +01:00
KevinMidboe b23566509f Merge pull request #128 from KevinMidboe/fix/match-list-plex
Fixed issue matching list of plex object to tmdb.
2020-01-16 21:22:34 +01:00
KevinMidboe 341a07621d Fixed issue matching list of plex object to tmdb.
We have a Plex function that allows us to input a tmdb object and a plex
search result too see if the tmdb object has anything similar when
searching in plex.
Fixed an issue where plex returned a list of items. This list is now
mapped over each list element.
2020-01-16 21:21:40 +01:00
KevinMidboe 259ed9b06f Error. Fixed capitalization of file name. 2019-12-25 21:12:29 +01:00
KevinMidboe cddf06cbcc Travis should make a copy of the example development config. 2019-12-23 02:08:20 +01:00
KevinMidboe 318d1e331b Merge pull request #124 from KevinMidboe/feature/plex-authentication
Feature plex authentication
2019-12-23 01:39:43 +01:00
KevinMidboe 5923cbf051 Incorrect query parameter. Changed from (the not defined) plex_userid to username. 2019-12-22 13:49:16 +01:00
KevinMidboe 291bdf089c Forget to rename copied link function to unlink 2019-12-22 13:42:13 +01:00
KevinMidboe 8eacde9ccc Moved tautulli config settings to be fetched from our configuration (either env variable or conf/development.json. 2019-12-22 13:36:13 +01:00
KevinMidboe f8847c62f2 UserRepository handles updating db settings better.
Moved the plex_userid to settings to expanded with functions for
updating and fetching settings, each with its own helper function
towards the database.
Since we had a linkPlexUserId function and we dont want plex_userid to
be updated from the updatesettings function we moved unlinking to a
separate endpoint and class function.
Also a new controller and endpoints for getting and updating settings.
2019-12-22 13:30:18 +01:00
KevinMidboe ddb7e7379d Every instance of sqlite database should have foreign_keys constraints on 2019-12-22 13:14:12 +01:00
KevinMidboe 720fb69648 Indentation 2019-12-22 12:45:01 +01:00
KevinMidboe fedacf498e Changed input parameter from name from user to username, cause we just get the username string. 2019-12-22 12:44:45 +01:00
KevinMidboe 9022853502 Sql schema now has requested_by as a foreign key for user_name and on delete set null clause. 2019-12-22 12:43:12 +01:00
KevinMidboe c1b96e17ca Moved database row plex_userid from user to a new table settings. Currently includes plex_userid, emoji and darkmode with user_name as a foreign key to user.user_name. 2019-12-20 21:45:31 +01:00
KevinMidboe a5248f0631 TODO comment for login 2019-11-25 23:38:52 +01:00
KevinMidboe e2d85c6242 Merged into master branch 2019-11-24 19:26:56 +01:00
KevinMidboe 510c014549 Added endpoint for getting plays grouped by days of week. 2019-11-11 17:59:16 +01:00
KevinMidboe 2650497986 Tautulli will serve all the tautulli api calls. Mostly this will be used by user requests for getting stats related to the logged in user. WIP but currently watch time stats, plays by number of days, and view history has been implemented.
TODO:
 - Error handling if tautulli request fails.
 - Filter the responses and/or limit them from tautulli
 - Handle parsing variable parameters from request
2019-11-04 23:11:00 +01:00
KevinMidboe 639f0ec17a Created middleware to check if the user has a plex user linked to seasoned account. If not respond with 403 meaning you did have a authorization key, but this is forbidden; explaining in the response message that no plex account user id was found for this user and to please authenticate their plex account at authenticate endpoint. 2019-11-04 23:04:42 +01:00
KevinMidboe 977d05c6f2 Refactor. Responses should return error string in object key message not error. 2019-11-04 22:58:42 +01:00
KevinMidboe 601fc1d0de Renamed search_history controller to searchHistory 2019-11-04 22:57:59 +01:00
KevinMidboe acc26a2f09 Renamed user history to search_history and fixed an issue where search history received the entire user object and not just the username 2019-11-04 20:32:41 +01:00
KevinMidboe 5d3a5dc8a4 Removed unused console log 2019-11-04 18:46:42 +01:00
KevinMidboe 3bb9bd84d9 Merge branch 'master' into feature/plex-authentication 2019-11-04 18:24:19 +01:00
KevinMidboe ea5bc36956 Merge pull request #111 from KevinMidboe/api/v2
Api/v2
2019-11-04 18:01:15 +01:00
KevinMidboe 002e663be1 Merge branch 'api/v2' into feature/plex-authentication 2019-11-04 17:55:27 +01:00
KevinMidboe fd475265c1 Updated test to reflect changes to all error response objects. (changed from returning message in error key to message. 2019-11-04 17:54:08 +01:00
KevinMidboe 495a3b4838 Merged api/v2 into feature branch 2019-11-04 00:58:43 +01:00
KevinMidboe b0804f8a08 Merge branch 'api/v2' of github.com:kevinmidboe/seasonedShows into api/v2 2019-11-04 00:57:45 +01:00
KevinMidboe 6b737b8ab4 Updated all controller responses to return message not error on errors. 2019-11-04 00:57:27 +01:00
KevinMidboe 9e2a0101c9 Added new formdata pacakge 2019-11-04 00:44:57 +01:00
KevinMidboe 05b001de2e Created endpoint for authenticating with plex and linking your plex user to the logged in seasoned user. User database table gets a new plex_userid column. This will be used to fetch tautulli stats for your account. 2019-11-04 00:43:42 +01:00
KevinMidboe 5623344666 Merge branch 'master' into api/v2 2019-11-03 20:57:25 +01:00
KevinMidboe f8cc19b510 Added rating and release_date when parsing movies and fixed respective tests 2019-11-03 20:56:46 +01:00
KevinMidboe c589457a6c Removed unused mongoose package 2019-11-03 20:55:54 +01:00
KevinMidboe b802a7b62b Moved, renamed, re-did and added a lot of stuff. Getting ready for the v2 upgrade 2019-11-03 20:33:30 +01:00
KevinMidboe 879a02b388 Finished movie credits and release dates 2019-11-03 16:01:19 +01:00
KevinMidboe bc3d4881bd New release_dates endpoint for movie 2019-11-03 15:43:35 +01:00
KevinMidboe ef8d4d90b2 Removed console log 2019-11-03 15:08:42 +01:00
KevinMidboe d2d396bb7a Set cache TTL for credits to 1 day 2019-11-03 15:07:11 +01:00
KevinMidboe 500b75eaf6 We know there could be a 401 response so we handle it 2019-11-03 15:04:14 +01:00
KevinMidboe 9308d4ea9b Credits endpoint for movies 2019-11-03 15:02:45 +01:00
KevinMidboe 6c2c81a1a1 Updated movieInfo controller to also handle requesting release_dates as query parameter 2019-10-04 22:36:39 +02:00
KevinMidboe 90aa4d2485 Rewrote the movie & show list controller to be more abstract and easier to extend later 2019-10-04 21:21:52 +02:00
KevinMidboe 0ca3f81bf8 listController first defines all async functions as constant variables then module exports them all as a dict 2019-10-04 20:55:39 +02:00
KevinMidboe b9831c6b3d Forgot to reasing variables after copy-pasting them in convertTmdbToMovie 2019-10-04 20:37:06 +02:00
KevinMidboe 4781e9ae65 Merge pull request #119 from KevinMidboe/snyk-fix-a43be890852096db7a469faa6c44f8ef
[Snyk] Fix for 3 vulnerable dependencies
2019-07-27 02:17:50 +02:00
snyk-test eb0881f19e fix: client/package.json to reduce vulnerabilities
The following vulnerabilities are fixed with an upgrade:
- https://snyk.io/vuln/SNYK-JS-JSYAML-173999
- https://snyk.io/vuln/SNYK-JS-JSYAML-174129
- https://snyk.io/vuln/npm:mem:20180117
2019-07-27 00:16:09 +00:00
KevinMidboe bc4d73821d Upgraded webpack-dev-server to not have a screaming vulnerability 2019-07-27 02:05:38 +02:00
KevinMidboe ab6144eb81 Update yarn lock, updated coveralls and mocha run under coverage command now uses required babel register 2019-07-27 02:03:11 +02:00
KevinMidboe c3d87e2200 Merge pull request #117 from KevinMidboe/snyk-fix-9429d5dfb86996c66ac12aef1a5178b1
[Snyk] Fix for 2 vulnerable dependencies
2019-07-27 01:58:33 +02:00
KevinMidboe e391ce7ef9 Merge pull request #112 from KevinMidboe/snyk-fix-5oeu5e
[Snyk] Fix for 4 vulnerable dependencies
2019-07-27 01:58:07 +02:00
KevinMidboe ca707078d9 Merge branch 'master' into snyk-fix-5oeu5e 2019-07-27 01:57:08 +02:00
snyk-test 53228a2662 fix: client/package.json to reduce vulnerabilities
The following vulnerabilities are fixed with an upgrade:
- https://snyk.io/vuln/SNYK-JS-JSYAML-173999
- https://snyk.io/vuln/SNYK-JS-JSYAML-174129
2019-07-26 23:56:40 +00:00
KevinMidboe 2a9fa27341 Merge pull request #113 from KevinMidboe/snyk-fix-25dne6
[Snyk] Fix for 1 vulnerable dependencies
2019-07-27 01:56:24 +02:00
KevinMidboe 3068281461 Update README.md 2019-07-27 01:55:43 +02:00
KevinMidboe 81e9fe5b15 Testing for a running application is a bit weird, disabling it for now 2019-07-27 01:42:44 +02:00
KevinMidboe 5d2e375213 Upgraded node version for travis from 8 to 11 2019-07-27 01:34:15 +02:00
KevinMidboe 7ede37039a info-success-response is now a list so need this reflected in this test also. Changed the port we test for to whats in our config/test.jsono 2019-07-27 01:30:08 +02:00
KevinMidboe 8e23ae5a27 Added babel for ES6 functionality. In this case the new import statements 2019-07-27 01:28:41 +02:00
KevinMidboe 04ba094a14 Throw more errors and cleanup some unmerged code 2019-07-26 21:55:59 +02:00
KevinMidboe 23f9911237 Throw errors when failing to create user 2019-07-26 21:54:13 +02:00
KevinMidboe 3b27af1f83 Error handling for themoviedb api response codes that are not 200. Started with 401 and 404. See issue #116 for info. 2019-07-26 21:52:20 +02:00
KevinMidboe afb7af46b8 The test uses the cached to not need to query themoviedb, but the function that would in prod now saves a Class containing movie result and extras such as credits. 2019-07-26 21:09:58 +02:00
KevinMidboe 6ba8ca2add Updated search query response 2019-07-26 21:07:26 +02:00
KevinMidboe 135375cb94 instead of "describe" "xdescribe" was defined which made the test pending in results. This has now been resolved. 2019-07-26 21:06:45 +02:00
KevinMidboe e5d5bdefd6 Updated cache key and cleaned up formatting 2019-07-26 21:05:45 +02:00
KevinMidboe 6f9ca9e067 Added package and commands for generating documentation and upgraded mocha 2019-07-26 20:56:33 +02:00
KevinMidboe c42195d242 Removed swap file 2019-07-25 00:53:40 +02:00
KevinMidboe a5aaf1bfca Merge branch 'api/v2' of github.com:KevinMidboe/seasonedShows into api/v2 2019-07-25 00:53:16 +02:00
KevinMidboe af7b1f2424 Script for updating all requested and downloading request status to downloaded if exist in plex 2019-07-25 00:48:16 +02:00
KevinMidboe 6aba9774c6 When requesting all request elements we now also return the page as int not string 2019-07-25 00:47:17 +02:00
KevinMidboe e19cfb5870 Updated formatting 2019-07-25 00:24:04 +02:00
KevinMidboe 144b27f128 Renamed token variable from user to username 2019-07-25 00:23:32 +02:00
KevinMidboe 12afbf6364 Tokens can also have a admin property. When admin is defined its included in the jwt token. 2019-07-25 00:13:28 +02:00
KevinMidboe 8a5ab204e1 Change node bcrypt package from bcrypt-nodejs to bcrypt. Change response message on invalid username/pass and changed to bcrypt syntax for compare and hash. 2019-07-24 23:02:06 +02:00
KevinMidboe 3f04d9bc56 Update script for updating all plex statuses of requestes. 2019-07-24 23:02:06 +02:00
KevinMidboe de50805d1e Handle both status code 301 and 302 from jackett 2019-07-15 18:16:46 +02:00
KevinMidboe 3a9131a022 Removed, commented and added comments 2019-07-02 23:53:26 +02:00
KevinMidboe 77433e8505 Query of plex search is now encoded 2019-07-02 23:53:08 +02:00
KevinMidboe 3845000b3f Allow filtering for requested items by status 2019-06-28 23:32:11 +02:00
KevinMidboe 071fd54825 Pages for requests are only calulated for items not yet downloaded and use ceil isteadof floor to get last items on a page 2019-06-28 22:51:09 +02:00
KevinMidboe 537f237e83 Updated lock file 2019-06-28 22:45:31 +02:00
KevinMidboe d3bc854e03 Pirate repository has relative and use virtuale python when running python commands 2019-06-28 22:44:39 +02:00
KevinMidboe 15826a00ba plexRepo now using class instance plexIp address 2019-06-28 22:42:11 +02:00
KevinMidboe 4019d63f3b Created example config and added development config to gitignore 2019-06-28 22:39:24 +02:00
KevinMidboe 91dcfaccb9 Rewrote posting request controller to handle body parameters and use new requestsRepository function istead of plexRepository functions 2019-06-28 22:31:52 +02:00
KevinMidboe 270a259cee Request list also gets and returns total pages 2019-06-28 21:51:43 +02:00
KevinMidboe 162d20ae52 Submitting requests now use requests repository 2019-06-28 21:51:11 +02:00
KevinMidboe 9f1badc1b1 Get a request item by id and type 2019-06-28 19:21:54 +02:00
KevinMidboe ac027a97d6 Added pagination and removed sort & filtering for requested items 2019-06-28 18:50:30 +02:00
KevinMidboe 127db88ded Renamed function name and comment to make for sense. Also deconstruct page from query 2019-06-28 18:45:53 +02:00
KevinMidboe 4b07434615 Imported configuration with incorrect name 2019-06-05 00:05:54 +02:00
KevinMidboe 5d6f2baa34 plex hook should have post not get 2019-06-05 00:00:31 +02:00
KevinMidboe 1a1a7328a3 Map results with total_results before returing. TODO this should be mapped with all wanted list return vars 2019-06-04 23:54:39 +02:00
KevinMidboe b9dec2344e Added timeout to plex requests and include error in error message when unable to search 2019-06-04 23:53:54 +02:00
KevinMidboe 476a34fb69 Changed the order of execution between getting tmdb movie and searching plex for it. Now we await tmdb movie and then check if exists in plex. This is better when we miss plex request 2019-06-04 23:47:10 +02:00
KevinMidboe e3ed08e8dd Now a plex ip address is dynamically passed into the plexrepository, fetched from the config 2019-06-04 23:45:22 +02:00
KevinMidboe 70f6497404 All converter function from tmdb to movie, show and person takes optional cast object and maps to response 2019-06-04 23:35:21 +02:00
KevinMidboe 99bab3fb73 Movie and show can also return credits for a item. Enabled by query parameter credits=true 2019-06-04 23:32:38 +02:00
KevinMidboe e6796aff8b Hotfix 🧯 for returning new poster object variable 2019-06-04 21:41:10 +02:00
KevinMidboe 1f9dc067e6 Plex params are now parsed with URI encoder. 2019-04-10 22:22:52 +02:00
snyk-bot 4eaa60b044 fix: seasoned_api/package.json to reduce vulnerabilities
The following vulnerabilities are fixed with an upgrade:
- https://snyk.io/vuln/SNYK-JS-MPATH-72672
2018-12-13 03:19:03 +00:00
snyk-bot 7db8f752c5 fix: seasoned_api/package.json to reduce vulnerabilities
The following vulnerabilities are fixed with an upgrade:
- https://snyk.io/vuln/npm:base64url:20180511
- https://snyk.io/vuln/npm:cryptiles:20180710
- https://snyk.io/vuln/npm:extend:20180424
- https://snyk.io/vuln/npm:stringstream:20180511
2018-12-08 03:59:04 +00:00
KevinMidboe 784aa2616a Fetchall gets docstring 2018-11-12 01:20:18 +01:00
KevinMidboe 7cb55ce054 Fetchall uses promises smarter. Now the utils functions also return promises to be able to nicely chain the steps a request needs to go through. Promise all lets us wait for all items return in the map function. Without the map function would return immidiately and resolve before the map operation completed. 2018-11-10 20:26:28 +01:00
KevinMidboe 87eb6de802 🔨 test now requests with id in body not in query params. 2018-11-10 01:57:19 +01:00
KevinMidboe 840816c930 request returns all requested items. Optional sort, query and filter params. 2018-11-10 01:50:24 +01:00
KevinMidboe 91d238de7c Request id is now passed as body param. Database default timestamp value changed to epoch time. 2018-11-09 22:13:00 +01:00
KevinMidboe 0ac17d3d0a Removed unused const declaration. 2018-11-01 00:24:47 +01:00
KevinMidboe 87c76e3f1d Tests now suppoer the new list endpoints. Also updated response for interstellar query (movieInfo). 2018-11-01 00:18:54 +01:00
KevinMidboe e64c4d5d01 Lists are now reachable by movie or show / listname. Endpoints added & removed outdated comments. 2018-11-01 00:17:51 +01:00
KevinMidboe 22e57c03de Controller for movie and shows. Each have multiple small export functions; one for each list search type 2018-11-01 00:16:56 +01:00
KevinMidboe d80386da40 Implementing lists lookups for movie and shows. Add new cachetags for the lists & created a helper function for returning response with convertFunction as parameter. 2018-11-01 00:15:49 +01:00
KevinMidboe e7c66af3f6 Merge branch 'master' into api/v2 2018-10-30 21:02:47 +01:00
KevinMidboe 8ece7b84c4 test configuration also gets plex ip parameter. 2018-10-30 20:38:05 +01:00
KevinMidboe 4250b1bd17 request endpoint finds type by body not query. Better error handling on what goes wrong if incorrect type or missing body parameter. 2018-10-30 20:34:26 +01:00
KevinMidboe 7e46d32e30 More unit tests for checking correct definition of movie. Changed some test to match update functions in tmdb. 2018-10-30 20:32:55 +01:00
KevinMidboe 5a48158f07 Request now happens at /request with id parameter and query for type selection. Only allows movie or show type and is static set in the controller. AddRequest adds tmdb item to database with time of request. 2018-10-30 19:20:52 +01:00
KevinMidboe 161a466ab7 Rewrote how local plex library is indexed and what it returns. After searching plex the response is separated into three classes by types (movie, show & episode). Plex also has a function for inputing a (tmdb)movie object and searching for matches of name & type in plex. If a match the object matchedInPlex variable is set to true. 2018-10-29 21:01:16 +01:00
KevinMidboe 8f5bd44e4d Added endpoint for new plex search. 2018-10-29 20:57:22 +01:00
KevinMidboe 5d8869e042 Rewrote every function for searching and looking up items from tmdb library. Now there are separate functions for the four categories of search and three for info (multi, movie, show & person). Each function now has its own endpoint and matching controller. Converting tmdb results into a class has been alterted from using three classes; movie, show & person, and each have each their own convertTmdbTo function. Now the structure of the three types are more structured and no longer a single "seasoned" class object. 2018-10-29 20:55:18 +01:00
KevinMidboe 90b8ee005e Changed moviedb package to my own fork of it. The old package had vulnerabilities and needed updating. 2018-10-29 20:49:21 +01:00
KevinMidboe 1b0525063f New parameter in config and added axios package for new plex connect command. 2018-10-29 20:47:57 +01:00
KevinMidboe 41d6bba743 v2 endpoints added for clearer intent in endpoints. Two new controller categories; info and search. 2018-10-28 12:21:47 +01:00
KevinMidboe 8977a4b195 Merge pull request #109 from KevinMidboe/package/upgrade
Changed moviedb node package to my own fork (km-tmdb) with updated to…
2018-10-26 01:01:43 +02:00
KevinMidboe 7e0da028de Imported new version of moviedb package 2018-10-26 00:59:22 +02:00
KevinMidboe 2250cf2c4b Changed moviedb node package to my own fork (km-tmdb) with updated to vulnerability in the superagent package 2018-10-26 00:20:37 +02:00
KevinMidboe b2bd7b6a1f Update README.md 2018-08-27 11:58:16 +02:00
KevinMidboe a2ad7f5628 Removed old content 2018-08-27 11:56:28 +02:00
KevinMidboe f85d31991f Removed versioneye badge 2018-08-27 11:54:48 +02:00
KevinMidboe 08dc2153ae Updated build config for codeclimate test reporting. 2018-08-26 10:56:50 +02:00
KevinMidboe bc64e69b3e Fixed syntax error. 2018-08-13 00:01:51 +02:00
KevinMidboe a29bca7361 Controller now expects three parameters for posting to addMagnet; magnet, name and tmdb_id. 2018-08-12 23:59:26 +02:00
KevinMidboe d84aa5f173 Merge branch 'master' of github.com:KevinMidboe/seasonedShows 2018-08-12 23:55:40 +02:00
KevinMidboe 48ebd398bc Changed name values of tables. 2018-08-12 23:50:07 +02:00
KevinMidboe 1b95103acd Removed test that caused breaking changes. 2018-08-12 23:31:18 +02:00
KevinMidboe 6a1d6687eb Updated gitmodules 2018-07-28 19:00:32 +02:00
KevinMidboe e849864bc2 Added delugeClient to gitmodules. 2018-07-28 18:55:39 +02:00
KevinMidboe ecc2a67d48 Updated readme for cloning requrse submodules. 2018-07-28 17:55:28 +02:00
KevinMidboe bfe0d55f71 Excetion node-pre-gyp failed for version check for sqlite3. The package was set to static version and needed a patch because of errors caused by newer versions of node. 2018-07-28 17:51:59 +02:00
KevinMidboe 634d4513eb Merge pull request #106 from KevinMidboe/remove_verified
Remove verified
2018-07-28 16:17:43 +02:00
KevinMidboe 0a1276a474 Merge pull request #105 from KevinMidboe/plex_api_update
Plex api update
2018-07-28 16:17:33 +02:00
KevinMidboe 3a34d8995e Merge pull request #104 from KevinMidboe/misc
Misc
2018-07-28 16:15:18 +02:00
KevinMidboe 918e629a06 Merge pull request #103 from KevinMidboe/stray_fixes
Stray fixes
2018-07-28 16:15:03 +02:00
KevinMidboe 7dd016a56e Changed the python run variable for stray eps. 2018-07-28 16:05:22 +02:00
KevinMidboe c10bbcf518 Added update function to package.json fole. 2018-07-28 15:58:02 +02:00
KevinMidboe 3402a52633 Change to log the parent name of the element instead of the name of file. 2018-07-28 10:50:57 +02:00
KevinMidboe 86e9188a5c The api from plex has changed. This reflects the changes from Video to metadata in the api url. 2018-07-28 10:46:25 +02:00
KevinMidboe 8918b7906e Merge branch 'remove_verified' of github.com:KevinMidboe/seasonedShows into HEAD 2018-05-13 19:21:52 +02:00
KevinMidboe 7e028a461d Merge branch 'master' of github.com:KevinMidboe/seasonedShows into HEAD 2018-05-13 19:21:14 +02:00
KevinMidboe fe5f0c815e Deluge is now imported and a Deluge class is created and remove function called for name of torrent that is being verified. 2018-05-13 19:18:45 +02:00
KevinMidboe d02e79e59e Merge pull request #100 from KevinMidboe/linting
Linting
2018-05-09 11:00:02 +02:00
KevinMidboe 5b49216c9d Fixed linting issues for json objects and tailing semicolon. 2018-05-09 10:52:08 +02:00
KevinMidboe 657ab10034 Removed unused comments. 2018-05-06 18:36:26 +02:00
KevinMidboe ed07c77b13 Updated files with tripple === and some linting issues. 2018-05-06 18:23:29 +02:00
KevinMidboe 4fe85d9fae Change the ordering of user requests to be newest-first. 2018-05-06 18:15:54 +02:00
KevinMidboe b99b5b32ec Updated to use abspath of the file not the call location. 2018-05-06 18:09:13 +02:00
KevinMidboe e8058c5e4c Update README.md 2018-04-19 21:13:14 +02:00
KevinMidboe 7332b7d474 Update README.md 2018-04-19 20:22:26 +02:00
KevinMidboe 7980f14426 Update README.md 2018-04-19 20:21:44 +02:00
KevinMidboe 65540fafbd Merge pull request #99 from KevinMidboe/dependency_update
Dependency update
2018-04-10 11:29:56 +02:00
KevinMidboe 64ede43dec Added custom rule for object-shorthand and comma-dangle. 2018-04-08 11:58:16 +02:00
KevinMidboe bed12cff72 Set branch to master for the submodule torrent_search 2018-04-08 11:50:15 +02:00
KevinMidboe 59e7f96643 Updated submodule torrent_search with new commits. 2018-04-08 11:43:12 +02:00
KevinMidboe 71e9a5a46e Merge pull request #98 from KevinMidboe/strayParser_fix
Updated when parsing for show name allow names with numbers.
2018-04-06 16:27:42 +02:00
KevinMidboe fce6dc7658 Updated when parsing for show name allow names with numbers. 2018-04-06 16:22:58 +02:00
KevinMidboe baff59181c Update README.md 2018-04-04 23:22:18 +02:00
KevinMidboe 0592cca16b Updated string_decoder. 2018-04-04 23:08:00 +02:00
KevinMidboe e4d5f5085c Updated yarn lockfile. 2018-04-04 14:41:01 +02:00
KevinMidboe 66a2a06f9b Compile error, if-statement. 2018-04-03 23:02:54 +02:00
KevinMidboe e984feeb8d Would fail if the parent was not a directory. 2018-04-03 23:00:14 +02:00
KevinMidboe 490d015f80 Update README.md 2018-03-30 15:31:15 +02:00
KevinMidboe f1cc2c4ebe Merge pull request #97 from KevinMidboe/coverage
Added coverage stats with nyc and coveralls
2018-03-22 18:30:12 +01:00
KevinMidboe 2f4421d9e0 Merge branch 'coverage' of github.com:kevinmidboe/seasonedShows into coverage 2018-03-22 18:25:01 +01:00
KevinMidboe 92cc094787 Removed old coverage script and updated travis config. 2018-03-22 18:24:42 +01:00
KevinMidboe f30b46c384 Update README.md 2018-03-22 17:20:14 +01:00
KevinMidboe d9f679603a Command in travis config was doc, not cov. 2018-03-22 17:17:23 +01:00
KevinMidboe 64bd9d1e14 Added support for travis upload test coverage to coveralls. 2018-03-22 15:09:59 +01:00
KevinMidboe 721826d454 Re-added request and removed superagent. 2018-03-22 13:24:57 +01:00
KevinMidboe 242fe3515c Removed and moved some dep to dev. 2018-03-22 13:23:30 +01:00
KevinMidboe ccf40d2161 Merge pull request #96 from KevinMidboe/snyk-fix-6de5e99f
[Snyk] Fix for 42 vulnerable dependency paths
2018-03-22 12:59:36 +01:00
KevinMidboe 832b8ba539 Added license and description to package.json. Also updated packages node packages. 2018-03-22 12:56:04 +01:00
snyk-bot 0477e49eca fix: seasoned_api/package.json to reduce vulnerabilities
The following vulnerabilities are fixed with an upgrade:
- https://snyk.io/vuln/npm:qs:20170213
- https://snyk.io/vuln/npm:qs:20140806
- https://snyk.io/vuln/npm:qs:20140806-1
- https://snyk.io/vuln/npm:mime:20170907
- https://snyk.io/vuln/npm:mime:20170907
- https://snyk.io/vuln/npm:fresh:20170908
- https://snyk.io/vuln/npm:debug:20170905
- https://snyk.io/vuln/npm:ms:20170412
- https://snyk.io/vuln/npm:qs:20170213
- https://snyk.io/vuln/npm:negotiator:20160616
- https://snyk.io/vuln/npm:ms:20151024
- https://snyk.io/vuln/npm:debug:20170905
- https://snyk.io/vuln/npm:ms:20170412
- https://snyk.io/vuln/npm:ms:20151024
- https://snyk.io/vuln/npm:hoek:20180212

Latest report for kevinmidboe/seasonedshows:seasoned_api/package.json:
https://snyk.io/test/github/kevinmidboe/seasonedshows?targetFile=seasoned_api/package.json
2018-03-22 11:34:42 +00:00
KevinMidboe 451b67630a Merge pull request #95 from KevinMidboe/docs_testing
Improved testing, more linting and added paging to request/all endpoint
2018-03-21 23:53:29 +01:00
KevinMidboe 096bbdf085 Merge branch 'master' into docs_testing 2018-03-21 23:51:31 +01:00
KevinMidboe e914e4ab45 Added ORDER BY date that was missing in sql stmt 2018-03-21 23:44:59 +01:00
KevinMidboe c1461e1f41 Merge pull request #93 from KevinMidboe/travis
Updated travis to pull submodules
2018-03-21 14:43:36 +01:00
KevinMidboe 91bf2c1e2a Updated travis to pull submodules 2018-03-21 14:30:26 +01:00
KevinMidboe da3df383ed Update gitmodule torrent_search url to be https 2018-03-21 14:29:51 +01:00
KevinMidboe 9816b978d3 Updated demo on readme 2018-03-21 12:13:57 +01:00
KevinMidboe 8e22b0f6ea Updated fixtures. 2018-03-20 21:18:11 +01:00
KevinMidboe 18359f442c Mapped results in tmdb now returns the complete json object so not needed to be created before sent. When getting all requested movies and shows it is now possible to only get one page at a time. 2018-03-20 21:17:41 +01:00
KevinMidboe 42b8b5ea0e Removed a ) 2018-03-20 20:15:28 +01:00
KevinMidboe 996295b1fe Removed the id parameter, not used. 2018-03-20 20:14:44 +01:00
KevinMidboe 1b08c8d3d1 Now when running tests we use a separate config file test.json 2018-03-20 20:14:15 +01:00
KevinMidboe 9e145f7068 When testing we dont have api access so we create cache entries before each request. The cache key changed so this is now updated in the tests and tmdb. 2018-03-20 20:12:49 +01:00
KevinMidboe 7051edb212 Added unit tests for testing config file for correct values and if not check the env variables. 2018-03-20 19:25:35 +01:00
KevinMidboe 0581813ee3 Merge pull request #92 from KevinMidboe/api_filterBug
Fixes api not filtering requests/all query when filtering
2018-03-20 13:17:17 +01:00
KevinMidboe edf1de223e When filtering the request on status the sql query for the db did not sort DESC. 2018-03-20 13:11:26 +01:00
KevinMidboe 7595b6df25 Update README.md 2018-03-19 17:03:03 +01:00
KevinMidboe 04ec0e0785 Update README.md
Added snyk badge
2018-03-19 17:02:02 +01:00
KevinMidboe b08dc899c3 Update ISSUE_TEMPLATE.md 2018-03-19 16:32:03 +01:00
KevinMidboe aafe524681 Merge pull request #90 from KevinMidboe/testing
Testing
2018-03-19 16:28:44 +01:00
KevinMidboe 40b7ceda5f The name of the teardown.sql file was renamed, reverted to correct name. 2018-03-19 16:23:58 +01:00
KevinMidboe 8850f06e3e Updated travis config. 2018-03-19 16:15:31 +01:00
KevinMidboe 972dcf4aab Merge pull request #84 from KevinMidboe/api_request
Api request
2018-03-19 15:46:18 +01:00
KevinMidboe 8e0eff7120 Merge branch 'master' into api_request 2018-03-19 15:46:07 +01:00
KevinMidboe e092eae60a The retrieveHash and changePassword functions now return promises. 2018-03-19 15:43:48 +01:00
KevinMidboe 424cf0985d Added test for registering when user is already registered. 2018-03-19 15:43:00 +01:00
KevinMidboe ed5d302820 Stricter when running test, now we wait until resetdatabase and createuser are done in in the before function before running the tests. 2018-03-19 15:27:11 +01:00
KevinMidboe 559ef1bc11 Before we create a db instance we check for testing flag is set and then use the :MEMORY: database for a anonymous db instance. 2018-03-19 15:25:53 +01:00
KevinMidboe 9645fba4ef Removed connect, connection is now a new sqlite instance, async run function, check for errors when executing and setup and teardown now return promises. 2018-03-19 15:24:42 +01:00
KevinMidboe e27d5c7084 We use the db instance globally with establisheddatabase. 2018-03-19 15:23:54 +01:00
KevinMidboe 4ab3946181 Added testing variable, this uses :MEMORY: database when running tests. 2018-03-19 15:22:33 +01:00
KevinMidboe 3a508b9843 Because we sort requests by date we want to use a finer measurement of time to sort more correctly. 2018-03-09 13:37:23 +01:00
KevinMidboe d8078570eb Changed the sort order when selecting all from requests. 2018-03-09 13:36:01 +01:00
KevinMidboe 47b499c072 Jackett sometimes returns a link to self that redirects to a magnet. The response is now parsed for if it is a link and gets the url it redirects to. 2018-03-08 16:35:17 +01:00
KevinMidboe a69e2d9bbb Merge pull request #88 from KevinMidboe/submodules
Submodules
2018-03-08 14:31:21 +01:00
KevinMidboe 72a7a7e00d Changed path to new submodule location. 2018-03-08 14:25:28 +01:00
KevinMidboe 50d3135a81 Moved submodule to parent directory. 2018-03-08 14:15:25 +01:00
KevinMidboe 9e557759ce deleted old submodule app. 2018-03-08 14:07:57 +01:00
KevinMidboe 1806f89e2b Imported submodule from scratch again. 2018-03-08 13:59:43 +01:00
KevinMidboe 9140fa37eb Deleted gitmodules and include folder. 2018-03-08 13:57:06 +01:00
KevinMidboe 6c062f5fc1 Changed gitmodules config file and moved the submodule from app to a new directory inculde. Also changed default search site to jackett. 2018-03-08 13:48:52 +01:00
KevinMidboe 3abd7ddc61 Fixed merge conflict to newer SQL statements 2018-03-08 10:38:56 +01:00
KevinMidboe 6a7b427ef3 Changed cache TTL to 3 hours. 2018-03-08 10:36:13 +01:00
KevinMidboe 8fbd0eb9d8 Also unpacks the value of username before passing it to searchHistory. 2018-03-08 10:33:04 +01:00
KevinMidboe 127db8b2fe Errors when we have a user that was not signed in. Fixed to unpack the username value first, then pass it to the function. 2018-03-08 10:32:19 +01:00
KevinMidboe 13401a318a History was not wokring because upgrade to sqlite3. Now we do a stateless call to the database and reolve or reject the response. 2018-03-08 10:30:39 +01:00
KevinMidboe 72847efb93 Update ISSUE_TEMPLATE.md 2018-03-08 02:27:17 +01:00
KevinMidboe 7801ea5d05 Create ISSUE_TEMPLATE.md 2018-03-08 02:26:02 +01:00
KevinMidboe 37474de848 Changed to resolve and reject the result from tmdb. And first get the username before sending user to another functoin. 2018-03-07 18:14:07 +01:00
KevinMidboe 1a4aefda14 Get the user variable from req not req.headers 2018-03-07 18:12:21 +01:00
KevinMidboe 40b539db68 Reversed the sorting direction. 2018-03-07 15:37:39 +01:00
KevinMidboe 453727db1b Fixed issue where the user was not being saved alongside the request. Also fixed what the list of requests are sorted by. 2018-03-07 15:35:55 +01:00
KevinMidboe ab2d3c6756 Renamed function name and now returns 500 error when a problem is found. 2018-03-07 14:57:18 +01:00
KevinMidboe 69e694493c updating submodule to latest 2018-03-07 09:58:54 +01:00
KevinMidboe 27c95375e2 Reverted back to only be authenticated beacuse something wrong with getting the correct user. Will replace with admin check as middleware when fixed. 2018-03-07 01:37:42 +01:00
KevinMidboe ba96e27c43 Alongside the token the value of admin state is also sent. 2018-03-07 01:35:40 +01:00
KevinMidboe 858f3f5d57 Can now get user admin status. 2018-03-07 01:30:28 +01:00
KevinMidboe 59b206bf2d Forgot a leading / 2018-03-06 22:13:53 +01:00
KevinMidboe fac9e0e425 Added a endpoint to fetch a random emoji. 2018-03-06 22:02:32 +01:00
KevinMidboe e65d4125b9 Trying to sort on the wrong column name. 2018-03-06 21:27:31 +01:00
KevinMidboe 354b833375 Removed console log of table data. 2018-03-06 21:24:18 +01:00
KevinMidboe cffba202b0 Now the server does not crash when unable to search plex. 2018-03-06 19:33:22 +01:00
KevinMidboe d3b4a34298 Re-added filter options for later use. 2018-03-06 19:32:51 +01:00
KevinMidboe 164191f89d Fixed indentation and updated sqlite to sqlite3! 🚀 2018-03-06 19:32:21 +01:00
KevinMidboe 02c36d041a Removed console.log and fixed docstring comment. 2018-03-06 19:30:11 +01:00
KevinMidboe 404c200474 Removed unused mail conf and added a function for getting all requested elements by loggedInUser. 2018-03-06 19:29:21 +01:00
KevinMidboe aa0094762b Now save only the username of the user in search history. 2018-03-06 19:27:18 +01:00
KevinMidboe 7c4aeb48ab When registering a token is also returned so we dont need to sign in after registration. 2018-03-06 19:26:28 +01:00
KevinMidboe ca146d77bc Fixed error message 2018-03-06 19:25:33 +01:00
KevinMidboe cede376349 Was not using the registered loggedInUser variable, but one passed by client 2018-03-06 19:24:58 +01:00
KevinMidboe e11dfc7712 Renamed class name. 2018-03-06 19:23:11 +01:00
KevinMidboe 1a693ef92f Our new controller for getting a users requested items. 2018-03-06 19:21:55 +01:00
KevinMidboe 2df734bb32 ADded endpoints for checking search history and the items a user has requested, both require authentication. Changed that searching for torrents now require being admin not just signed in. 2018-03-06 19:20:32 +01:00
KevinMidboe 6619184a45 When creating a account the email field is now optional. 2018-03-06 19:18:51 +01:00
KevinMidboe 34a97c069b Updated from sqlite to sqlite3 now all functions are async so we wait for response. 2018-03-06 19:16:10 +01:00
KevinMidboe e6a8515432 Now we can check if user is admin. This has to be set manually and now only is used for fetching torrents. 2018-03-06 19:12:21 +01:00
KevinMidboe 08b373cba0 Response results are now saved in the object results instead of torrents 2018-02-26 19:58:54 +01:00
KevinMidboe ec0923f1c0 Can now filter the response by status. 2018-02-26 17:17:37 +01:00
KevinMidboe 4bc94ae3b7 When a element is not found in plex it now still gets a matchedInPlex value, now False when not found. 2018-02-26 16:59:56 +01:00
KevinMidboe bf4cf8bef1 Merge pull request #83 from KevinMidboe/api
Api
2018-02-07 15:56:40 +01:00
KevinMidboe 1d8a72429d Merge branch 'api' of github.com:KevinMidboe/seasonedShows into api 2018-02-07 15:53:52 +01:00
KevinMidboe 528cbed30f Wasn't getting all the items because some elements do not have a media_type 2018-02-07 15:53:27 +01:00
KevinMidboe 7ee3ace83b Removed unused semicolon 2018-02-07 15:44:24 +01:00
KevinMidboe ba69893e21 Removed static function. 2018-02-07 15:44:04 +01:00
KevinMidboe ad4fa9d95a Fixed issue with types not being upheld when converting from tmdb objects. 2018-02-07 15:40:39 +01:00
KevinMidboe 28a731efbf Merge pull request #82 from KevinMidboe/testing
Testing
2018-02-07 14:41:05 +01:00
KevinMidboe 878c71ef4a Merge pull request #81 from KevinMidboe/frontend
Frontend
2018-02-07 14:38:16 +01:00
KevinMidboe bd1e3c4455 Added test for checking popular shows list in tmdb. 2018-02-07 14:35:36 +01:00
KevinMidboe 1be8bc0319 Merge branch 'master' of github.com:kevinmidboe/seasonedShows 2018-02-07 14:25:31 +01:00
KevinMidboe f0476b2890 Merge pull request #80 from KevinMidboe/linting
Linting
2018-02-07 14:24:43 +01:00
KevinMidboe 72294e19c5 Merge pull request #79 from KevinMidboe/api
Api
2018-02-07 14:22:50 +01:00
KevinMidboe 30b494e356 The id used for searching was somehow not a movies id and returned false. 2018-02-07 14:20:34 +01:00
KevinMidboe b037cb23a2 Updated test to reflect new tmdb response. 2018-02-07 14:16:45 +01:00
KevinMidboe 98b10aa693 Changed the api endpoint to one that requires to be logged in. 2018-02-07 14:15:10 +01:00
KevinMidboe 3ca3c06824 Removed static tag in tmdb functions. 2018-02-07 14:13:50 +01:00
KevinMidboe f6c504223a Now yarn lint lints all files in src not only webserver. 2018-02-07 13:53:15 +01:00
KevinMidboe 93e1ef6d99 Linted app and server.js files. 2018-02-07 13:52:39 +01:00
KevinMidboe 58449fc753 Linted all middleware. 2018-02-07 13:52:08 +01:00
KevinMidboe 34982c14fe Linted all controllers 2018-02-07 13:51:57 +01:00
KevinMidboe 81aeed86ef Linted all pirate, git, tmdb and searchHistory scripts. 2018-02-07 13:51:42 +01:00
KevinMidboe a8ec7acff5 Linted all seasoned scripts. 2018-02-07 13:51:02 +01:00
KevinMidboe 74d143775b Linted all user files. 2018-02-07 13:50:16 +01:00
KevinMidboe dda1db6c5f Linted all plex scripts. 2018-02-07 13:50:05 +01:00
KevinMidboe 97217a2826 Linted all media_classes 2018-02-07 13:49:37 +01:00
KevinMidboe 272300249e Linted all database files. 2018-02-07 13:49:22 +01:00
KevinMidboe fe1ad2b1ad Linted all config files. 2018-02-07 13:49:11 +01:00
KevinMidboe 444295d5d1 Updated our eslint config file. Still extends airbnb, but have changed indent rule and dropped some other rules. 2018-02-07 13:47:11 +01:00
KevinMidboe a40d4f7cd5 Removed movie and show in replacement with a common media.js and tmdb and plex that extend this media class. 2018-02-07 13:44:26 +01:00
KevinMidboe 6fd65fdb23 Refactoring where now we can search lists and lookup movies or shows by using the new tmdbMethod function which takes the api endpoint variable and type to return the appropriate tmdbMethod name for the matching api call to tmdb. Removed a lot of the globally defined variables. 2018-02-07 13:42:46 +01:00
KevinMidboe 7e874bbc9d Now our convert scripts use our new plex and tmdb classes. Also compacted and abstracted a lot of the code here. 2018-02-07 13:39:45 +01:00
KevinMidboe 6564948af8 Split up our plex and tmdb classes into more logical files with a common media class that it extends off of. 2018-02-07 13:38:25 +01:00
KevinMidboe fafb5c30e0 Merge pull request #76 from KevinMidboe/api
Api
2018-02-05 22:57:22 +01:00
KevinMidboe af64a4e28a Removed and clean up function and formatting. 2018-02-05 22:48:41 +01:00
KevinMidboe 3c263a1ae7 Now we have brand spanking new functions for lookup and searching. The idea here is to match tmdb items to plex counterparts. 2018-02-05 22:48:00 +01:00
KevinMidboe d8b2cef18f Removed filtering of items when searching tmdb. Also added total_results object to all return statements. 2018-02-05 22:46:10 +01:00
KevinMidboe 6f626d410d Rewrote most of this class. Now we have helper functions for getting info about items in plex. Used function is inPlex where we can input a tmdb item and it append if the item exsists in plex. 2018-02-05 22:44:57 +01:00
KevinMidboe e6b81ef0dd Merge pull request #75 from KevinMidboe/api_fixes
Api fixes
2018-02-05 15:02:10 +01:00
KevinMidboe cfb878fa64 Fixed issue where a item would not be returned when nothing was found matching query in plex. Also moved checkId up in the request. 2018-02-05 14:55:49 +01:00
KevinMidboe 44878a295c Merge branch 'api_fixes' of github.com:kevinmidboe/seasonedShows into api_fixes 2018-02-05 14:25:25 +01:00
KevinMidboe 9b0fa88a72 Changed docstring and removed the unused port variable. 2018-02-05 14:25:13 +01:00
KevinMidboe 80582f0022 Changed so that tmdb now gets its own type, tmdbtype 2018-02-05 14:23:40 +01:00
KevinMidboe 77d7167fcb Changed so that one must not be auth to get requested elements. This might change. 2018-02-05 14:22:44 +01:00
KevinMidboe 39cff99c10 Merge branch 'master' of github.com:kevinmidboe/seasonedShows 2018-02-05 14:04:11 +01:00
KevinMidboe 3edf7d43cb Changes reflecting update in api for naming scheme. 2018-02-05 13:57:57 +01:00
KevinMidboe 4c5384ed2e We can now sort filesizes that have a human size name and not in bytes. These values will be converted and saved as byte size. 2018-02-05 13:57:27 +01:00
KevinMidboe 2c36b7eb30 Api changed that a item has title, not name. These changes are reflected here. Also removed a console.log 2018-02-05 13:56:33 +01:00
KevinMidboe 3d2fb6e28c Api changed that a item has title, not name. These changes are reflected here. 2018-02-05 13:54:02 +01:00
KevinMidboe 952fe7220e Reflected changes in api, now we receive a results json object, not requestedItems. 2018-02-05 13:53:17 +01:00
KevinMidboe eedfe4cf3f Changed schema to reflect changes movie title from name to title. 2018-02-05 13:48:16 +01:00
KevinMidboe cfedd4e9c8 Added ability to check if a element is previously been requested. Also changed the schema of db to store movies poster as poster_path and background as background_path. 2018-02-05 13:47:10 +01:00
KevinMidboe cefbb5e41c Added length of elements in return statement as total_results in api response. 2018-02-05 13:44:13 +01:00
KevinMidboe 89ec9d496b Changed the ip addres of plex server 2018-02-05 13:42:35 +01:00
KevinMidboe bd0c7b8ab5 Now also has size of the existing file. 2018-02-05 13:41:41 +01:00
KevinMidboe 0c3ad3a95e Fixed a issue where the tmdb type was not being selected correctly. (movie/show) 2018-02-02 11:51:13 +01:00
KevinMidboe c0c9587066 Update README.md 2018-01-09 23:45:01 +01:00
KevinMidboe 6f6b38c23d Update README.md 2018-01-09 23:43:44 +01:00
KevinMidboe 3639105240 Update README.md 2018-01-09 23:43:32 +01:00
KevinMidboe 0b42cf7f12 Because the content on the landing page felt static I added a random function to pick between the four list types. 2018-01-09 23:12:27 +01:00
KevinMidboe 4f98f2fd23 Changed the distance between the search bar and the content to be a little less and added a shadow around the search bar to make it pop a bit more. 2018-01-09 23:08:59 +01:00
KevinMidboe 0addc76126 Changed the positioning of the type icon in the header and forgot the set the style of the movie info header to the correct css element, this is fixed now. 2018-01-09 23:03:53 +01:00
KevinMidboe 5d0b766d3d Fixed conflict with what site should be searched. 2018-01-09 22:53:34 +01:00
KevinMidboe 8285f14bfa Merge branch 'master' of github.com:kevinmidboe/seasonedShows 2018-01-09 22:51:26 +01:00
KevinMidboe 159de6527c Now checks the return status for if it was a success or not. 2018-01-09 22:49:59 +01:00
KevinMidboe fe020faf21 Added underline to header and align text the left. 2018-01-09 22:49:00 +01:00
KevinMidboe 9d2b904260 Changed the app search site from jackett to piratebay and added some better handling of errors, now the server does not instantly crash on a error. 2018-01-09 22:47:59 +01:00
KevinMidboe a4146fe055 Merge branch 'master' of github.com:KevinMidboe/seasonedShows 2018-01-09 22:47:19 +01:00
KevinMidboe fd53150054 Changed the python path to linux requirments 2018-01-09 22:46:52 +01:00
KevinMidboe 59fe9108ae Torrent table is the component that displays the response from our torrent query search. Here we can sort the table on the columns and also filter with a query. 2018-01-09 22:44:02 +01:00
KevinMidboe 0e85046751 Update README.md
Changed travis build badge to look at master instead of testing branch.
2018-01-09 17:06:16 +01:00
KevinMidboe aa1866bbd9 Fixed merge conflict regarding version of express 2018-01-09 17:05:02 +01:00
KevinMidboe bc0211e34f Moved eslint to devDep and updated express to 4.11 2018-01-09 17:02:03 +01:00
KevinMidboe 34a0c7f958 Merge pull request #72 from KevinMidboe/testing
Testing
2018-01-09 16:56:45 +01:00
KevinMidboe f6bd547701 Merge branch 'master' of github.com:kevinmidboe/seasonedShows 2018-01-09 16:55:09 +01:00
KevinMidboe e0da166406 Our request page is redesigned with a new header and search bar. It also has a better way of redrawing when the page size is changed to a mobile device. This is not very reactonic, but this file contains the intire search and request logic of the page, but this means we still have things todo :) 2018-01-09 16:53:41 +01:00
KevinMidboe 65bce27a2b Added our torrent_search repo as a submodule in our app folder. This will need to be set up automatically with build setting when the project is to be downloaded. 2018-01-09 16:49:24 +01:00
KevinMidboe e9f6f3a656 We added a new python app for fetching torrents. The api is now changed to use the new project. IMPORTANT that a check for if jackett is set up is done, if else this is will not run correctly. 2018-01-09 16:44:10 +01:00
KevinMidboe b8fb718540 Added NODE_ENV environmental variable production when building. 2018-01-09 16:40:49 +01:00
KevinMidboe 21a94d88a9 Added a seperate component for the info button. 2018-01-09 16:39:16 +01:00
KevinMidboe 519eba8fda Added some css for admin component that defines the styles for parent elements of imported components. 2018-01-09 16:36:24 +01:00
KevinMidboe 10ef1bfa69 This receives a response of list of torrents as props and displays them in a table view. From here we can send to download and also filter the results by query. 2018-01-09 16:33:46 +01:00
KevinMidboe da54ad0066 Changed import name of torrentTable to correct TorrentTable 2018-01-09 16:30:38 +01:00
KevinMidboe c8a2ea9907 This now contains the button and request function for getting torrents matching a serach query given by the name of the elment. The result is passed to the child component torrentTable which renders the result in a table view. 2018-01-09 16:28:34 +01:00
KevinMidboe 262093d196 Completely re-wrote our sidebar. Now we can filter by search and category on top and a list of all elements are displayed below in a scrollable list. Each element has a indicator showing if the item is requested, downloading or downloaded. There are several generator functions that are to be pulled out in their seperate components. 2018-01-09 16:25:52 +01:00
KevinMidboe e64d88bfdf The stylesheet for all of requestInfo page. 2018-01-09 16:20:41 +01:00
KevinMidboe f7c6f6603b Renamed style variable to match the imported stylesheet. 2018-01-09 16:20:18 +01:00
KevinMidboe 279b004aad We re-wrote most off the look and feel of this page. No we get a header that is generated with all the info for a requested element. This has a indicator generated by generateStatusIndicator, showing if the element is requested, downloading or downloaded. Then we have a function for generating the type icon, that is movie/show that is displayed in the header. The summary for a movie might be long so if it is over 180 chars than it is cut and a show more button is displayed. After the info on the item we import PirateSearch that takes a name input and displays a search result for that query in a table view. 2018-01-09 16:19:43 +01:00
KevinMidboe 33cb6f5f09 In admin we added a updatehandler so that a child can update this parent element. Also added our new sidebar for filtering our requested items by query and category. 2018-01-09 16:03:11 +01:00
KevinMidboe 4d99cae74c Pulled our info button out to its own component, changed the size of the poster that is fetched from tmdb from width of 300 to 185 pixels. Also some minor changes to the info displayed, no also has type listed in the object. 2018-01-09 15:52:28 +01:00
KevinMidboe 976d5cf69f TorrentTable gets passed a json list of torrents and this displays it in a table view. 2018-01-02 15:30:23 +01:00
KevinMidboe 76069f4fea Added license badfge 2017-12-21 01:04:00 +01:00
KevinMidboe d92e6d8c78 Psst. Removed console logs from debug hunt. 2017-12-21 01:01:43 +01:00
KevinMidboe 601440d540 Ok. I can explain this bug. It seems commenting out teardown in resetDatabase solves our issue that travis cannot find our teardown script eventough it outputs the exact path it should be at. Will come back to this as it seems it works everywhere else. Good night. 2017-12-21 01:00:47 +01:00
KevinMidboe 7824e8be27 Merge branch 'testing' of github.com:kevinmidboe/seasonedShows into testing 2017-12-21 00:55:05 +01:00
KevinMidboe b4352ad721 Too many ; bro 2017-12-21 00:53:38 +01:00
KevinMidboe 9e160a7a91 Added build status to radme 2017-12-21 00:52:34 +01:00
KevinMidboe 2623649c5d Tried to re-add teardown and updated it to drop only tables that are in the database. Crossing fingers for a second pass. 2017-12-21 00:46:52 +01:00
KevinMidboe 908fca6dd0 Trying to remove teardown between tests. 2017-12-21 00:42:44 +01:00
KevinMidboe e6a34a0503 Back to linux. plz fix plzzzzz 2017-12-21 00:38:46 +01:00
KevinMidboe f0e8f84e12 Building with osx now 2017-12-20 23:20:49 +01:00
KevinMidboe 0459fb9312 More testing for this bug. 2017-12-20 23:18:13 +01:00
KevinMidboe 6a725e3921 Added tons of console.logs to try find out where the problem with travis not being able to read file lies. 2017-12-20 23:06:02 +01:00
KevinMidboe fb22576708 Travis is still not reading our teardown file. 2017-12-20 22:51:08 +01:00
KevinMidboe bced4e052d There was a weird thing with the file permissions so it would not be read by travis. Now it should be fixed 2017-12-20 22:42:53 +01:00
KevinMidboe 54eca33dfa Forgot to update the node version in travis config file. 2017-12-20 22:27:20 +01:00
KevinMidboe 93e43d9954 Added a template development.json file for build purposes. 2017-12-20 22:22:25 +01:00
KevinMidboe fba3845523 Now we check if the values length is 0 not the filters varaible. 2017-12-20 22:17:45 +01:00
KevinMidboe fe64af856e Fixed a old bug. 2017-12-20 22:16:57 +01:00
KevinMidboe 625717f7ad Now when checking config file if the value is blank it checks env variables for a variable defined as uppcase section + option with a separator of _. 2017-12-20 22:16:36 +01:00
KevinMidboe e4573e6808 Forgot to remove path without test scripts yet. 2017-12-20 21:13:24 +01:00
KevinMidboe e19b604a78 Our teardown sql script. 2017-12-20 21:08:35 +01:00
KevinMidboe f6c27482e4 Added script for teardown of database. 2017-12-20 21:07:33 +01:00
KevinMidboe 00ad5cf7a8 Added mocha, istanbul and supertest packages. Added coverange and test script. 2017-12-20 21:07:13 +01:00
KevinMidboe ba33552099 Merge pull request #71 from KevinMidboe/testing
Testing
2017-12-20 20:55:18 +01:00
KevinMidboe 99ddb61c37 Started by adding some system tests for the api. 2017-12-20 20:47:32 +01:00
KevinMidboe 790f5d6588 Added travis support to api. 2017-12-20 20:46:15 +01:00
KevinMidboe a51a78bc71 Merge branch 'master' of github.com:kevinmidboe/seasonedShows 2017-12-03 15:01:50 +01:00
KevinMidboe 317aa69f18 Re-added a CSS class that was removed. 2017-12-02 15:42:23 +01:00
KevinMidboe de90b48430 Update README.md 2017-12-02 15:39:02 +01:00
KevinMidboe 6011615a41 Merge pull request #68 from KevinMidboe/add-metadata-requests
Add metadata requests
2017-12-02 13:23:24 +01:00
KevinMidboe 0338e90bf7 Merge pull request #66 from KevinMidboe/styling
Styling
2017-12-02 13:19:54 +01:00
KevinMidboe cf43500c01 Merge branch 'master' into styling 2017-12-02 13:19:00 +01:00
KevinMidboe c65cdd84b7 Changed our gitignore file for the api folder. 2017-12-02 13:18:20 +01:00
KevinMidboe 7849a8ce3f Fixed syntax error. 2017-12-02 13:17:17 +01:00
KevinMidboe 246abd7020 Renamed the variable name for our stylesheet. 2017-12-02 13:16:57 +01:00
KevinMidboe 580cdc430f Removed a lot of unused css classes. 2017-12-02 13:15:23 +01:00
KevinMidboe 7bde2821d0 Followed with the renaming of MovieObject to now be refered to as SearchObject. Also change the loading animation for InfiniteScroll. It is the same loading animation imported as we have in torrent search. 2017-12-02 13:13:35 +01:00
KevinMidboe 45db534681 When a requested element from the sidebar is selected this is where the detailed info is displayed. Now it is possible to change the status of a item by using the dropdown. This is where piratesearch also gets the query name through props. 2017-12-02 12:17:00 +01:00
KevinMidboe fd0a2c9d50 Renamed the exporting class name to match the file name. 2017-12-02 12:08:47 +01:00
KevinMidboe e55067025e Renamed movieObject to searchObject, same with css. 2017-12-02 12:05:17 +01:00
KevinMidboe 187ac6317e Still a bit messy, but removed unsued sections and redid the naming schema for css items. 2017-12-02 12:01:49 +01:00
KevinMidboe 9fcc82d7cf Put both jsx and js varaibles on same line in config file. 2017-12-02 11:35:39 +01:00
KevinMidboe c11b222ee3 Deleted unused svg image. 2017-12-02 11:35:10 +01:00
KevinMidboe 8695a553d6 Admin page is our landing page for admin panel, now it passes our api response to sidebar and info panel when we get the response back from the api. Some renaming of stylesheet variable. 2017-12-02 11:32:49 +01:00
KevinMidboe 3fe8f46dd4 Viewport changes to limit zoom in on mobile the typing in a search bar. 2017-12-02 11:31:25 +01:00
KevinMidboe 5fb1e7ba2e Sidebar is now redesigned with color indicators, filtering name by search query or filtering status by dropdown. Moved CSS to a separate stylesheet. 2017-12-02 11:20:26 +01:00
KevinMidboe e1fed24fc0 Added a loading image to pirateSearch. Now we get feedback when waiting for response from the api. 2017-12-02 10:39:18 +01:00
KevinMidboe 8542da34cd Added a separate stylesheet for our buttons. 2017-12-02 10:37:31 +01:00
KevinMidboe 7d44f1518f Updated gitignore to ignore conf folder and deleted the contents of conf. 2017-12-01 10:57:28 +01:00
KevinMidboe fa8f82449b Merge branch 'master' of github.com:KevinMidboe/seasonedShows 2017-12-01 10:49:08 +01:00
KevinMidboe f7b772a170 Changed the default cache TTL from 7 days to 2 days. 2017-12-01 10:46:50 +01:00
KevinMidboe d0597b4e1e This is our main focus on changing, this is where we add a background parameter in our database entry. 2017-12-01 10:34:09 +01:00
KevinMidboe db11c968a3 Renamed folder the plural. 2017-12-01 10:28:29 +01:00
KevinMidboe b8647f982d This is where we use our setup function to add tables if none are present in our database file. 2017-12-01 10:27:46 +01:00
KevinMidboe ac6bbe6ac6 Now we can build or database by reading schema files. Also added doc strings to all the functions. This is a much better definition of a database script for our usecases.. 2017-12-01 10:19:22 +01:00
KevinMidboe 2b772e3017 Long overdue, now we define our database schema so we can finally build our database, and not just move it around. 2017-12-01 10:14:22 +01:00
KevinMidboe 57658f10c1 Used the wrong shows.db. Now corrected. 2017-11-28 22:25:06 +01:00
KevinMidboe 62b6f5c8ca Using react-interactive we now have hover animations for our buttons. 2017-11-28 21:02:25 +01:00
KevinMidboe 362e5f54d1 Added react-interactive package to make it easier to handle hover and focus on mobile. 2017-11-28 20:58:45 +01:00
KevinMidboe c5938c3c78 Merge pull request #63 from KevinMidboe/sentry
Sentry
2017-11-28 19:26:18 +01:00
KevinMidboe 2421b04548 Removed last commented line. 2017-11-28 19:25:23 +01:00
KevinMidboe ff78dfbd50 Cleaned up in the main gitignore file and moved seasoned_api related ignore to its child file. 2017-11-28 19:24:47 +01:00
KevinMidboe 36b4f16047 Revert "Removed files."
This reverts commit 7f4b95c8c4.
2017-11-28 19:22:40 +01:00
KevinMidboe 5da57062a7 Added a gitignore for each main folder because we have python based scripts in one folder, javascript/react and javascript/node in the other. 2017-11-28 19:19:50 +01:00
KevinMidboe be4c79ca16 Added env folder to gitignore 2017-11-28 19:10:45 +01:00
KevinMidboe 613b4ecee3 Merge pull request #62 from KevinMidboe/sentry
Sentry
2017-11-08 14:04:37 +01:00
KevinMidboe 7f4b95c8c4 Removed files. 2017-11-04 19:35:06 +01:00
KevinMidboe 0e4f96640c Ommited to entire conf folder in api. 2017-11-04 19:34:28 +01:00
KevinMidboe dc685ebcdd Removed conf files from api. 2017-11-04 19:30:57 +01:00
KevinMidboe 6e9d511b5c Added empty log file and stripped config file. 2017-11-04 19:28:34 +01:00
KevinMidboe 2fc9674548 Added some todos for routers/middleware and setup so that raven will be used for error handling and gets value from config file. 2017-11-04 18:11:44 +01:00
KevinMidboe 38383da1b9 Merge pull request #61 from KevinMidboe/sentry
Setup for sentry.
2017-10-26 15:20:31 +02:00
KevinMidboe 7cce61b3bb Setup for sentry. Need to add __DSN__ value manually. 2017-10-26 15:19:10 +02:00
KevinMidboe 303ec8fa10 Merge pull request #60 from KevinMidboe/lint
Lint
2017-10-26 14:53:06 +02:00
KevinMidboe e417fedece Now follows our airbnb linting schema. 2017-10-26 14:52:00 +02:00
KevinMidboe 6dd9cf7083 Added eslint and set preset to airbnb base. 2017-10-26 14:51:12 +02:00
KevinMidboe 546f33883a Merge pull request #59 from KevinMidboe/conf_webpack
Changed to now use a prod and a dev webpack config. Our prod config n…
2017-10-24 23:02:00 +02:00
KevinMidboe d10b974545 Changed to now use a prod and a dev webpack config. Our prod config now minifies and sets up caching for us. Package json now has build command. 2017-10-24 23:01:44 +02:00
KevinMidboe c6fac11b8d Merge pull request #58 from KevinMidboe/client_styling
Changed the box shadow and margin of result wrapper.
2017-10-23 16:12:57 +02:00
KevinMidboe d6c066266c Changed the box shadow and margin of result wrapper. 2017-10-23 16:12:00 +02:00
KevinMidboe 5c408b826b Merge pull request #57 from KevinMidboe/client_title-position
Changed the title position of our result list.
2017-10-23 16:07:06 +02:00
KevinMidboe a0ce4e07fe Changed the title position of our result list. 2017-10-23 16:06:38 +02:00
KevinMidboe 1b58cb461c Merge pull request #56 from KevinMidboe/client_show-request-user
Now also prints the requestee of a element if it is not null.
2017-10-23 15:50:31 +02:00
KevinMidboe dbd258d9fa Now also prints the requestee of a element if it is not null. 2017-10-23 15:49:54 +02:00
KevinMidboe 1bdf89b69f Merge pull request #55 from KevinMidboe/api_tuning
Lowered the tollerance for what objects we choose to look at by tmdb.…
2017-10-23 15:39:57 +02:00
KevinMidboe 498bd13a1f Lowered the tollerance for what objects we choose to look at by tmdb. That is we only accept items that have some value of vote or popularity. 2017-10-23 15:39:28 +02:00
KevinMidboe 80dcba2fba Merge pull request #54 from KevinMidboe/api_save-user-w-request
Api save user w request
2017-10-23 15:36:28 +02:00
KevinMidboe effa41978e Merge pull request #53 from KevinMidboe/api_search-tmdb-list
Api search tmdb list
2017-10-23 15:35:35 +02:00
KevinMidboe 7069db8901 Now checks for loggedinuser header variable and passes it to sendRequest. 2017-10-23 15:32:49 +02:00
KevinMidboe 81fbc86cad Now loggedinuser cookie variable is sent in our header on each call. 2017-10-23 15:31:41 +02:00
KevinMidboe 6f12d0ca49 Added docstring to new functions in tmdb 2017-10-23 15:14:16 +02:00
KevinMidboe 2d986eb5b3 Removed unused controllers 2017-10-23 15:06:22 +02:00
KevinMidboe 233ad03dd3 Changed so our client now uses our new endpoint for searching lists. 2017-10-23 15:05:42 +02:00
KevinMidboe 40928a6e46 Added docstring 2017-10-23 14:59:58 +02:00
KevinMidboe c48db517ac Created one function for searching for different tmdb lists. This replaces 4 endpoints so we have a single way of searching for lists. 2017-10-23 14:58:52 +02:00
KevinMidboe d836dd01d3 Merge pull request #52 from KevinMidboe/app/search-the-bay
App/search the bay
2017-10-22 18:56:00 +02:00
KevinMidboe da0609d56c Also passes our regex if there is a year now. 2017-10-22 18:54:38 +02:00
KevinMidboe f15df29801 More general re matching for date and also returns empty dict of no candidates are found. 2017-10-22 18:50:50 +02:00
KevinMidboe 4b01387e56 Merge pull request #51 from KevinMidboe/api_cache
Api cache
2017-10-22 18:19:36 +02:00
KevinMidboe 0e3189ff11 Added default cache time to be 1 day 2017-10-22 18:16:39 +02:00
KevinMidboe 1925c2a5e3 Changed the variable for saving to searchHistory to be set when user is not false. 2017-10-22 18:04:20 +02:00
KevinMidboe 7952e6015a Added caching for all tmdb requests. 2017-10-22 17:41:12 +02:00
KevinMidboe 6e95d48f11 Now when searching a loggedinuser header element is passed also. 2017-10-22 17:34:16 +02:00
KevinMidboe d2d791f5b2 added loggedinuser as acceptable header. 2017-10-22 17:31:37 +02:00
KevinMidboe 0e70a10cc2 Opps, start with then. 2017-10-22 17:31:14 +02:00
KevinMidboe 592528dec2 Forgot to change this to point at our new server 2017-10-22 17:30:49 +02:00
KevinMidboe 354e06a963 Removed searchHistory from tmdb class. 2017-10-22 17:03:40 +02:00
KevinMidboe cc8d9f0c50 Added docstring, removed a unused console.log and added better error handling for empty return statement.s 2017-10-22 17:03:16 +02:00
KevinMidboe 8d248135e0 Chagned from using user.username, to just the variable user. 2017-10-22 17:01:58 +02:00
KevinMidboe 6574c087bc Added user history saving and that tmdb now gets to use cache. 2017-10-22 17:01:26 +02:00
KevinMidboe d90197b8b4 Added a cookie variable loggedInUser, to see search history of a user. 2017-10-22 14:58:38 +02:00
KevinMidboe d20ac6470f Was a missing ) 2017-10-22 14:18:38 +02:00
KevinMidboe 8f4e3a24f4 And the last that imports tmdb class. 2017-10-22 14:16:43 +02:00
KevinMidboe f89d81681a Added import for cache to initiate our tmdb class in the correct way, but still not implemented the actual cache lookup. 2017-10-22 14:14:29 +02:00
KevinMidboe 1b29a08ce8 Removed a unused comment 2017-10-22 14:10:25 +02:00
KevinMidboe f57878813d Added Cache file for tmdb queries and controller check the cache and returns it if hit. This is to limit our request to a third party library. 2017-10-22 14:09:57 +02:00
KevinMidboe b82efddeba Now tmdb searches also check cache for a matching query request, if so returns it. 2017-10-22 14:08:36 +02:00
KevinMidboe 6ec6fc771a Merge pull request #49 from KevinMidboe/pirate_bugfixes
Pirate bugfixes
2017-10-21 16:13:36 +02:00
KevinMidboe de33f02aeb Temp here just to be able to add torrents. 2017-10-21 16:12:49 +02:00
KevinMidboe 7d4178a901 Now uses python2, not python3 because of libtorrent library. 2017-10-21 16:12:27 +02:00
KevinMidboe e5b3c8a249 Changed the logging dir to be based on __file__ location. 2017-10-21 16:11:36 +02:00
KevinMidboe 1183126653 Merge pull request #48 from KevinMidboe/api_add-magnet
Api add magnet
2017-10-21 15:39:15 +02:00
KevinMidboe dbcb316bfd Merge pull request #47 from KevinMidboe/client_search-the-bay
Client search the bay
2017-10-21 15:38:31 +02:00
KevinMidboe b6b9c790e9 Errors. Removed a return from within a resolve and a stray { in addMagnet. 2017-10-21 15:33:40 +02:00
KevinMidboe 1425da3090 Missed to add some extra parantheses around our return statement. 2017-10-21 15:31:04 +02:00
KevinMidboe d1d204691a Changed from GET to POST 2017-10-21 15:28:32 +02:00
KevinMidboe 5a24837163 Controller for adding magnets from post body magnet variable. 2017-10-21 15:28:13 +02:00
KevinMidboe 4c7be51a61 Added endpoint for add and now user must be authenticated for both pirate endpoints. 2017-10-21 15:27:21 +02:00
KevinMidboe 3e6fc0960c PirateRepo now has a function for calling our add magnet python script with a magnet passed from the controller. 2017-10-21 15:26:29 +02:00
KevinMidboe 3d73000e92 Displays a button that can be used to fetch search results and then each element has a magnet button to send it back. 2017-10-21 15:16:58 +02:00
KevinMidboe 71e7f46927 AdminRequestInfo is also passed listItemSelected variable 2017-10-21 15:16:15 +02:00
KevinMidboe 5d40bd8e06 Added Pirate Search component. 2017-10-21 15:15:28 +02:00
KevinMidboe 799b8731c5 Merge pull request #46 from KevinMidboe/api/update_requested
Api/update requested
2017-10-21 12:46:32 +02:00
KevinMidboe 74ea05cf17 Fixed error where the was an extra ' character. 2017-10-21 12:45:31 +02:00
KevinMidboe db226d6c95 Now uses a input_dir variable that is can be different than the destination. 2017-10-21 12:44:45 +02:00
KevinMidboe 9b1b041ef5 Now uses ip for other plex server. 2017-10-21 12:43:43 +02:00
KevinMidboe 3223e423b2 Now uses ip for other plex server 2017-10-21 12:43:06 +02:00
KevinMidboe 99eb5512df Merge pull request #45 from KevinMidboe/app_pirate
Added our scripts needed for pirate search, these are files that need…
2017-10-21 12:40:38 +02:00
KevinMidboe 63ba10bc5a Added our scripts needed for pirate search, these are files that need to be imported from the currently in-dev project seasonedParser 2017-10-21 12:39:20 +02:00
KevinMidboe 3c039447f5 Renamed the endpoint to search. 2017-10-21 12:29:28 +02:00
KevinMidboe a6d00714d6 Inputs a query, page and type and will search the bay for the query and return it as a json objec.t 2017-10-21 12:28:21 +02:00
KevinMidboe 67f5cef718 Now makes a async call for python script and returns the ouput of the script when finished as a json object. 2017-10-21 12:27:39 +02:00
KevinMidboe 67d343e446 Added endpoint for searching the bay 2017-10-21 10:01:16 +02:00
KevinMidboe 1f6130a53d Added pirateRepository 2017-10-21 10:00:45 +02:00
KevinMidboe f0fb791e9d Removed unused imports 2017-10-21 10:00:13 +02:00
KevinMidboe 87babed265 Merge pull request #44 from KevinMidboe/client/admin-page
Client/admin page
2017-10-21 09:50:32 +02:00
KevinMidboe a0565f79d2 Changed name of variable element to requestElement and now the link name of a list item is generated by generateListElements and if this element is the one selected make it visually different 2017-10-21 09:48:13 +02:00
KevinMidboe 277105e6df Our selected Request is passed through props, the prop name is now changed from display to selectedRequest. 2017-10-21 09:45:46 +02:00
KevinMidboe 87a2de1d0b Reflects the changes to the url paramater for admin, is now names requestParam. Also the param is passed to Sidebar so that we can highlight the one selected. 2017-10-21 09:44:12 +02:00
KevinMidboe ebfb61d776 Renamed admin param to request from search 2017-10-21 08:22:02 +02:00
KevinMidboe 4cdf368d2c Merge pull request #43 from KevinMidboe/client/admin_page
Client/admin page
2017-10-21 00:16:01 +02:00
KevinMidboe 5aaf0ddf84 Moved Admin and LoginForm to admin folder and deleted app and main in replacement for root. 2017-10-21 00:14:50 +02:00
KevinMidboe 39e363dbf3 Works together with AdminRequestInfo to display info about requested items on the admin page. 2017-10-21 00:12:44 +02:00
KevinMidboe 7b32dfa35a Moved loginform to admin folder 2017-10-21 00:12:09 +02:00
KevinMidboe cd58a830b5 Together with sidebar this loads the info of the selected request item on the admin page. 2017-10-21 00:11:53 +02:00
KevinMidboe d9f432a8de Our new router, which is being directly loaded from index.js 2017-10-21 00:11:24 +02:00
KevinMidboe 2a09f67248 Now index.js uses root instead of app component. 2017-10-21 00:10:51 +02:00
KevinMidboe 65c1f7ad96 Changed from compDidMount to WillMount so it loads before our render function. 2017-10-21 00:09:29 +02:00
KevinMidboe 2fd515d997 Fixed a bug where the json object was not being returned and fixed indentation of fetchJSON function. 2017-10-21 00:08:35 +02:00
KevinMidboe 4297505861 Moved admin to a admin folder and this is now the landing page for all that admin things we want to do. It now just displays the sidebar and adminRequestInfo for a selected item in the sidebar. Also checks if user is logged in before loading view. 2017-10-21 00:06:50 +02:00
KevinMidboe 5bd1041deb Merge pull request #42 from KevinMidboe/client/update_requested
Client/update requested
2017-10-20 18:13:30 +02:00
KevinMidboe e114cff32f This will be handling all http requests. 2017-10-20 18:11:34 +02:00
KevinMidboe cdba914758 Where all our button components will recide. 2017-10-20 18:11:15 +02:00
KevinMidboe 5f13ad202c Added import of fetchJSON function in http 2017-10-20 18:10:53 +02:00
KevinMidboe 8fa7f7f918 Just added a justify content variable 2017-10-20 18:09:43 +02:00
KevinMidboe b2f4151850 Movie object now uses http functions when submitting a post for a movie. 2017-10-20 18:08:31 +02:00
KevinMidboe 45a84cbf85 The admin page that has all the fetched elements now prints all the elements out in a grid. Tried a cool thing on how to render the dropdown for changing the status of a element, but its not working atm. 2017-10-20 17:20:43 +02:00
KevinMidboe 42d0b40825 Does not fit on this branch, but updated the key value, so it is concat of both id and index of the map function for search results. It was prev just the index, and when rapped this hits same value, which is unacceptable by react. 2017-10-07 14:52:49 +02:00
KevinMidboe d9a22f506e When posting a request, the type is also added to the database. 2017-10-06 15:38:22 +02:00
KevinMidboe 307a8352a3 Now takes both id and type because there is two different sets of id pools, and need to select the corrent one. 2017-10-06 15:37:09 +02:00
KevinMidboe 3c372aab92 Added returnstatement to updateRequestedById and fixed the sql query. 2017-10-06 15:19:08 +02:00
KevinMidboe 650c2603e4 Changed the input path of requestRepo 2017-10-06 15:16:45 +02:00
KevinMidboe fb479e7a37 Renamed function in requestRepo to updateRequestedById 2017-10-06 15:14:31 +02:00
KevinMidboe ac38dacedd Changed database table name. 2017-10-06 15:04:50 +02:00
KevinMidboe f54bd0f743 Added a function for updating the status from a given id in requests database. 2017-10-06 15:04:18 +02:00
KevinMidboe 61c242b5eb Passes the id from request parameter and status we want to change to from the request body. 2017-10-06 15:03:47 +02:00
KevinMidboe 51d446f378 Added endpoint for updating a requested item. 2017-10-06 15:02:44 +02:00
KevinMidboe 9b6e7af3ee Merge pull request #41 from KevinMidboe/client/admin
Client/admin
2017-10-06 12:28:05 +02:00
KevinMidboe 42749c5b64 Now searchRequest passes a index key to movieObject so that each div object can have a separate key. 2017-10-06 12:20:51 +02:00
KevinMidboe 2e47324005 Merge pull request #40 from KevinMidboe/fix/api
Fix/api
2017-10-06 12:15:08 +02:00
KevinMidboe f8ff71bcff Removed a no longer used ip address for allowed origins. 2017-10-06 12:14:05 +02:00
KevinMidboe 7e27e19a0d Added more strict header allowence and was a error where endpoints for user was not using router, but app. 2017-10-06 12:12:21 +02:00
KevinMidboe c954bd4874 Added stylesheet for requestElement 2017-10-06 12:08:06 +02:00
KevinMidboe dbb8eb4057 Added redux reducer and store for calling from the login page. 2017-10-06 12:07:43 +02:00
KevinMidboe c03449b9e9 The login form for authentication a user. 2017-10-06 12:05:54 +02:00
KevinMidboe 6dd45cf89e Empty header file. 2017-10-06 12:05:00 +02:00
KevinMidboe 3bc8c5912b Loader for what pages is to be displayed based on the state of cookie: logged_in. Either displays login page or fetchrequested. 2017-10-06 12:04:35 +02:00
KevinMidboe bd74e1dee1 The first thing the index does is to load the app file in a hashrouter object and send it to the element with id root. 2017-10-06 12:03:16 +02:00
KevinMidboe ed1d0f3c39 App.jsx now loads the header and the main script that holds our routes. 2017-10-06 12:01:30 +02:00
KevinMidboe 0b79c8679d This is the landing page for viewing all requested items. One must be authenticated through the admin page, and requests need the token variable in browser storage. The returned data can be filtered and sorted. 2017-10-06 12:00:51 +02:00
KevinMidboe 927352e9ae Added react-redux, react-router and other redux packages. 2017-10-06 11:59:16 +02:00
KevinMidboe 5b110b9d82 This is the main router for index page, routes to searchRequest and admin pages. 2017-10-06 11:57:22 +02:00
KevinMidboe 1633be1276 Added 404 page when a request does not match any routes. 2017-10-06 11:56:22 +02:00
KevinMidboe 6d5c1e8d6d File that holds functions for retriveing and saving cookie data. 2017-10-06 11:55:20 +02:00
KevinMidboe 5e50d52344 Now gitignore is working for new prefs. 2017-10-04 14:25:05 +02:00
KevinMidboe 26c1bda3df Now when calling moveSeason python script, the python version used is python3. 2017-10-04 14:18:42 +02:00
KevinMidboe da83b73732 Added a parent_input that can be used for constructing the move path to a different path than the destination path. Also moved from using os.move to shutil.move for cross filesystem support. Also removed owner renaming. 2017-10-04 14:07:23 +02:00
KevinMidboe d9d8d6ab58 Merge pull request #39 from KevinMidboe/feature/api_request
Feature/api request
2017-10-03 16:28:39 +02:00
KevinMidboe 5fbedeb4b2 Misspelled User-Agent, when should have been user-agent. 2017-10-03 16:26:03 +02:00
KevinMidboe 455bf565b2 Moved the ? marker in the sql query to the end of the statement. 2017-10-03 16:19:55 +02:00
KevinMidboe a819a4bbf3 Now passes the user agent that is making the post request (requesting content) so it can be saved in the db. 2017-10-03 16:11:15 +02:00
KevinMidboe f50e0b2a59 Added user_agent to be passed to and saved in db when a request is made. 2017-10-03 16:10:27 +02:00
KevinMidboe 9d2c7c4c2f Added variable for status for database request query. 2017-10-03 16:01:28 +02:00
KevinMidboe 8908e36e89 Merge branch 'master' of github.com:KevinMidboe/seasonedShows 2017-09-27 21:55:33 +02:00
KevinMidboe 33aefef362 This was for some reason removed, now added again. 2017-09-27 21:55:21 +02:00
KevinMidboe cee27fd9d2 Merge pull request #38 from KevinMidboe/bugfix/infolinkForShows
Added a check for the type and then change the info link to end with … #35
2017-09-27 20:43:54 +02:00
KevinMidboe 2bacc54687 Added a check for the type and then change the info link to end with either movie or tv. 2017-09-27 20:41:49 +02:00
KevinMidboe 850401fd7a Merge pull request #37 from KevinMidboe/feature/getRequested
Feature/get requested
2017-09-27 17:58:16 +02:00
KevinMidboe a8166bcad9 Removed unused console. 2017-09-27 17:56:36 +02:00
KevinMidboe 149b05ef28 Calling function for fetching all the requested items. 2017-09-27 17:55:07 +02:00
KevinMidboe e43bcbdd52 Added endpoint for getting all requests. 2017-09-27 17:54:34 +02:00
KevinMidboe 509759bca8 Added a function in requestRepo to fetch out all the requested items. And a sql statement to do so. 2017-09-27 17:54:01 +02:00
KevinMidboe f1f407c71c Merge pull request #36 from KevinMidboe/feature/authentication
Feature/authentication
2017-09-27 16:30:05 +02:00
KevinMidboe bab4af08d9 Added the use of tokenToUser so that it is verified that a valid token is in the request header when doing actions that need login verification. 2017-09-27 16:27:05 +02:00
KevinMidboe a3de70e2da Created a middleware for requests that checks for a token in the Authentication field in the header and verifies that the token is valid for a user. 2017-09-27 16:25:54 +02:00
KevinMidboe 698d2d6072 Created endpoints for user tasks like login, register and a test endpoint history. A test for checking that session token works as expected. 2017-09-27 16:19:02 +02:00
KevinMidboe 6ec6c7f9cb Added token, user, userRepository and userSecurity in user folder. This handles creating new users, loging in and creating a user specific token then returning it when logging in. 2017-09-27 16:15:28 +02:00
KevinMidboe 72654fd465 Added searchHistory for adding a logged in users history trace. (More like a test function of the page) 2017-09-27 16:13:39 +02:00
KevinMidboe f4aee549be Added bcrypt and jsonwebtoken to package list 2017-09-27 16:12:20 +02:00
KevinMidboe bb6dd1ad59 Added middleware for authentiaction, endpoints for a user and import for token handling. 2017-09-27 14:02:59 +02:00
KevinMidboe a14d9e5ec6 Merge pull request #34 from KevinMidboe/update/frontend_logic
Update/frontend logic
2017-09-27 14:00:29 +02:00
KevinMidboe 5ceb19f449 Merge branch 'update/frontend_logic' of github.com:kevinmidboe/seasonedShows into update/frontend_logic 2017-09-27 13:51:07 +02:00
KevinMidboe 8b5b8bb0b7 Reduced the filter values to return more results. 2017-09-27 13:49:49 +02:00
KevinMidboe 6b3fe8c46f Merge pull request #33 from KevinMidboe/update/frontend_logic
Update/frontend logic
2017-09-27 00:47:25 +02:00
KevinMidboe 98dafe56c4 Merge branch 'master' into update/frontend_logic 2017-09-27 00:45:49 +02:00
KevinMidboe 44d8dbfe0c Updated gitignore to include yarn.locks. 2017-09-27 00:30:51 +02:00
KevinMidboe 6d0a91dc93 Changed the default filter values to be multi until a filter selector is implemented. 2017-09-27 00:29:28 +02:00
KevinMidboe 8014f01766 Added stricter handling for selecting what mediaType to convert to when searching tmdb and converting to seasoned object. 2017-09-27 00:28:41 +02:00
KevinMidboe d787fba024 Changed a smellingerror (reponse -> response). Also now pass the type of the search request when converting to seasoned object. Fixed issue where number_of_items_on_page was not set to the length of the list. 2017-09-27 00:22:22 +02:00
KevinMidboe 6bd1b29d5e Changed the font size of the header, space between the search bar and result content. Also changed so that the font size in the search bar is large enough to not zoom on ios because of smaller than standard font size. 2017-09-27 00:19:19 +02:00
KevinMidboe 86c479de15 Changed so that the output is split in two idependent mediaQuery items at the second most parent. This is a lazy way to controll all elements of when resizing to a smaller screen. 2017-09-27 00:18:06 +02:00
KevinMidboe daa9a7749e Now also the ip address is passed to the sendRequest function in requestRepository so that the requesters ip address can be logged in the database. 2017-09-26 20:43:56 +02:00
KevinMidboe d47f2bf757 Added support for database operations. Now when requesting a item, it is saved to a database and sends a email from pi.midboe account. 2017-09-26 20:42:49 +02:00
KevinMidboe 6f54a61223 Removed a extra / that was in the posterURL 2017-09-26 20:41:25 +02:00
KevinMidboe 1321671840 Added infinite-scroller, notify-toast, urijs. Also burger-menu for later support for selecting discover lists. 2017-09-26 20:40:11 +02:00
KevinMidboe 1af9368a6c Updated stylesheets to reflect changes to request and movieObject pages. 2017-09-26 20:38:13 +02:00
KevinMidboe 5341e940c6 Added viewport width to header of main index.html page. 2017-09-26 20:37:24 +02:00
KevinMidboe 30226af6f6 Lots of style changes done to look neater. Now when scrolled to bottom of the page it fetches more items from the api. 2017-09-26 20:36:37 +02:00
KevinMidboe b2f9d6f5f5 Added rating and background to the class constructor. Added type variable to url when requesting a movie/show and added a notification agent. Now this page is also updated to support mobile formatting, when tilted it shows the background image instead of poster image. 2017-09-26 20:34:54 +02:00
KevinMidboe 6b4daf140a Change a grammar error 2017-09-21 16:58:44 +02:00
KevinMidboe a5d270967e Merge pull request #31 from KevinMidboe/fix/seasonedApiSearch
Fix/seasoned api search
2017-09-21 16:39:00 +02:00
KevinMidboe e281e73293 Now the type is checked and passed to convertTmdbToSeasoned if it is movie or show and not multiwq 2017-09-21 16:37:53 +02:00
KevinMidboe 743c132aef If a strict type is passed to convertTmdbToSeasoned than this is now used to set the type of objects to be made. 2017-09-21 16:37:10 +02:00
KevinMidboe b219242787 Added infinate scroll to lists also. 2017-09-21 15:04:53 +02:00
KevinMidboe 00d000b8f8 Started working in the infinate scroll so it loads the next page when visible on page. This commit was under_development bugs as of now. 2017-09-21 15:03:33 +02:00
KevinMidboe c79d5dbc6e Also rewrote the funtionality for fetching lists from tmdb. It is now done in a similar fashion as searching requests, but with its own error handling and messaging. 2017-09-21 15:01:51 +02:00
KevinMidboe 372ec1b241 Rewrote most of how api calls are made when searching for a movie and how the returning data is handled. We now have finer handling of status response from the original api call aswell as if any of the functions hit a error e.g. not hitting the server, than we have our own errors thrown. Also updated the page incrementers to updated the last api call with a higher or lower number. 2017-09-21 14:36:41 +02:00
KevinMidboe 4c2982293b Merge pull request #29 from KevinMidboe/searchRequest
Search request
2017-09-20 10:49:27 +02:00
KevinMidboe 072f0cca93 Forgot to append the tmdb endpoint to the baseurl in searchRequest 2017-09-20 10:46:10 +02:00
KevinMidboe acd0a1782d Changed the links from localhost to the correct global url 2017-09-20 10:38:10 +02:00
KevinMidboe d9dd7ec153 It is now expected to get a JSON object were the items we want to display are listed in results object within, this is now reflected in the client by iterating over data.results not just data. 2017-09-20 10:26:30 +02:00
KevinMidboe ae66e9a684 Changed variable name from movie to searchResult and now checks if the result list in the return JSON object is empty before sending a result back to client. 2017-09-20 10:25:01 +02:00
KevinMidboe 2d465d841e Before when doing a request search we had a lot of promisses and loops within one-another, now it's more split so easier to read and better error handling if something goes wrong when looking up either in plex or tmdb api. 2017-09-20 10:23:36 +02:00
KevinMidboe 72d50209a5 Rewrote the search function in tmdb to now return a JSON object with results: (all movie/show objects), number_of_items: (count of result objects), page and total page. Split up the filtering and converting of the result from tmdb api to finished seasoned objects for better readability. 2017-09-20 10:21:57 +02:00
KevinMidboe fae3fcc74c Merge branch 'master' of github.com:KevinMidboe/seasonedShows 2017-09-12 20:15:41 +02:00
KevinMidboe 12835d4ed5 Changed the location of moveseasoned app script. 2017-09-12 20:15:20 +02:00
KevinMidboe c187ce7515 Merge pull request #26 from KevinMidboe/client_features
Now we can fetch lists for discover, popular, now playing and upcomin…
2017-09-12 14:40:46 +02:00
KevinMidboe 4fa32440af Now we can fetch lists for discover, popular, now playing and upcoming with optional tv_show and movie filter options. 2017-09-12 14:39:32 +02:00
KevinMidboe cf90616ec9 Set theme jekyll-theme-cayman 2017-09-08 20:22:54 +02:00
KevinMidboe 895d52b41a Set theme jekyll-theme-cayman 2017-09-08 20:22:53 +02:00
KevinMidboe d26f4b0531 Set theme jekyll-theme-cayman 2017-09-08 20:21:56 +02:00
KevinMidboe d347a2bbfe Merge pull request #25 from KevinMidboe/documentation
Documentation
2017-09-08 12:49:49 +02:00
KevinMidboe 8e894b3dfb Merge pull request #23 from KevinMidboe/bugfix_api/plex/playing
Bugfix api/plex/playing #21
2017-09-07 23:57:41 +02:00
KevinMidboe fa3d56c4e5 Merge pull request #22 from KevinMidboe/client_features
Client features
2017-09-07 23:56:02 +02:00
KevinMidboe eb2de340a3 Added variables for audioProfile, videoProfile, duration and container for a playback. 2017-09-07 23:52:05 +02:00
KevinMidboe 0370f051bc Changed the error message returned if something goes wrong when fetching playing 2017-09-07 23:51:22 +02:00
KevinMidboe 3eb2bbe54e Removed a console log 2017-09-07 23:49:21 +02:00
KevinMidboe a8c6850863 For some reason this was player info, but it is now been replaced with all the variables specific for mediaInfo 2017-09-07 23:48:06 +02:00
KevinMidboe cd52d295b0 Now both Media and Media.Part in the JSON object about a stream is a list and need to index the first element of the stream to get this info. 2017-09-07 23:47:11 +02:00
KevinMidboe e9c0e5bfa6 Removed extra space under header 2017-09-04 13:45:18 +02:00
KevinMidboe fcf6b324c9 Moved the pitch sentence down under the header 2017-09-04 13:44:34 +02:00
KevinMidboe 231b70c226 Added what the different parts of seasoned does, that is external, api and services. 2017-09-04 13:43:31 +02:00
KevinMidboe 28d0b63960 Added TODO and a output of what is requested in the router 2017-09-03 18:17:07 +02:00
KevinMidboe 9eb31ea433 Added type input to the sendRequest function. 2017-09-03 18:16:26 +02:00
KevinMidboe 2b7f9551bf Moved styles from serachRequest to a seapate file in ./styles 2017-09-03 17:34:45 +02:00
KevinMidboe 6a4e2bc2ab Moved all styling to a separate file in ./style 2017-09-03 17:29:51 +02:00
KevinMidboe 850452db78 Removed unnesessary this.id when calling function, this is already a object, don't need to pass itself. 2017-09-03 17:07:53 +02:00
KevinMidboe 7a1f709d90 Small changed that first checks if the variable is undefined or not and then length 2017-09-03 17:06:19 +02:00
KevinMidboe e81f5c8057 Added functions for discover, upcoming, nowplaying, popular and similar. Also documented better and some finer error handling with more logical responses. 2017-09-03 17:04:59 +02:00
KevinMidboe 789ed77ab6 Now the type of object being passed can be set by strictType variable. The type is then set before creating a movie or show object item. 2017-09-03 17:03:31 +02:00
KevinMidboe 2dc22b386d Added endpoints for discover media (either movie or show), getUpcoming movies, nowPlaying (movies or shows), popular (movie or show) and search similar where the type movie/show must be defined 2017-09-03 17:02:02 +02:00
KevinMidboe 3e12cf10fb Updated tmdb to use the new endpoints 2017-09-03 16:59:52 +02:00
KevinMidboe c64f6a861e Merge branch 'master' of github.com:kevinmidboe/seasonedShows into client_features 2017-09-02 23:16:46 +02:00
KevinMidboe 70b284380f Removed unused console.log 2017-09-02 23:16:24 +02:00
KevinMidboe e33e7d8dc7 Removed unsued imports. 2017-09-02 23:15:30 +02:00
KevinMidboe a5bb42f175 Merge pull request #20 from KevinMidboe/api_bugfixing
Api bugfixing and changed value of filter for tmdb search
2017-09-02 22:54:09 +02:00
KevinMidboe dacd44a8f5 Changed the value of the filter to be vote_count >= 80 or popularity > 18 2017-09-02 22:53:04 +02:00
KevinMidboe 84a897da6e Added popularity and vote_count to movie and show object and added a new check for name and title of movie 2017-09-02 22:51:49 +02:00
KevinMidboe d5479be563 Merge pull request #19 from KevinMidboe/api_bugfixing
Api bugfixing
2017-09-02 21:15:36 +02:00
KevinMidboe 849bc4aa5e Merge pull request #18 from KevinMidboe/app_style_update
App style update
2017-09-02 21:13:43 +02:00
KevinMidboe 17dd66e9ac Fixed a bug where if a seach query did not pass anything back when checking existance in plex, then you would get a 404 message. 2017-09-02 21:12:38 +02:00
KevinMidboe 8149c8f732 Added date_added for plex added date. 2017-09-02 20:50:19 +02:00
KevinMidboe 8652a0ee0f Rewrote for more general use. 2017-09-02 20:49:50 +02:00
KevinMidboe 101dc7d570 Changed the filter cutoff to be based on, if the show/movie has popularity over 2, vote_count over 20 and has a defined release_date/first_air_date. Also changed so that it now uses the new convertTmdbToSeaonsed insted of the old (convertTmdbToMovie) 2017-09-02 20:49:09 +02:00
KevinMidboe 3bf646483d Renamed file. Now converts to both movie and show. Also added more data that is saved to the object. 2017-09-02 18:13:51 +02:00
KevinMidboe 622e89ea50 Changed import name from convertPlexToMovie -> convertPlexToSeasoned and changed from map function to reduce to be able to filter only movies and shows to be parsed. 2017-09-02 18:13:08 +02:00
KevinMidboe b4a005da42 Changed the name of convertPlexToMovie -> convertPlexToSeasoned 2017-09-02 18:11:59 +02:00
KevinMidboe 1a858d09bb Created a new shows class that only holds relevant information for shows. 2017-09-02 18:10:16 +02:00
KevinMidboe a4fe5cc635 Made this a singular movie object that now only holds relevant information for movies. 2017-09-02 18:09:43 +02:00
KevinMidboe a82dc1ae78 Added requestRep again 2017-09-02 16:36:54 +02:00
KevinMidboe ebe09390d2 Removed unused css loaders 2017-09-02 16:32:57 +02:00
KevinMidboe d4fdf6bdcf Updated babel node packages 2017-09-02 16:32:35 +02:00
KevinMidboe 72c4a43d2e Also has a lot of inline css, but not the search type buttons have function, but not feedback. 2017-09-02 16:30:42 +02:00
KevinMidboe d77a4c6d9e Now it is a little messy with lots of css inline, but will clean and comment. Has now a working UI, needs more 404 handling of items. 2017-09-02 16:30:02 +02:00
KevinMidboe e33840f1db Commented out unused elements. 2017-09-02 16:03:43 +02:00
KevinMidboe bd3d8f385b Added link to font-awesome and google font api. Also set the margin: 0 for body. 2017-08-31 13:16:06 +02:00
KevinMidboe 5706c02a5d Updated much of the surrounding styling for the request page, still need to ble cleaned. 2017-08-31 13:14:57 +02:00
KevinMidboe ee019f5674 Now with new styling, still need to do cleanup, but added a big bulk of the grutt. 2017-08-31 13:13:50 +02:00
KevinMidboe 56405e54f9 Downgraded from sqlite3 to sqlite because of connection function error. 2017-08-23 10:26:23 +02:00
KevinMidboe daa1915203 Merge pull request #17 from KevinMidboe/filestructure_cleanup
Filestructure cleanup
2017-08-14 22:50:27 +02:00
KevinMidboe ea97a0b4af Moved all python related scripts to app directory 2017-08-14 22:42:48 +02:00
KevinMidboe d2bed84ecb Moved conf folder to seasoned_api 2017-08-14 22:42:24 +02:00
KevinMidboe b8d01fcf1c Changed filename for moveSeasoned to include dir 2017-08-14 22:33:28 +02:00
KevinMidboe 47aa638fd8 Moved eveything related to the api to a seperate folder seasoned_api. 2017-08-06 16:45:02 +02:00
KevinMidboe f53ab55d96 Merge pull request #16 from KevinMidboe/request_ui
Request ui
2017-08-06 09:26:22 +02:00
KevinMidboe 20a7cb0ba1 Added yarn lock files 2017-08-06 09:23:48 +02:00
KevinMidboe 10da9874fc Added yarn error log 2017-08-06 09:22:26 +02:00
KevinMidboe bc4eff795e Added quotes to poster url 2017-08-06 09:19:39 +02:00
KevinMidboe 82add07e00 Font that is not yet in use 2017-08-05 17:27:01 +02:00
KevinMidboe e5cb80afc8 Mostly unused style file 2017-08-05 17:26:40 +02:00
KevinMidboe 891b7ecb28 Added CSS loaders to webpack.config 2017-08-05 17:24:08 +02:00
KevinMidboe 292922c06c Added webfontloader and css-loader to package.json 2017-08-05 17:23:45 +02:00
KevinMidboe 9265b469c4 Added style to the elements of the html elements 2017-08-05 17:06:26 +02:00
KevinMidboe c892755759 Created function for creating a HTML object that is returned to the caller. 2017-08-05 17:05:15 +02:00
KevinMidboe d2c456305a Added style to background and main object 2017-08-05 17:04:44 +02:00
KevinMidboe 66b9c8c3db Updated webpack version 2017-08-05 17:02:38 +02:00
KevinMidboe fbf49e4cd9 Updated to use sqlite3 instead of sqlite in api 2017-07-29 13:21:25 +02:00
KevinMidboe a436f79770 Merge pull request #15 from KevinMidboe/submit_request
Submit request
2017-07-17 17:17:38 +02:00
KevinMidboe 6b5a2341bf Class file that creates and returns objects for the mail sender in a single place. 2017-07-16 13:47:49 +02:00
KevinMidboe 30d26d82c3 Added nodemailer to send email of the requested movie 2017-07-16 13:47:06 +02:00
KevinMidboe a6af5e8c5d Now returns just id of the movie when clicked button. 2017-07-16 13:45:42 +02:00
KevinMidboe 6322b0b17b Formatting changes, sorted alpha 2017-07-16 13:45:05 +02:00
KevinMidboe e77a05db07 Merge pull request #14 from KevinMidboe/client_feature
Client feature
2017-07-16 11:19:15 +02:00
KevinMidboe f2cc05307b Added TODO 2017-07-16 11:18:14 +02:00
KevinMidboe 90546755cc Rewrote much of the fetching of media items for the rendered request page. 2017-07-16 11:11:43 +02:00
KevinMidboe b34e23077b Updated gitignore with dist folder 2017-07-16 11:08:36 +02:00
KevinMidboe c45df8a131 Added controller for calling POST request function for media items. 2017-07-16 11:08:14 +02:00
KevinMidboe 91606fc9b8 Removed unused import 2017-07-16 11:05:30 +02:00
KevinMidboe 3047dce147 Opened the POST endpoint for requesting a movie in the server 2017-07-16 11:04:56 +02:00
Kevin Midbøe 2c97803d82 Added a sendRequest function for handliing the post of a movie request 2017-07-16 11:03:30 +02:00
Kevin Midbøe 97dea47a7a Updated inherits in yarn config file 2017-07-16 10:54:44 +02:00
Kevin Midbøe c97ab972ef Changed movieObject ot pass full object, not just it's id when clicked 2017-07-16 10:51:02 +02:00
Kevin Midbøe 29e575cbf1 Start of fire script to move bulk files to wanted dir, while creating the respected folder names. 2017-07-12 00:11:05 +02:00
KevinMidboe b2848e6a7e Merge pull request #13 from KevinMidboe/api
Verify that respond has content, if so send the content back. If no c…
2017-07-01 08:47:19 +02:00
KevinMidboe 1663f5931d Verify that respond has content, if so send the content back. If no content was found, send 404 status and error message 2017-07-01 08:46:37 +02:00
KevinMidboe 5b8394c5a0 Merge pull request #12 from KevinMidboe/api
If search request returns empty array from tmdb, then send a 404 repo…
2017-07-01 08:37:53 +02:00
KevinMidboe ba3a1fa028 If search request returns empty array from tmdb, then send a 404 reponse with error message back to client. 2017-07-01 08:37:29 +02:00
KevinMidboe b358c61efb Had a bug that when searched and empty return it tried to map the empty array. Now we check for the contents of the response before we map the items. 2017-07-01 08:29:49 +02:00
KevinMidboe 71130e66f3 Merge pull request #11 from KevinMidboe/rename_and_move
Rename and move
2017-06-28 00:03:13 +02:00
KevinMidboe 82d2e439fe Merge branch 'master' into rename_and_move 2017-06-28 00:03:04 +02:00
KevinMidboe be839ba2dd Trying to fix bug with os.join, now has a two args, not list 2017-06-27 15:58:40 -06:00
KevinMidboe f884406c06 Missed a syntax errro 2017-06-27 15:56:47 -06:00
KevinMidboe 04066b8da4 Now the full path is sent to fix ownership, not just the file path 2017-06-27 15:56:06 -06:00
KevinMidboe 34ab8be097 Fix_ownership got a list of files, now it iterates through this list and sends each item to the function 2017-06-27 15:53:52 -06:00
KevinMidboe 31e16e2784 Now the for loop actually goes through dir not just the string of file name. *facepalm* 2017-06-27 15:52:17 -06:00
KevinMidboe 7cda4accdb Added print for debugging purposes 2017-06-27 15:49:34 -06:00
KevinMidboe 7023b135b4 Now all subfiles also have their permission chagned. 2017-06-27 15:48:25 -06:00
KevinMidboe 979a95a468 Added a try except to remove and a todo to remind of need improvements. 2017-06-27 15:37:55 -06:00
KevinMidboe c1cd821d8a Changed group id to the wanted value. 2017-06-27 15:33:50 -06:00
KevinMidboe 4b54339b72 Opps, changed to use the int value of uid and gid, not looking up the int value and then converting to int. Does not make sense. 2017-06-27 15:30:34 -06:00
KevinMidboe 3d8dc80bb9 Merge pull request #10 from KevinMidboe/subtitles
Subtitles
2017-06-27 23:27:07 +02:00
KevinMidboe 8a53cc4765 Added todo for fix_ownership 2017-06-27 15:25:19 -06:00
KevinMidboe 3bc539323a Changed user and group id to the wanted value. 2017-06-27 15:24:49 -06:00
KevinMidboe 80746252c0 Added excepts to all move and delete, so it can be run mulitple times and see no downside to having it run and logging it if not found. Also think I fixed a bug that renamed the folder that would be deleted, not the new folder 2017-06-27 15:22:02 -06:00
KevinMidboe d5ea7a6bbb Error with function call 2017-06-18 21:49:58 -06:00
KevinMidboe 17b89748e1 Added todo to analyseSubtitles function 2017-06-18 21:48:08 -06:00
KevinMidboe 5a45856699 Pulled variable for subtitles path out of open() and retunes subfile without analysis on typeError. 2017-06-18 21:47:15 -06:00
KevinMidboe 90384e4ebc Calls fix_ownership after creating new directory 2017-06-13 05:17:40 +02:00
KevinMidboe 7f14f64762 Set gid and uid to static vars 2017-06-13 05:11:50 +02:00
KevinMidboe 2333559477 Merge branch 'master' of github.com:KevinMidboe/seasonedShows 2017-06-12 20:32:14 +02:00
KevinMidboe a6d21ed181 Added fix_ownership for setting the new folder to user, not root. 2017-06-12 20:28:58 +02:00
KevinMidboe 1eb46a41e0 Merge branch 'master' of github.com:KevinMidboe/seasonedShows 2017-06-04 12:42:01 +02:00
KevinMidboe 3b5d6fbb6c Merge pull request #9 from KevinMidboe/revert-8-api
Revert "Api"
2017-06-04 00:16:29 +02:00
KevinMidboe 0022f656bd Revert "Api" 2017-06-04 00:16:16 +02:00
KevinMidboe 9a02130124 Merge pull request #8 from KevinMidboe/api
Added endpoint for submitting movie/tv requests
2017-06-04 00:16:04 +02:00
KevinMidboe 3bb43f08f2 Controller for handling submits of item requests. 2017-06-04 00:13:35 +02:00
KevinMidboe 703e3d3785 Added a api endpoint for submitting a movie request. 2017-06-04 00:13:02 +02:00
KevinMidboe 6496988e51 Changed variable names to better reflect their purpose 2017-06-04 00:12:38 +02:00
KevinMidboe 069d984c39 Added for handling input of movie request, but still need to work on the way the python script is run. 2017-06-04 00:10:35 +02:00
KevinMidboe 27edd29bd2 Started adding features for submitting a request to server 2017-06-04 00:07:23 +02:00
KevinMidboe 531900744e Merge branch 'master' of github.com:KevinMidboe/seasonedShows 2017-06-03 13:13:23 +02:00
KevinMidboe d9cb5b97c7 Merge pull request #7 from KevinMidboe/api
Added overview (description) from tmdb movie to movieObject.
2017-06-03 13:12:32 +02:00
KevinMidboe 9d29482857 Added overview of requested movie to be printed. 2017-06-03 13:12:10 +02:00
KevinMidboe 907bc73a7f Added overview (description) from tmdb movie to movieObject. 2017-06-03 13:07:49 +02:00
KevinMidboe 6f8a7bc06c Merge branch 'master' of github.com:KevinMidboe/seasonedShows 2017-06-03 11:21:15 +02:00
KevinMidboe 25e4cb1870 Merge pull request #6 from KevinMidboe/api
Changes to header info
2017-06-03 11:19:39 +02:00
KevinMidboe b695d1aaf0 Merge pull request #5 from KevinMidboe/client_feature
Client feature
2017-06-03 11:19:01 +02:00
KevinMidboe d05aa13c39 Changes to more strict origin policy. 2017-06-03 11:17:09 +02:00
KevinMidboe 21c40e9b34 Created a function that can be called for updating the mediaList, saving lines! 2017-06-03 11:13:57 +02:00
KevinMidboe 244f606133 Changed the way html for the object is created, but need to do this with React native functions. Also added a few functions that will be for requesting items. 2017-06-03 11:12:28 +02:00
KevinMidboe 7152c987b7 Linting changes 2017-06-02 19:39:06 +02:00
KevinMidboe 47269ebc70 Class for creating a object that can easily be called for the html implicit to the object type 2017-06-02 19:37:58 +02:00
KevinMidboe 30dd05ebf3 Now the search request updates based on the input in the text field. Super simple, need better handling and styling. 2017-06-02 19:37:08 +02:00
KevinMidboe 4f2a69f32d Removed a unused log 2017-06-02 19:36:18 +02:00
KevinMidboe 545152c888 Start of request, now just prints what is written in text field and reads keypress enter 2017-06-02 14:35:43 +02:00
KevinMidboe bd3ceb7b6d Added component for getting input for requestSearch. 2017-06-02 14:29:19 +02:00
KevinMidboe 7c055bd9d1 Created a function for deciding what to return based on the object it computes. 2017-06-02 13:50:42 +02:00
KevinMidboe 9d11798133 Removed test logging functions 2017-06-02 12:14:19 +02:00
KevinMidboe 53a60de38b Removed the exit status 2017-06-02 08:54:40 +02:00
KevinMidboe 74c6ed02a8 Changed debug level and removed a unused exit 2017-06-02 08:52:54 +02:00
226 changed files with 18562 additions and 4262 deletions
+108
View File
@@ -0,0 +1,108 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
.hypothesis/
# Translations
*.mo
*.pot
# Django stuff:
*.log
.static_storage/
.media/
local_settings.py
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
target/
# Jupyter Notebook
.ipynb_checkpoints
# pyenv
.python-version
# celery beat schedule file
celerybeat-schedule
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
# - - - - -
# My own gitignore files and folders
env_variables.py
@@ -3,7 +3,7 @@
# @Author: KevinMidboe
# @Date: 2017-04-05 18:40:11
# @Last Modified by: KevinMidboe
# @Last Modified time: 2017-06-01 19:02:04
# @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())
@@ -91,7 +91,12 @@ class strayEpisode(object):
def analyseSubtitles(self, subFile):
# TODO verify that it is a file
f = open(os.path.join([env.show_dir, self.parent, subFile]), 'r', encoding='ISO-8859-15')
try:
subtitlePath = os.path.join([env.input_dir, self.parent, subFile])
except TypeError:
# TODO don't get a list in subtitlePath
return self.removeUploadSign(subFile)
f = open(subtitlesPath, 'r', encoding='ISO-8859-15')
language = detect(f.read())
f.close()
@@ -126,7 +131,7 @@ class strayEpisode(object):
conn = sqlite3.connect(env.db_path)
c = conn.cursor()
path = '/'.join([env.show_dir, self.parent])
path = '/'.join([env.input_dir, self.parent])
video_files = json.dumps(self.videoFiles)
subtitles = json.dumps(self.subtitles)
trash = json.dumps(self.trash)
@@ -144,14 +149,13 @@ class strayEpisode(object):
def getDirContent(dir=env.show_dir):
def getDirContent(dir=env.input_dir):
# TODO What if item in db is not in this list?
try:
return [d for d in os.listdir(dir) if d[0] != '.']
except FileNotFoundError:
# TODO Log to error file
logging.info('Error: "' + dir + '" is not a directory.')
# TODO Remove this exit(0)
# Hashes the contents of media folder to easily check for changes.
def directoryChecksum():
@@ -185,12 +189,12 @@ def XOR(list1, list2):
def filterChildItems(parent):
try:
children = getDirContent('/'.join([env.show_dir, parent]))
children = getDirContent('/'.join([env.input_dir, parent]))
if children:
strayEpisode(parent, children)
except FileNotFoundError:
# TODO Log to error file
logging.info('Error: "' + '/'.join([env.show_dir, parent]) + '" is not a valid directory.')
logging.info('Error: "' + '/'.join([env.input_dir, parent]) + '" is not a valid directory.')
def getNewItems():
newItems = XOR(getDirContent(), getShowNames())
@@ -209,7 +213,7 @@ def main():
if __name__ == '__main__':
if (os.path.exists(env.logfile)):
logging.basicConfig(filename=env.logfile, level=logging.INFO)
logging.basicConfig(filename=env.logfile, level=logging.DEBUG)
else:
print('Logfile could not be found at ' + env.logfile + '. Verifiy presence or disable logging in config.')
exit(0)
@@ -217,3 +221,4 @@ if __name__ == '__main__':
while True:
main()
sleep(30)
+279
View File
@@ -0,0 +1,279 @@
#!/usr/bin/env python3.6
# -*- coding: utf-8 -*-
# @Author: KevinMidboe
# @Date: 2017-08-25 23:22:27
# @Last Modified by: KevinMidboe
# @Last Modified time: 2017-10-12 22:44:27
from guessit import guessit
import os, errno
import logging
import tvdb_api
from pprint import pprint
import env_variables as env
from video import VIDEO_EXTENSIONS, Episode, Movie, Video
from subtitle import SUBTITLE_EXTENSIONS, Subtitle, get_subtitle_path
from utils import sanitize
logging.basicConfig(filename=os.path.dirname(__file__) + '/' + env.logfile, level=logging.INFO)
from datetime import datetime
#: Supported archive extensions
ARCHIVE_EXTENSIONS = ('.rar',)
def scan_video(path):
"""Scan a video from a `path`.
:param str path: existing path to the video.
:return: the scanned video.
:rtype: :class:`~subliminal.video.Video`
"""
# check for non-existing path
if not os.path.exists(path):
raise ValueError('Path does not exist')
# check video extension
# if not path.endswith(VIDEO_EXTENSIONS):
# raise ValueError('%r is not a valid video extension' % os.path.splitext(path)[1])
dirpath, filename = os.path.split(path)
logging.info('Scanning video %r in %r', filename, dirpath)
# guess
parent_path = path.strip(filename)
# video = Video.fromguess(filename, parent_path, guessit(path))
video = Video(filename)
# guessit(path)
return video
def scan_subtitle(path):
if not os.path.exists(path):
raise ValueError('Path does not exist')
dirpath, filename = os.path.split(path)
logging.info('Scanning subtitle %r in %r', filename, dirpath)
# guess
parent_path = path.strip(filename)
subtitle = Subtitle.fromguess(filename, parent_path, guessit(path))
return subtitle
def scan_files(path, age=None, archives=True):
"""Scan `path` for videos and their subtitles.
See :func:`refine` to find additional information for the video.
:param str path: existing directory path to scan.
:param datetime.timedelta age: maximum age of the video or archive.
:param bool archives: scan videos in archives.
:return: the scanned videos.
:rtype: list of :class:`~subliminal.video.Video`
"""
# check for non-existing path
if not os.path.exists(path):
raise ValueError('Path does not exist')
# check for non-directory path
if not os.path.isdir(path):
raise ValueError('Path is not a directory')
name_dict = {}
# walk the path
mediafiles = []
for dirpath, dirnames, filenames in os.walk(path):
logging.debug('Walking directory %r', dirpath)
# remove badly encoded and hidden dirnames
for dirname in list(dirnames):
if dirname.startswith('.'):
logging.debug('Skipping hidden dirname %r in %r', dirname, dirpath)
dirnames.remove(dirname)
# scan for videos
for filename in filenames:
# filter on videos and archives
if not (filename.endswith(VIDEO_EXTENSIONS) or filename.endswith(SUBTITLE_EXTENSIONS) or archives and filename.endswith(ARCHIVE_EXTENSIONS)):
continue
# skip hidden files
if filename.startswith('.'):
logging.debug('Skipping hidden filename %r in %r', filename, dirpath)
continue
# reconstruct the file path
filepath = os.path.join(dirpath, filename)
# skip links
if os.path.islink(filepath):
logging.debug('Skipping link %r in %r', filename, dirpath)
continue
# skip old files
if age and datetime.utcnow() - datetime.utcfromtimestamp(os.path.getmtime(filepath)) > age:
logging.debug('Skipping old file %r in %r', filename, dirpath)
continue
# scan
if filename.endswith(VIDEO_EXTENSIONS): # video
try:
video = scan_video(filepath)
try:
name_dict[video.series] += 1
except KeyError:
name_dict[video.series] = 0
mediafiles.append(video)
except ValueError: # pragma: no cover
logging.exception('Error scanning video')
continue
elif archives and filename.endswith(ARCHIVE_EXTENSIONS): # archive
print('archive')
pass
# try:
# video = scan_archive(filepath)
# mediafiles.append(video)
# except (NotRarFile, RarCannotExec, ValueError): # pragma: no cover
# logging.exception('Error scanning archive')
# continue
elif filename.endswith(SUBTITLE_EXTENSIONS): # subtitle
try:
subtitle = scan_subtitle(filepath)
mediafiles.append(subtitle)
except ValueError:
logging.exception('Error scanning subtitle')
continue
else: # pragma: no cover
raise ValueError('Unsupported file %r' % filename)
pprint(name_dict)
return mediafiles
def organize_files(path):
hashList = {}
mediafiles = scan_files(path)
# print(mediafiles)
for file in mediafiles:
hashList.setdefault(file.__hash__(),[]).append(file)
# hashList[file.__hash__()] = file
return hashList
def save_subtitles(files, single=False, directory=None, encoding=None):
t = tvdb_api.Tvdb()
if not isinstance(files, list):
files = [files]
for file in files:
# TODO this should not be done in the loop
dirname = "%s S%sE%s" % (file.series, "%02d" % (file.season), "%02d" % (file.episode))
createParentfolder = not dirname in file.parent_path
if createParentfolder:
dirname = os.path.join(file.parent_path, dirname)
print('Created: %s' % dirname)
try:
os.makedirs(dirname)
except OSError as e:
if e.errno != errno.EEXIST:
raise
# TODO Clean this !
try:
tvdb_episode = t[file.series][file.season][file.episode]
episode_title = tvdb_episode['episodename']
except:
episode_title = ''
old = os.path.join(file.parent_path, file.name)
if file.name.endswith(SUBTITLE_EXTENSIONS):
lang = file.getLanguage()
sdh = '.sdh' if file.sdh else ''
filename = "%s S%sE%s %s%s.%s.%s" % (file.series, "%02d" % (file.season), "%02d" % (file.episode), episode_title, sdh, lang, file.container)
else:
filename = "%s S%sE%s %s.%s" % (file.series, "%02d" % (file.season), "%02d" % (file.episode), episode_title, file.container)
if createParentfolder:
newname = os.path.join(dirname, filename)
else:
newname = os.path.join(file.parent_path, filename)
print('Moved: %s ---> %s' % (old, newname))
os.rename(old, newname)
print()
# for hash in files:
# hashIndex = [files[hash]]
# for hashItems in hashIndex:
# for file in hashItems:
# print(file.series)
# saved_subtitles = []
# for subtitle in files:
# # check content
# if subtitle.name is None:
# logging.error('Skipping subtitle %r: no content', subtitle)
# continue
# # check language
# if subtitle.language in set(s.language for s in saved_subtitles):
# logging.debug('Skipping subtitle %r: language already saved', subtitle)
# continue
# # create subtitle path
# subtitle_path = get_subtitle_path(video.name, None if single else subtitle.language)
# if directory is not None:
# subtitle_path = os.path.join(directory, os.path.split(subtitle_path)[1])
# # save content as is or in the specified encoding
# logging.info('Saving %r to %r', subtitle, subtitle_path)
# if encoding is None:
# with io.open(subtitle_path, 'wb') as f:
# f.write(subtitle.content)
# else:
# with io.open(subtitle_path, 'w', encoding=encoding) as f:
# f.write(subtitle.text)
# saved_subtitles.append(subtitle)
# # check single
# if single:
# break
# return saved_subtitles
def stringTime():
return str(datetime.now().strftime("%Y-%m-%d %H:%M:%S:%f"))
def main():
# episodePath = '/Volumes/media/tv/Black Mirror/Black Mirror Season 01/'
episodePath = '/Volumes/mainframe/shows/Black Mirror/Black Mirror Season 01/'
t = tvdb_api.Tvdb()
hashList = organize_files(episodePath)
pprint(hashList)
if __name__ == '__main__':
main()
+99
View File
@@ -0,0 +1,99 @@
#!/usr/bin/env python
'''
Created on Apr 19, 2012
@author: dan, Faless
GNU GENERAL PUBLIC LICENSE - Version 3
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
http://www.gnu.org/licenses/gpl-3.0.txt
'''
import shutil
import tempfile
import os.path as pt
import sys, logging
import libtorrent as lt
from time import sleep
import env_variables as env
logging.basicConfig(filename=pt.dirname(__file__) + '/' + env.logfile)
def magnet2torrent(magnet, output_name=None):
if output_name and \
not pt.isdir(output_name) and \
not pt.isdir(pt.dirname(pt.abspath(output_name))):
logging.info("Invalid output folder: " + pt.dirname(pt.abspath(output_name)))
logging.info("")
sys.exit(0)
tempdir = tempfile.mkdtemp()
ses = lt.session()
params = {
'save_path': tempdir,
'storage_mode': lt.storage_mode_t(2),
'paused': False,
'auto_managed': True,
'duplicate_is_error': True
}
handle = lt.add_magnet_uri(ses, magnet, params)
logging.info("Downloading Metadata (this may take a while)")
while (not handle.has_metadata()):
try:
sleep(1)
except KeyboardInterrupt:
logging.info("Aborting...")
ses.pause()
logging.info("Cleanup dir " + tempdir)
shutil.rmtree(tempdir)
sys.exit(0)
ses.pause()
logging.info("Done")
torinfo = handle.get_torrent_info()
torfile = lt.create_torrent(torinfo)
output = pt.abspath(torinfo.name() + ".torrent")
if output_name:
if pt.isdir(output_name):
output = pt.abspath(pt.join(
output_name, torinfo.name() + ".torrent"))
elif pt.isdir(pt.dirname(pt.abspath(output_name))):
output = pt.abspath(output_name)
logging.info("Saving torrent file here : " + output + " ...")
torcontent = lt.bencode(torfile.generate())
f = open(output, "wb")
f.write(lt.bencode(torfile.generate()))
f.close()
logging.info("Saved! Cleaning up dir: " + tempdir)
ses.remove_torrent(handle)
shutil.rmtree(tempdir)
return output
def main():
magnet = sys.argv[1]
logging.info('INPUT: {}'.format(magnet))
magnet2torrent(magnet, env.torrent_dumpsite)
if __name__ == "__main__":
main()
+112
View File
@@ -0,0 +1,112 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# @Author: KevinMidboe
# @Date: 2017-04-12 23:27:51
# @Last Modified by: KevinMidboe
# @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
self.getVarsFromDB()
def getVarsFromDB(self):
c = sqlite3.connect(env.db_path).cursor()
c.execute('SELECT parent, name, season, episode, video_files, subtitles, trash FROM stray_eps WHERE id = ?', (self.id,))
returnMsg = c.fetchone()
self.parent = returnMsg[0]
self.name = returnMsg[1]
self.season = returnMsg[2]
self.episode = returnMsg[3]
self.video_files = json.loads(returnMsg[4])
self.subtitles = json.loads(returnMsg[5])
self.trash = json.loads(returnMsg[6])
c.close()
self.queries = {
'parent_input': [env.input_dir, self.parent],
'season': [env.show_dir, self.name, self.name + ' Season ' + "%02d" % self.season],
'episode': [env.show_dir, self.name, self.name + ' Season ' + "%02d" % self.season, \
self.name + ' S' + "%02d" % self.season + 'E' + "%02d" % self.episode],
}
def typeDir(self, dType, create=False, mergeItem=None):
url = '/'.join(self.queries[dType])
print(url)
if create and not os.path.isdir(url):
os.makedirs(url)
fix_ownership(url)
if mergeItem:
return '/'.join([url, str(mergeItem)])
return url
def fix_ownership(path):
pass
# TODO find this from username from config
# uid = 1000
# gid = 112
# os.chown(path, uid, gid)
def moveStray(strayId):
ep = episode(strayId)
for item in ep.video_files:
try:
old_dir = ep.typeDir('parent_input', mergeItem=item[0])
new_dir = ep.typeDir('episode', mergeItem=item[1], create=True)
shutil.move(old_dir, new_dir)
except FileNotFoundError:
logging.warning(old_dir + ' does not exits, cannot be moved.')
for item in ep.subtitles:
try:
old_dir = ep.typeDir('parent_input', mergeItem=item[0])
new_dir = ep.typeDir('episode', mergeItem=item[1], create=True)
shutil.move(old_dir, new_dir)
except FileNotFoundError:
logging.warning(old_dir + ' does not exits, cannot be moved.')
for item in ep.trash:
try:
os.remove(ep.typeDir('parent_input', mergeItem=item))
except FileNotFoundError:
logging.warning(ep.typeDir('parent_input', mergeItem=item) + 'does not exist, cannot be removed.')
fix_ownership(ep.typeDir('episode'))
for root, dirs, files in os.walk(ep.typeDir('episode')):
for item in files:
fix_ownership(os.path.join(ep.typeDir('episode'), item))
# TODO because we might jump over same files, the dir might no longer
# be empty and cannot remove dir like this.
try:
os.rmdir(ep.typeDir('parent_input'))
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__':
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.')
moveStray(sys.argv[-1])
+318
View File
@@ -0,0 +1,318 @@
#!/usr/bin/env python3.6
# -*- coding: utf-8 -*-
# @Author: KevinMidboe
# @Date: 2017-10-12 11:55:03
# @Last Modified by: KevinMidboe
# @Last Modified time: 2017-10-22 18:54:18
import sys, logging, re, json
from urllib import parse, request
from urllib.error import URLError
from bs4 import BeautifulSoup
from os import path
import datetime
from pprint import pprint
from core import stringTime
import env_variables as env
logging.basicConfig(filename=path.dirname(__file__) + '/' + env.logfile, level=logging.INFO)
RELEASE_TYPES = ('bdremux', 'brremux', 'remux',
'bdrip', 'brrip', 'blu-ray', 'bluray', 'bdmv', 'bdr', 'bd5',
'web-cap', 'webcap', 'web cap',
'webrip', 'web rip', 'web-rip', 'web',
'webdl', 'web dl', 'web-dl', 'hdrip',
'dsr', 'dsrip', 'satrip', 'dthrip', 'dvbrip', 'hdtv', 'pdtv', 'tvrip', 'hdtvrip',
'dvdr', 'dvd-full', 'full-rip', 'iso',
'ts', 'hdts', 'hdts', 'telesync', 'pdvd', 'predvdrip',
'camrip', 'cam')
def sanitize(string, ignore_characters=None, replace_characters=None):
"""Sanitize a string to strip special characters.
:param str string: the string to sanitize.
:param set ignore_characters: characters to ignore.
:return: the sanitized string.
:rtype: str
"""
# only deal with strings
if string is None:
return
replace_characters = replace_characters or ''
ignore_characters = ignore_characters or set()
characters = ignore_characters
if characters:
string = re.sub(r'[%s]' % re.escape(''.join(characters)), replace_characters, string)
return string
def return_re_match(string, re_statement):
if string is None:
return
m = re.search(re_statement, string)
if 'Y-day' in m.group():
return datetime.timedelta(days=1).strftime('%m-%d %Y')
if 'Today' in m.group():
return datetime.datetime.now().strftime('%m-%d %Y')
return sanitize(m.group(), '\xa0', ' ')
# Can maybe be moved away from this class
# returns a number that is either the value of multiple_pages
# or if it exceeds total_pages, return total_pages.
def pagesToCount(multiple, total):
if (multiple > total):
return total
return multiple
# Should maybe not be able to set values without checking if they are valid?
class piratebay(object):
def __init__(self, query=None, page=0, sort=None, category=None):
# This should be moved to a config file
self.url = 'https://thepiratebay.org/search'
self.sortTypes = {
'size': 5,
'seed_count': 99
}
self.categoryTypes = {
'movies': 207,
'porn_movies': 505,
}
# - - -
# Req params
self.query = query
self.page = page
self.sort = sort
self.category = category
self.total_pages = 0
self.headers = {'User-Agent': 'Mozilla/5.0'}
# self.headers = {}
def build_URL_request(self):
url = '/'.join([self.url, parse.quote(self.query), str(self.page), str(self.sort), str(self.category)])
return request.Request(url, headers=self.headers)
def next_page(self):
# If page exceeds the max_page, return None
# Can either save the last query/url in the object or have it passed
# again on call to next_page
# Throw a error if it is not possible (overflow)
self.page += 1
raw_page = self.callPirateBaT()
return self.parse_raw_page_for_torrents(raw_page)
def set_total_pages(self, raw_page):
# body-id:searchResults-id:content-align:center
soup = BeautifulSoup(raw_page, 'html.parser')
content_searchResult = soup.body.find(id='SearchResults')
page_div = content_searchResult.find_next(attrs={"align": "center"})
last_page = 0
for page in page_div.find_all('a'):
last_page += 1
self.total_pages = last_page
def callPirateBaT(self):
req = self.build_URL_request()
raw_page = self.fetchURL(req).read()
logging.info('Finished searching piratebay for query | %s' % stringTime())
if raw_page is None:
raise ValueError('Search result returned no content. Please check log for error reason.')
if self.total_pages is 0:
self.set_total_pages(raw_page)
return raw_page
# Sets the search
def search(self, query, multiple_pages=1, page=0, sort=None, category=None):
# This should not be logged here, but in loop. Something else here maybe?
logging.info('Searching piratebay with query: %r, sort: %s and category: %s | %s' %
(query, sort, category, stringTime()))
if sort is not None and sort in self.sortTypes:
self.sort = self.sortTypes[sort]
else:
raise ValueError('Invalid sort category for piratebay search')
# Verify input? and reset total_pages
self.query = query
self.total_pages = 0
if str(page).isnumeric() and type(page) == int and page >= 0:
self.page = page
# TODO add category list
if category is not None and category in self.categoryTypes:
self.category = self.categoryTypes[category]
# TODO Pull most of this logic out bc it needs to also be done in next_page
raw_page = self.callPirateBaT()
torrents_found = self.parse_raw_page_for_torrents(raw_page)
# Fetch in parallel
n = pagesToCount(multiple_pages, self.total_pages)
while n > 1:
torrents_found.extend(self.next_page())
n -= 1
return torrents_found
def removeHeader(self, bs4_element):
if ('header' in bs4_element['class']):
return bs4_element.find_next('tr')
return bs4_element
def has_magnet(self, href):
return href and re.compile('magnet').search(href)
def parse_raw_page_for_torrents(self, content):
soup = BeautifulSoup(content, 'html.parser')
content_searchResult = soup.body.find(id='searchResult')
if content_searchResult is None:
logging.info('No torrents found for the search criteria.')
return None
listElements = content_searchResult.tr
torrentWrapper = self.removeHeader(listElements)
torrents_found = []
for torrentElement in torrentWrapper.find_all_next('td'):
if torrentElement.find_all("div", class_='detName'):
name = torrentElement.find('a', class_='detLink').get_text()
url = torrentElement.find('a', class_='detLink')['href']
magnet = torrentElement.find(href=self.has_magnet)
uploader = torrentElement.find('a', class_='detDesc')
if uploader is None:
uploader = torrentElement.find('i')
uploader = uploader.get_text()
info_text = torrentElement.find('font', class_='detDesc').get_text()
date = return_re_match(info_text, r"(\d+\-\d+(\s\d{4})?)|(Y\-day|Today)")
size = return_re_match(info_text, r"(\d+(\.\d+)?\s[a-zA-Z]+)")
# COULD NOT FIND HREF!
if (magnet is None):
continue
seed_and_leech = torrentElement.find_all_next(attrs={"align": "right"})
seed = seed_and_leech[0].get_text()
leech = seed_and_leech[1].get_text()
torrent = Torrent(name, magnet['href'], size, uploader, date, seed, leech, url)
torrents_found.append(torrent)
else:
# print(torrentElement)
continue
logging.info('Found %s torrents for given search criteria.' % len(torrents_found))
return torrents_found
def fetchURL(self, req):
try:
response = request.urlopen(req)
except URLError as e:
if hasattr(e, 'reason'):
logging.error('We failed to reach a server with request: %s' % req.full_url)
logging.error('Reason: %s' % e.reason)
elif hasattr(e, 'code'):
logging.error('The server couldn\'t fulfill the request.')
logging.error('Error code: ', e.code)
else:
return response
class Torrent(object):
def __init__(self, name, magnet=None, size=None, uploader=None, date=None,
seed_count=None, leech_count=None, url=None):
self.name = name
self.magnet = magnet
self.size = size
self.uploader = uploader
self.date = date
self.seed_count = seed_count
self.leech_count = leech_count
self.url = url
def find_release_type(self):
name = self.name.casefold()
return [r_type for r_type in RELEASE_TYPES if r_type in name]
def get_all_attr(self):
return ({'name': self.name, 'magnet': self.magnet,'uploader': self.uploader,'size': self.size,'date': self.date,'seed': self.seed_count,'leech': self.leech_count,'url': self.url})
def __repr__(self):
return '<%s [%r]>' % (self.__class__.__name__, self.name)
# This should be done front_end!
# I.E. filtering like this should be done in another script
# and should be done with the shared standard for types.
# PS: Is it the right move to use a shared standard? What
# happens if it is no longer public?
def chooseCandidate(torrent_list):
interesting_torrents = []
match_release_type = ['bdremux', 'brremux', 'remux', 'bdrip', 'brrip', 'blu-ray', 'bluray', 'bdmv', 'bdr', 'bd5']
for torrent in torrent_list:
intersecting_release_types = set(torrent.find_release_type()) & set(match_release_type)
size, _, size_id = torrent.size.partition(' ')
if intersecting_release_types and int(torrent.seed_count) > 0 and float(size) > 4 and size_id == 'GiB':
# print('{} : {} : {} {}'.format(torrent.name, torrent.size, torrent.seed_count, torrent.magnet))
interesting_torrents.append(torrent.get_all_attr())
# else:
# print('Denied match! %s : %s : %s' % (torrent.name, torrent.size, torrent.seed_count))
return interesting_torrents
def searchTorrentSite(query, site='piratebay'):
pirate = piratebay()
torrents_found = pirate.search(query, page=0, multiple_pages=3, sort='size')
candidates = {}
if (torrents_found):
candidates = chooseCandidate(torrents_found)
print(json.dumps(candidates))
# torrents_found = pirate.next_page()
# pprint(torrents_found)
# candidates = chooseCandidate(torrents_found)
# Can autocall to next_page in a looped way to get more if nothing is found
# and there is more pages to be looked at
def main():
query = sys.argv[1]
searchTorrentSite(query)
if __name__ == '__main__':
main()
+57
View File
@@ -0,0 +1,57 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# @Author: KevinMidboe
# @Date: 2017-07-11 19:16:23
# @Last Modified by: KevinMidboe
# @Last Modified time: 2017-07-11 19:16:23
import fire, re, os
class seasonMover(object):
''' Moving multiple files to multiple folders with
identifer '''
workingDir = os.getcwd()
def create(self, name, interval):
pass
def move(self, fileSyntax, folderName):
episodeRange = self.findInterval(fileSyntax)
self.motherMover(fileSyntax, folderName, episodeRange)
def findInterval(self, item):
if (re.search(r'\((.*)\)', item) is None):
raise ValueError('Need to declare an identifier e.g. (1..3) in: \n\t' + item)
start = int(re.search('\((\d+)\.\.', item).group(1))
end = int(re.search('\.\.(\d+)\)', item).group(1))
return list(range(start, end+1))
def removeUploadSign(self, file):
match = re.search('-[a-zA-Z\[\]\-]*.[a-z]{3}', file)
if match:
uploader = match.group(0)[:-4]
return re.sub(uploader, '', file)
return file
def motherMover(self, fileSyntax, folderName, episodeRange):
# Call for sub of fileList
# TODO check if range is same as folderContent
for episode in episodeRange:
leadingZeroNumber = "%02d" % episode
fileName = re.sub(r'\((.*)\)', leadingZeroNumber, fileSyntax)
oldPath = os.path.join(self.workingDir,fileName)
newFolder = os.path.join(self.workingDir, folderName + leadingZeroNumber)
newPath = os.path.join(newFolder, self.removeUploadSign(fileName))
os.makedirs(newFolder)
os.rename(oldPath, newPath)
# print(newFolder)
# print(oldPath + ' --> ' + newPath)
if __name__ == '__main__':
fire.Fire(seasonMover)
+111
View File
@@ -0,0 +1,111 @@
# -*- coding: utf-8 -*-
import codecs
import logging
import os
import chardet
import hashlib
from video import Episode, Movie
from utils import sanitize
from langdetect import detect
logger = logging.getLogger(__name__)
#: Subtitle extensions
SUBTITLE_EXTENSIONS = ('.srt', '.sub')
class Subtitle(object):
"""Base class for subtitle.
:param language: language of the subtitle.
:type language: :class:`~babelfish.language.Language`
:param bool hearing_impaired: whether or not the subtitle is hearing impaired.
:param page_link: URL of the web page from which the subtitle can be downloaded.
:type page_link: str
:param encoding: Text encoding of the subtitle.
:type encoding: str
"""
#: Name of the provider that returns that class of subtitle
provider_name = ''
def __init__(self, name, parent_path, series, season, episode, language=None, hash=None, container=None, format=None, sdh=False):
#: Language of the subtitle
self.name = name
self.parent_path = parent_path
self.series = series
self.season = season
self.episode = episode
self.language=language
self.hash = hash
self.container = container
self.format = format
self.sdh = sdh
@classmethod
def fromguess(cls, name, parent_path, guess):
if not (guess['type'] == 'movie' or guess['type'] == 'episode'):
raise ValueError('The guess must be an episode guess')
if 'title' not in guess:
raise ValueError('Insufficient data to process the guess')
sdh = 'sdh' in name.lower()
if guess['type'] is 'episode':
return cls(name, parent_path, guess.get('title', 1), guess.get('season'), guess['episode'],
container=guess.get('container'), format=guess.get('format'), sdh=sdh)
elif guess['type'] is 'movie':
return cls(name, parent_path, guess.get('title', 1), container=guess.get('container'),
format=guess.get('format'), sdh=sdh)
def getLanguage(self):
f = open(os.path.join(self.parent_path, self.name), 'r', encoding='ISO-8859-15')
language = detect(f.read())
f.close()
return language
def __hash__(self):
return hashlib.md5("b'{}'".format(str(self.series) + str(self.season) + str(self.episode)).encode()).hexdigest()
def __repr__(self):
return '<%s %s [%sx%s]>' % (self.__class__.__name__, self.series, self.season, str(self.episode))
def get_subtitle_path(subtitles_path, language=None, extension='.srt'):
"""Get the subtitle path using the `subtitles_path` and `language`.
:param str subtitles_path: path to the subtitle.
:param language: language of the subtitle to put in the path.
:type language: :class:`~babelfish.language.Language`
:param str extension: extension of the subtitle.
:return: path of the subtitle.
:rtype: str
"""
subtitle_root = os.path.splitext(subtitles_path)[0]
if language:
subtitle_root += '.' + str(language)
return subtitle_root + extension
+38
View File
@@ -0,0 +1,38 @@
# -*- coding: utf-8 -*-
from datetime import datetime
import hashlib
import os
import re
import struct
def sanitize(string, ignore_characters=None):
"""Sanitize a string to strip special characters.
:param str string: the string to sanitize.
:param set ignore_characters: characters to ignore.
:return: the sanitized string.
:rtype: str
"""
# only deal with strings
if string is None:
return
ignore_characters = ignore_characters or set()
# replace some characters with one space
# characters = {'-', ':', '(', ')', '.'} - ignore_characters
# if characters:
# string = re.sub(r'[%s]' % re.escape(''.join(characters)), ' ', string)
# remove some characters
characters = {'\''} - ignore_characters
if characters:
string = re.sub(r'[%s]' % re.escape(''.join(characters)), '', string)
# replace multiple spaces with one
string = re.sub(r'\s+', ' ', string)
# strip and lower case
return string.strip().lower()
+233
View File
@@ -0,0 +1,233 @@
#!/usr/bin/env python3.6
# -*- coding: utf-8 -*-
# @Author: KevinMidboe
# @Date: 2017-08-26 08:23:18
# @Last Modified by: KevinMidboe
# @Last Modified time: 2017-09-29 13:56:21
from guessit import guessit
import os
import hashlib, tvdb_api
#: Video extensions
VIDEO_EXTENSIONS = ('.3g2', '.3gp', '.3gp2', '.3gpp', '.60d', '.ajp', '.asf', '.asx', '.avchd', '.avi', '.bik',
'.bix', '.box', '.cam', '.dat', '.divx', '.dmf', '.dv', '.dvr-ms', '.evo', '.flc', '.fli',
'.flic', '.flv', '.flx', '.gvi', '.gvp', '.h264', '.m1v', '.m2p', '.m2ts', '.m2v', '.m4e',
'.m4v', '.mjp', '.mjpeg', '.mjpg', '.mkv', '.moov', '.mov', '.movhd', '.movie', '.movx', '.mp4',
'.mpe', '.mpeg', '.mpg', '.mpv', '.mpv2', '.mxf', '.nsv', '.nut', '.ogg', '.ogm' '.ogv', '.omf',
'.ps', '.qt', '.ram', '.rm', '.rmvb', '.swf', '.ts', '.vfw', '.vid', '.video', '.viv', '.vivo',
'.vob', '.vro', '.wm', '.wmv', '.wmx', '.wrap', '.wvx', '.wx', '.x264', '.xvid')
class Video(object):
"""Base class for videos.
Represent a video, existing or not.
:param str name: name or path of the video.
:param str format: format of the video (HDTV, WEB-DL, BluRay, ...).
:param str release_group: release group of the video.
:param str resolution: resolution of the video stream (480p, 720p, 1080p or 1080i).
:param str video_codec: codec of the video stream.
:param str audio_codec: codec of the main audio stream.
:param str imdb_id: IMDb id of the video.
:param dict hashes: hashes of the video file by provider names.
:param int size: size of the video file in bytes.
:param set subtitle_languages: existing subtitle languages.
"""
def __init__(self, name, format=None, release_group=None, resolution=None, video_codec=None, audio_codec=None,
imdb_id=None, hashes=None, size=None, subtitle_languages=None):
#: Name or path of the video
self.name = name
#: Format of the video (HDTV, WEB-DL, BluRay, ...)
self.format = format
#: Release group of the video
self.release_group = release_group
#: Resolution of the video stream (480p, 720p, 1080p or 1080i)
self.resolution = resolution
#: Codec of the video stream
self.video_codec = video_codec
#: Codec of the main audio stream
self.audio_codec = audio_codec
#: IMDb id of the video
self.imdb_id = imdb_id
#: Hashes of the video file by provider names
self.hashes = hashes or {}
#: Size of the video file in bytes
self.size = size
#: Existing subtitle languages
self.subtitle_languages = subtitle_languages or set()
@property
def exists(self):
"""Test whether the video exists"""
return os.path.exists(self.name)
@property
def age(self):
"""Age of the video"""
if self.exists:
return datetime.utcnow() - datetime.utcfromtimestamp(os.path.getmtime(self.name))
return timedelta()
@classmethod
def fromguess(cls, name, parent_path, guess):
"""Create an :class:`Episode` or a :class:`Movie` with the given `name` based on the `guess`.
:param str name: name of the video.
:param dict guess: guessed data.
:raise: :class:`ValueError` if the `type` of the `guess` is invalid
"""
if guess['type'] == 'episode':
return Episode.fromguess(name, parent_path, guess)
if guess['type'] == 'movie':
return Movie.fromguess(name, guess)
raise ValueError('The guess must be an episode or a movie guess')
@classmethod
def fromname(cls, name):
"""Shortcut for :meth:`fromguess` with a `guess` guessed from the `name`.
:param str name: name of the video.
"""
return cls.fromguess(name, guessit(name))
def __repr__(self):
return '<%s [%r]>' % (self.__class__.__name__, self.name)
def __hash__(self):
return hash(self.name)
class Episode():
"""Episode :class:`Video`.
:param str series: series of the episode.
:param int season: season number of the episode.
:param int episode: episode number of the episode.
:param str title: title of the episode.
:param int year: year of the series.
:param bool original_series: whether the series is the first with this name.
:param int tvdb_id: TVDB id of the episode.
:param \*\*kwargs: additional parameters for the :class:`Video` constructor.
"""
def __init__(self, name, parent_path, series, season, episode, year=None, original_series=True, tvdb_id=None,
series_tvdb_id=None, series_imdb_id=None, release_group=None, video_codec=None, container=None,
format=None, screen_size=None, **kwargs):
super(Episode, self).__init__()
self.name = name
self.parent_path = parent_path
#: Series of the episode
self.series = series
#: Season number of the episode
self.season = season
#: Episode number of the episode
self.episode = episode
#: Year of series
self.year = year
#: The series is the first with this name
self.original_series = original_series
#: TVDB id of the episode
self.tvdb_id = tvdb_id
#: TVDB id of the series
self.series_tvdb_id = series_tvdb_id
#: IMDb id of the series
self.series_imdb_id = series_imdb_id
# The release group of the episode
self.release_group = release_group
# The video vodec of the series
self.video_codec = video_codec
# The Video container of the episode
self.container = container
# The Video format of the episode
self.format = format
# The Video screen_size of the episode
self.screen_size = screen_size
@classmethod
def fromguess(cls, name, parent_path, guess):
if guess['type'] != 'episode':
raise ValueError('The guess must be an episode guess')
if 'title' not in guess or 'episode' not in guess:
raise ValueError('Insufficient data to process the guess')
return cls(name, parent_path, guess['title'], guess.get('season', 1), guess['episode'],
year=guess.get('year'), original_series='year' not in guess, release_group=guess.get('release_group'),
video_codec=guess.get('video_codec'), audio_codec=guess.get('audio_codec'), container=guess.get('container'),
format=guess.get('format'), screen_size=guess.get('screen_size'))
@classmethod
def fromname(cls, name):
return cls.fromguess(name, guessit(name, {'type': 'episode'}))
def __hash__(self):
return hashlib.md5("b'{}'".format(str(self.series) + str(self.season) + str(self.episode)).encode()).hexdigest()
# THE EP NUMBER IS CONVERTED TO STRING AS A QUICK FIX FOR MULTIPLE NUMBERS IN ONE
def __repr__(self):
if self.year is None:
return '<%s [%r, %sx%s]>' % (self.__class__.__name__, self.series, self.season, str(self.episode))
return '<%s [%r, %d, %sx%s]>' % (self.__class__.__name__, self.series, self.year, self.season, str(self.episode))
class Movie():
"""Movie :class:`Video`.
:param str title: title of the movie.
:param int year: year of the movie.
:param \*\*kwargs: additional parameters for the :class:`Video` constructor.
"""
def __init__(self, name, title, year=None, format=None, **kwargs):
super(Movie, self).__init__()
#: Title of the movie
self.title = title
#: Year of the movie
self.year = year
self.format = format
@classmethod
def fromguess(cls, name, guess):
if guess['type'] != 'movie':
raise ValueError('The guess must be a movie guess')
if 'title' not in guess:
raise ValueError('Insufficient data to process the guess')
return cls(name, guess['title'], format=guess.get('format'), release_group=guess.get('release_group'),
resolution=guess.get('screen_size'), video_codec=guess.get('video_codec'),
audio_codec=guess.get('audio_codec'), year=guess.get('year'))
@classmethod
def fromname(cls, name):
return cls.fromguess(name, guessit(name, {'type': 'movie'}))
def __repr__(self):
if self.year is None:
return '<%s [%r]>' % (self.__class__.__name__, self.title)
return '<%s [%r, %d]>' % (self.__class__.__name__, self.title, self.year)
+58
View File
@@ -0,0 +1,58 @@
# 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
Binary file not shown.
+25
View File
@@ -0,0 +1,25 @@
import React, { Component } from 'react';
import { HashRouter as Router, Route, Switch, IndexRoute } from 'react-router-dom';
import SearchRequest from './components/SearchRequest.jsx';
import AdminComponent from './components/admin/Admin.jsx';
class Root extends Component {
// We need to provide a list of routes
// for our app, and in this case we are
// doing so from a Root component
render() {
return (
<Router>
<Switch>
<Route exact path='/' component={SearchRequest} />
<Route path='/admin/:request' component={AdminComponent} />
<Route path='/admin' component={AdminComponent} />
</Switch>
</Router>
);
}
}
export default Root;
+41
View File
@@ -0,0 +1,41 @@
@font-face {
font-family: "din";
src: url('/app/DIN-Regular-webfont.woff')
}
html {
font-family: 'din', 'Open Sans', sans-serif;
display: inline-block;
color:red;
}
#requestMovieList {
display: flex;
flex-wrap: wrap;
justify-content: center;
}
.movie_wrapper {
color:red;
display: flex;
align-content: center;
width: 30%;
background-color: #ffffff;
height: 231px;
margin: 20px;
-webkit-box-shadow: 0px 0px 5px 1px rgba(0,0,0,0.15);
-moz-box-shadow: 0px 0px 5px 1px rgba(0,0,0,0.15);
box-shadow: 0px 0px 5px 1px rgba(0,0,0,0.15);
}
.movie_content {
margin-left: 15px;
}
.movie_header {
font-size: 1.6em;
}
+26
View File
@@ -0,0 +1,26 @@
import React from 'react';
export function getCookie(cname) {
var name = cname + "=";
var decodedCookie = decodeURIComponent(document.cookie);
var ca = decodedCookie.split(';');
for(var i = 0; i <ca.length; i++) {
var c = ca[i];
while (c.charAt(0) == ' ') {
c = c.substring(1);
}
if (c.indexOf(name) == 0) {
return c.substring(name.length, c.length);
}
}
return false;
}
export function setCookie(cname, cvalue, exdays) {
var d = new Date();
d.setTime(d.getTime() + (exdays*24*60*60*1000));
var expires = "expires="+ d.toUTCString();
document.cookie = cname + "=" + cvalue + ";" + expires + ";path=/";
}
@@ -0,0 +1,63 @@
import React from 'react';
class FetchData extends React.Component {
constructor(props){
super(props)
this.state = {
playing: [],
hei: '1',
intervalId: null,
url: ''
}
}
componentDidMount(){
var that = this;
fetch("https://apollo.kevinmidboe.com/api/v1/plex/playing").then(
function(response){
response.json().then(function(data){
that.setState({
playing: that.state.playing.concat(data.video)
})
})
}
)
}
componentWillUnmount() {
// use intervalId from the state to clear the interval
clearInterval(this.state.intervalId);
}
getPlaying() {
if (this.state.playing.length != 0) {
return this.state.playing.map((playingObj) => {
if (playingObj.type === 'episode') {
console.log('episode')
return ([
<span>{playingObj.title}</span>,
<span>{playingObj.season}</span>,
<span>{playingObj.episode}</span>
])
} else if (playingObj.type === 'movie') {
console.log('movie')
return ([
<span>{playingObj.title}</span>
])
}
})
} else {
return (<span>Nothing playing</span>)
}
}
render(){
return(
<div className="FetchData">{this.getPlaying()}</div>
);
}
}
export default FetchData;
@@ -0,0 +1,266 @@
import React from 'react';
import requestElement from './styles/requestElementStyle.jsx'
import { getCookie } from './Cookie.jsx';
class DropdownList extends React.Component {
constructor(props) {
super(props);
this.state = {
filter: ['all', 'requested', 'downloading', 'downloaded'],
sort: ['requested_date', 'name', 'status', 'requested_by', 'ip', 'user_agent'],
status: ['requested', 'downloading', 'downloaded'],
}
}
render() {
const {elementType, elementId, elementStatus, elementCallback, props} = this.props;
console.log(elementCallback('downloaded'))
switch (elementType) {
case 'status':
return (
<div>HERE</div>
)
}
return (
<div {...props}>
</div>
);
}
}
class RequestElement extends React.Component {
constructor(props) {
super(props);
this.state = {
dropDownState: undefined,
}
}
filterRequestList(requestList, filter) {
if (filter === 'all')
return requestList
if (filter === 'movie' || filter === 'show')
return requestList.filter(item => item.type === filter)
return requestList.filter(item => item.status === filter)
}
sortRequestList(requestList, sort_type, reversed) {
requestList.sort(function(a,b) {
if(a[sort_type] < b[sort_type]) return -1;
if(a[sort_type] > b[sort_type]) return 1;
return 0;
});
if (reversed)
requestList.reverse();
}
userAgent(agent) {
if (agent) {
try {
return agent.split(" ")[1].replace(/[\(\;]/g, '');
}
catch(e) {
return agent;
}
}
return '';
}
updateDropDownState(status) {
if (status !== this.dropDownState) {
this.dropDownState = status;
}
}
ItemsStatusDropdown(id, type, status) {
return (
<div>
<select id="lang"
defaultValue={status}
onChange={event => this.updateDropDownState(event.target.value)}
>
<option value='requested'>Requested</option>
<option value='downloading'>Downloading</option>
<option value='downloaded'>Downloaded</option>
</select>
<button onClick={() => { this.updateRequestedItem(id, type)}}>Update Status</button>
</div>
)
}
updateRequestedItem(id, type) {
console.log(id, type, this.dropDownState);
Promise.resolve()
fetch('https://apollo.kevinmidboe.com/api/v1/plex/request/' + id, {
method: 'PUT',
headers: {
'Content-type': 'application/json',
'authorization': getCookie('token')
},
body: JSON.stringify({
type: type,
status: this.dropDownState,
})
})
.then(response => {
if (response.status !== 200) {
console.log('error');
}
response.json()
.then(data => {
if (data.success === true) {
console.log('UPDATED :', id, ' with ', this.dropDownState)
}
})
})
.catch(error => {
new Error(error);
})
}
createHTMLElement(data, index) {
var posterPath = 'https://image.tmdb.org/t/p/w300' + data.image_path;
return (
<div style={requestElement.wrappingDiv} key={index}>
<img style={requestElement.requestPoster} src={posterPath}></img>
<div style={requestElement.infoDiv}>
<span><b>Name</b>: {data.name} </span><br></br>
<span><b>Year</b>: {data.year}</span><br></br>
<span><b>Type</b>: {data.type}</span><br></br>
<span><b>Status</b>: {data.status}</span><br></br>
<span><b>Address</b>: {data.ip}</span><br></br>
<span><b>Requested Data:</b> {data.requested_date}</span><br></br>
<span><b>Requested By:</b> {data.requested_by}</span><br></br>
<span><b>Agent</b>: { this.userAgent(data.user_agent) }</span><br></br>
</div>
{ this.ItemsStatusDropdown(data.id, data.type, data.status) }
</div>
)
}
render() {
const {requestedElementsList, requestedElementsFilter, requestedElementsSort, props} = this.props;
var filteredRequestedList = this.filterRequestList(requestedElementsList, requestedElementsFilter)
this.sortRequestList(filteredRequestedList, requestedElementsSort.value, requestedElementsSort.reversed)
return (
<div {...props} style={requestElement.bodyDiv}>
{filteredRequestedList.map((requestItem, index) => this.createHTMLElement(requestItem, index))}
</div>
);
}
}
class FetchRequested extends React.Component {
constructor(props){
super(props)
this.state = {
requested_objects: [],
filter: 'all',
sort: {
value: 'requested_date',
reversed: false
},
}
}
componentDidMount(){
Promise.resolve()
fetch('https://apollo.kevinmidboe.com/api/v1/plex/requests/all', {
method: 'GET',
headers: {
'Content-type': 'application/json',
'authorization': getCookie('token')
}
})
.then(response => {
if (response.status !== 200) {
console.log('error');
}
response.json()
.then(data => {
if (data.success === true) {
this.setState({
requested_objects: data.requestedItems
})
}
})
})
.catch(error => {
new Error(error);
})
}
changeFilter(filter) {
this.setState({
filter: filter
})
}
updateSort(sort=null, reverse=false) {
if (sort) {
this.setState({
sort: { value: sort, reversed: reverse }
})
}
else {
this.setState({
sort: { value: this.state.sort.value, reversed: reverse }
})
}
}
render(){
return(
<div>
<select id="lang" onChange={event => this.changeFilter(event.target.value)} value={this.state.value}>
<option value="all">All</option>
<option value="requested">Requested</option>
<option value="downloading">Downloading</option>
<option value="downloaded">Downloaded</option>
<option value='movie'>Movies</option>
<option value='show'>Shows</option>
</select>
<select id="lang" onChange={event => this.updateSort(event.target.value)} value={this.state.value}>
<option value='requested_date'>Date</option>
<option value='name'>Title</option>
<option value='status'>Status</option>
<option value='requested_by'>Requested By</option>
<option value='ip'>Address</option>
<option value='user_agent'>Agent</option>
</select>
<button onClick={() => {this.updateSort(null, !this.state.sort.reversed)}}>Reverse</button>
<RequestElement
requestedElementsList={this.state.requested_objects}
requestedElementsFilter={this.state.filter}
requestedElementsSort={this.state.sort}
/>
</div>
)
}
}
export default FetchRequested;
+11
View File
@@ -0,0 +1,11 @@
import React from 'react'
import { Link } from 'react-router-dom'
// The Header creates links that can be used to navigate
// between routes.
const Header = () => (
<header>
</header>
)
export default Header
@@ -29,7 +29,6 @@ class ListStrays extends React.Component {
{this.state.strays.map((strayObj) => {
if (strayObj.verified == 0) {
var url = "https://kevinmidboe.com/seasoned/verified.html?id=" + strayObj.id;
console.log(url);
return ([
<span key={strayObj.id}>{strayObj.name}</span>,
<a href={url}>{strayObj.id}</a>
@@ -0,0 +1,10 @@
// components/NotFound.js
import React from 'react';
const NotFound = () =>
<div>
<h3>404 page not found</h3>
<p>We are sorry but the page you are looking for does not exist.</p>
</div>
export default NotFound;
@@ -0,0 +1,126 @@
import React from 'react';
import Notifications, {notify} from 'react-notify-toast';
// StyleComponents
import searchObjectCSS from './styles/searchObject.jsx';
import buttonsCSS from './styles/buttons.jsx';
import InfoButton from './buttons/InfoButton.jsx';
var MediaQuery = require('react-responsive');
import { fetchJSON } from './http.jsx';
import Interactive from 'react-interactive';
class SearchObject {
constructor(object) {
this.id = object.id;
this.title = object.title;
this.year = object.year;
this.type = object.type;
this.rating = object.rating;
this.poster = object.poster_path;
this.background = object.background_path;
this.matchedInPlex = object.matchedInPlex;
this.summary = object.summary;
}
requestExisting(movie) {
console.log('Exists', movie);
}
requestMovie() {
fetchJSON('https://apollo.kevinmidboe.com/api/v1/plex/request/' + this.id + '?type='+this.type, 'POST')
.then((response) => {
console.log(response);
notify.show(this.title + ' requested!', 'success', 3000);
})
.catch((e) => {
console.error('Request movie fetch went wrong: '+ e);
})
}
getElement(index) {
const element_key = index + this.id;
if (this.poster == null || this.poster == undefined) {
var posterPath = 'https://openclipart.org/image/2400px/svg_to_png/211479/Simple-Image-Not-Found-Icon.png'
} else {
var posterPath = 'https://image.tmdb.org/t/p/w185' + this.poster;
}
var backgroundPath = 'https://image.tmdb.org/t/p/w640_and_h360_bestv2/' + this.background;
var foundInPlex;
if (this.matchedInPlex) {
foundInPlex = <Interactive
as='button'
onClick={() => {this.requestExisting(this)}}
style={buttonsCSS.submit}
focus={buttonsCSS.submit_hover}
hover={buttonsCSS.submit_hover}>
<span>Request Anyway</span>
</Interactive>;
} else {
foundInPlex = <Interactive
as='button'
onClick={() => {this.requestMovie()}}
style={buttonsCSS.submit}
focus={buttonsCSS.submit_hover}
hover={buttonsCSS.submit_hover}>
<span>&#x0002B; Request</span>
</Interactive>;
}
// TODO go away from using mediaQuery, and create custom resizer
return (
<div key={element_key}>
<Notifications />
<div style={searchObjectCSS.container} key={this.id}>
<MediaQuery minWidth={600}>
<div style={searchObjectCSS.posterContainer}>
<img style={searchObjectCSS.posterImage} id='poster' src={posterPath}></img>
</div>
<span style={searchObjectCSS.title_large}>{this.title}</span>
<br></br>
<span style={searchObjectCSS.stats_large}>
Released: { this.year } | Rating: {this.rating} | Type: {this.type}
</span>
<br></br>
<span style={searchObjectCSS.summary}>{this.summary}</span>
<br></br>
</MediaQuery>
<MediaQuery maxWidth={600}>
<img src={ backgroundPath } style={searchObjectCSS.backgroundImage}></img>
<span style={searchObjectCSS.title_small}>{this.title}</span>
<br></br>
<span style={searchObjectCSS.stats_small}>Released: {this.year} | Rating: {this.rating}</span>
</MediaQuery>
<div style={searchObjectCSS.buttons}>
{foundInPlex}
<InfoButton id={this.id} type={this.type} />
</div>
</div>
<MediaQuery maxWidth={600}>
<br />
</MediaQuery>
<div style={searchObjectCSS.dividerRow}>
<div style={searchObjectCSS.itemDivider}></div>
</div>
</div>
)
}
}
export default SearchObject;
@@ -0,0 +1,464 @@
import React from 'react';
import URI from 'urijs';
import InfiniteScroll from 'react-infinite-scroller';
// StyleComponents
import searchRequestCSS from './styles/searchRequestStyle.jsx';
import SearchObject from './SearchObject.jsx';
import Loading from './images/loading.jsx'
import { fetchJSON } from './http.jsx';
import { getCookie } from './Cookie.jsx';
var MediaQuery = require('react-responsive');
// TODO add option for searching multi, movies or tv shows
class SearchRequest extends React.Component {
constructor(props){
super(props)
// Constructor with states holding the search query and the element of reponse.
this.state = {
lastApiCallURI: '',
searchQuery: '',
responseMovieList: null,
movieFilter: false,
showFilter: false,
discoverType: '',
page: 1,
resultHeader: '',
loadResults: false,
scrollHasMore: true,
loading: false,
}
this.allowedListTypes = ['discover', 'popular', 'nowplaying', 'upcoming']
this.baseUrl = 'https://apollo.kevinmidboe.com/api/v1/tmdb/list';
// this.baseUrl = 'http://localhost:31459/api/v1/tmdb/list';
this.searchUrl = 'https://apollo.kevinmidboe.com/api/v1/plex/request';
// this.searchUrl = 'http://localhost:31459/api/v1/plex/request';
}
componentWillMount(){
var that = this;
// this.setState({responseMovieList: null})
this.resetPageNumber();
this.state.loadResults = true;
this.fetchTmdbList(this.allowedListTypes[Math.floor(Math.random()*this.allowedListTypes.length)]);
}
// Handles all errors of the response of a fetch call
handleErrors(response) {
if (!response.ok)
throw Error(response.status);
return response;
}
handleQueryError(response) {
if (!response.ok) {
if (response.status === 404) {
this.setState({
responseMovieList: <h1>Nothing found for search query: { this.findQueryInURI(uri) }</h1>
})
}
console.log('handleQueryError: ', error);
}
return response;
}
// Unpacks the query value of a uri
findQueryValueInURI(uri) {
let uriSearchValues = uri.query(true);
let queryValue = uriSearchValues['query']
return queryValue;
}
// Unpacks the page value of a uri
findPageValueInURI(uri) {
let uriSearchValues = uri.query(true);
let queryValue = uriSearchValues['page']
return queryValue;
}
resetPageNumber() {
this.state.page = 1;
}
setLoading(value) {
this.setState({
loading: value
});
}
// Test this by calling missing endpoint or 404 query and see what code
// and filter the error message based on the code.
// Calls a uri and returns the response as json
callURI(uri, method, data={}) {
return fetch(uri, {
method: method,
headers: new Headers({
'Content-Type': 'application/json',
'authorization': getCookie('token'),
'loggedinuser': getCookie('loggedInUser'),
})
})
.then(response => { return response })
.catch((error) => {
throw Error(error);
});
}
// Saves the input string as a h1 element in responseMovieList state
fillResponseMovieListWithError(msg) {
this.setState({
responseMovieList: <h1>{ msg }</h1>
})
}
// Here we first call api for a search with the input uri, handle any errors
// and fill the reponseData from api into the state of reponseMovieList as movieObjects
callSearchFillMovieList(uri) {
Promise.resolve()
.then(() => this.callURI(uri, 'GET'))
.then(response => {
// If we get a error code for the request
if (!response.ok) {
if (response.status === 404) {
if (this.findPageValueInURI(new URI(response.url)) > 1) {
this.state.scrollHasMore = false;
console.log(this.state.scrollHasMore)
return null
let returnMessage = 'this is the return mesasge than will never be delivered'
let theSecondReturnMsg = 'this is the second return messag ethat will NEVE be delivered'
}
else {
let errorMsg = 'Nothing found for the search query: ' + this.findQueryValueInURI(uri);
this.fillResponseMovieListWithError(errorMsg)
}
}
else {
let errorMsg = 'Error fetching query from server ' + this.response.status;
this.fillResponseMovieListWithError(errorMsg)
}
}
// Convert to json and update the state of responseMovieList with the results of the api call
// mapped as a SearchObject.
response.json()
.then(responseData => {
if (this.state.page === 1) {
this.setState({
responseMovieList: responseData.results.map((searchResultItem, index) => this.createMovieObjects(searchResultItem, index)),
lastApiCallURI: uri // Save the value of the last sucessfull api call
})
} else {
let responseMovieObjects = responseData.results.map((searchResultItem, index) => this.createMovieObjects(searchResultItem, index));
let growingReponseMovieObjectList = this.state.responseMovieList.concat(responseMovieObjects);
this.setState({
responseMovieList: growingReponseMovieObjectList,
lastApiCallURI: uri // Save the value of the last sucessfull api call
})
}
})
.catch((error) => {
console.log('CallSearchFillMovieList: ', error)
})
})
.catch((error) => {
console.log('Something went wrong when fetching query.', error)
})
}
callListFillMovieList(uri) {
// Write loading animation
Promise.resolve()
.then(() => this.callURI(uri, 'GET', undefined))
.then(response => {
// If we get a error code for the request
if (!response.ok) {
if (response.status === 404) {
let errorMsg = 'List not found';
this.fillResponseMovieListWithError(errorMsg)
}
else {
let errorMsg = 'Error fetching list from server ' + this.response.status;
this.fillResponseMovieListWithError(errorMsg)
}
}
// Convert to json and update the state of responseMovieList with the results of the api call
// mapped as a SearchObject.
response.json()
.then(responseData => {
if (this.state.page === 1) {
this.setState({
responseMovieList: responseData.results.map((searchResultItem, index) => this.createMovieObjects(searchResultItem, index)),
lastApiCallURI: uri // Save the value of the last sucessfull api call
})
} else {
let responseMovieObjects = responseData.results.map((searchResultItem, index) => this.createMovieObjects(searchResultItem, index));
let growingReponseMovieObjectList = this.state.responseMovieList.concat(responseMovieObjects);
this.setState({
responseMovieList: growingReponseMovieObjectList,
lastApiCallURI: uri // Save the value of the last sucessfull api call
})
}
})
})
.catch((error) => {
console.log('Something went wrong when fetching query.', error)
})
}
searchSeasonedRequest() {
this.state.resultHeader = 'Search result for: ' + this.state.searchQuery;
// Build uri with the url for searching requests
var uri = new URI(this.searchUrl);
// Add input of search query and page count to the uri payload
uri = uri.search({ 'query': this.state.searchQuery, 'page': this.state.page });
if (this.state.showFilter)
uri = uri.addSearch('type', 'show');
else
if (this.state.movieFilter)
uri = uri.addSearch('type', 'movie')
// Send uri to call and fill the response list with movie/show objects
this.callSearchFillMovieList(uri);
}
fetchTmdbList(tmdbListType) {
console.log(tmdbListType)
// Check if it is a whitelisted list, this should be replaced with checking if the return call is 500
if (this.allowedListTypes.indexOf(tmdbListType) === -1)
throw Error('Invalid discover type: ' + tmdbListType);
this.state.responseMovieList = []
// Captialize the first letter of and save the discoverQueryType to resultHeader state.
this.state.resultHeader = tmdbListType.toLowerCase().replace(/\b[a-z]/g, function(letter) {
return letter.toUpperCase();
});
// Build uri with the url for searching requests
var uri = new URI(this.baseUrl);
uri.segment(tmdbListType);
// Add input of search query and page count to the uri payload
uri = uri.search({ 'page': this.state.page });
if (this.state.showFilter)
uri = uri.addSearch('type', 'show');
// Send uri to call and fill the response list with movie/show objects
this.callListFillMovieList(uri);
}
// Updates the internal state of the query search field.
updateQueryState(event){
this.setState({
searchQuery: event.target.value
});
}
// For checking if the enter key was pressed in the search field.
_handleQueryKeyPress(e) {
if (e.key === 'Enter') {
// this.fetchQuery();
// Reset page number for a new search
this.resetPageNumber();
this.searchSeasonedRequest();
}
}
// When called passes the variable to SearchObject and calls it's interal function for
// generating the wanted HTML
createMovieObjects(item, index) {
let movie = new SearchObject(item);
return movie.getElement(index);
}
toggleFilter(filterType) {
if (filterType == 'movies') {
this.setState({
movieFilter: !this.state.movieFilter
})
console.log(this.state.movieFilter);
}
else if (filterType == 'shows') {
this.setState({
showFilter: !this.state.showFilter
})
console.log(this.state.showFilter);
}
}
pageBackwards() {
if (this.state.page > 1) {
let pageNumber = this.state.page - 1;
let uri = this.state.lastApiCallURI;
// Augment the page number of the uri with a callback
uri.search(function(data) {
data.page = pageNumber;
});
// Call the api with the new uri
this.callSearchFillMovieList(uri);
// Update state of our page number after the call is done
this.state.page = pageNumber;
}
}
// TODO need to get total page number and save in a state to not overflow
pageForwards() {
// Wrap this in the check
let pageNumber = this.state.page + 1;
let uri = this.state.lastApiCallURI;
// Augment the page number of the uri with a callback
uri.search(function(data) {
data.page = pageNumber;
});
// Call the api with the new uri
this.callSearchFillMovieList(uri);
// Update state of our page number after the call is done
this.state.page = pageNumber;
}
movieToggle() {
if (this.state.movieFilter)
return <span style={searchRequestCSS.searchFilterActive}
className="search_category hvrUnderlineFromCenter"
onClick={() => {this.toggleFilter('movies')}}
id="category_active">Movies</span>
else
return <span style={searchRequestCSS.searchFilterNotActive}
className="search_category hvrUnderlineFromCenter"
onClick={() => {this.toggleFilter('movies')}}
id="category_active">Movies</span>
}
showToggle() {
if (this.state.showFilter)
return <span style={searchRequestCSS.searchFilterActive}
className="search_category hvrUnderlineFromCenter"
onClick={() => {this.toggleFilter('shows')}}
id="category_active">TV Shows</span>
else
return <span style={searchRequestCSS.searchFilterNotActive}
className="search_category hvrUnderlineFromCenter"
onClick={() => {this.toggleFilter('shows')}}
id="category_active">TV Shows</span>
}
render(){
const loader = <div className="loader">Loading ...<br></br></div>;
return(
<InfiniteScroll
pageStart={0}
loadMore={this.pageForwards.bind(this)}
hasMore={this.state.scrollHasMore}
loader={<Loading />}
initialLoad={this.state.loadResults}>
<MediaQuery minWidth={600}>
<div style={searchRequestCSS.body}>
<div className='backgroundHeader' style={searchRequestCSS.backgroundLargeHeader}>
<div className='pageTitle' style={searchRequestCSS.pageTitle}>
<span style={searchRequestCSS.pageTitleLargeSpan}>Request new content for plex</span>
</div>
<div style={searchRequestCSS.searchLargeContainer}>
<span style={searchRequestCSS.searchIcon}><i className="fa fa-search"></i></span>
<input style={searchRequestCSS.searchLargeBar} type="text" id="search" placeholder="Search for new content..."
onKeyPress={(event) => this._handleQueryKeyPress(event)}
onChange={event => this.updateQueryState(event)}
value={this.state.searchQuery}/>
</div>
</div>
<div id='requestMovieList' ref='requestMovieList' style={searchRequestCSS.requestWrapper}>
<div style={{marginLeft: '30px'}}>
<div style={searchRequestCSS.resultLargeHeader}>{this.state.resultHeader}</div>
<span style={{content: '', display: 'block', width: '2em', borderTop: '2px solid #000,'}}></span>
</div>
<br></br>
{this.state.responseMovieList}
</div>
</div>
</MediaQuery>
<MediaQuery maxWidth={600}>
<div style={searchRequestCSS.body}>
<div className='backgroundHeader' style={searchRequestCSS.backgroundSmallHeader}>
<div className='pageTitle' style={searchRequestCSS.pageTitle}>
<span style={searchRequestCSS.pageTitleSmallSpan}>Request new content</span>
</div>
<div className='box' style={searchRequestCSS.box}>
<div style={searchRequestCSS.searchSmallContainer}>
<span style={searchRequestCSS.searchIcon}><i className="fa fa-search"></i></span>
<input style={searchRequestCSS.searchSmallBar} type="text" id="search" placeholder="Search for new content..."
onKeyPress={(event) => this._handleQueryKeyPress(event)}
onChange={event => this.updateQueryState(event)}
value={this.state.searchQuery}/>
</div>
</div>
</div>
<div id='requestMovieList' ref='requestMovieList' style={searchRequestCSS.requestWrapper}>
<span style={searchRequestCSS.resultSmallHeader}>{this.state.resultHeader}</span>
<br></br><br></br>
{this.state.responseMovieList}
</div>
</div>
</MediaQuery>
</InfiniteScroll>
)
}
// <form style={searchRequestCSS.controls}>
// <label style={searchRequestCSS.withData}>
// <div style={searchRequestCSS.sortOptions}>Discover</div>
// </label>
// </form>
// <form style={searchRequestCSS.controls}>
// <label style={searchRequestCSS.withData}>
// <select style={searchRequestCSS.sortOptions}>
// <option value="discover">All</option>
// <option value="nowplaying">Movies</option>
// <option value="nowplaying">TV Shows</option>
// </select>
// </label>
// </form>
}
export default SearchRequest;
@@ -0,0 +1,92 @@
import React from 'react';
import LoginForm from './LoginForm/LoginForm.jsx';
import { Provider } from 'react-redux';
import store from '../redux/store.jsx';
import { getCookie } from '../Cookie.jsx';
import { fetchJSON } from '../http.jsx';
import Sidebar from './Sidebar.jsx';
import AdminRequestInfo from './AdminRequestInfo.jsx';
import adminCSS from '../styles/adminComponent.jsx'
class AdminComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
requested_objects: '',
}
this.updateHandler = this.updateHandler.bind(this)
}
// Fetches all requested elements and updates the state with response
componentWillMount() {
this.fetchRequestedItems()
}
fetchRequestedItems() {
fetchJSON('https://apollo.kevinmidboe.com/api/v1/plex/requests/all', 'GET')
.then(result => {
this.setState({
requested_objects: result.results.reverse()
})
})
}
updateHandler() {
this.fetchRequestedItems()
}
// Displays loginform if not logged in and passes response from
// api call to sidebar and infoPanel through props
verifyLoggedIn() {
const logged_in = getCookie('logged_in');
if (!logged_in) {
return <LoginForm />
}
let selectedRequest = undefined;
let listItemSelected = undefined;
const requestParam = this.props.match.params.request;
if (requestParam && this.state.requested_objects !== '') {
selectedRequest = this.state.requested_objects[requestParam]
listItemSelected = requestParam;
}
return (
<div>
<div style={adminCSS.selectedObjectPanel}>
<AdminRequestInfo
selectedRequest={selectedRequest}
listItemSelected={listItemSelected}
updateHandler = {this.updateHandler}
/>
</div>
<div style={adminCSS.sidebar}>
<Sidebar
requested_objects={this.state.requested_objects}
listItemSelected={listItemSelected}
/>
</div>
</div>
)
}
render() {
return (
<Provider store={store}>
{ this.verifyLoggedIn() }
</Provider>
)
}
}
export default AdminComponent;
@@ -0,0 +1,218 @@
import React, { Component } from 'react';
import { fetchJSON } from '../http.jsx';
import PirateSearch from './PirateSearch.jsx'
// No in use!
import InfoButton from '../buttons/InfoButton.jsx';
// Stylesheets
import requestInfoCSS from '../styles/adminRequestInfo.jsx'
import buttonsCSS from '../styles/buttons.jsx';
String.prototype.capitalize = function() {
return this.charAt(0).toUpperCase() + this.slice(1);
}
class AdminRequestInfo extends Component {
constructor() {
super();
this.state = {
statusValue: '',
movieInfo: undefined,
expandedSummary: false,
}
this.requestInfo = '';
}
componentWillReceiveProps(props) {
this.requestInfo = props.selectedRequest;
this.state.statusValue = this.requestInfo.status;
this.state.expandedSummary = false;
this.fetchIteminfo()
}
userAgent(agent) {
if (agent) {
try {
return agent.split(" ")[1].replace(/[\(\;]/g, '');
}
catch(e) {
return agent;
}
}
return '';
}
generateStatusDropdown() {
return (
<select onChange={ event => this.updateRequestStatus(event) } value={this.state.statusValue}>
<option value='requested'>Requested</option>
<option value='downloading'>Downloading</option>
<option value='downloaded'>Downloaded</option>
</select>
)
}
updateRequestStatus(event) {
const eventValue = event.target.value;
const itemID = this.requestInfo.id;
const apiData = {
type: this.requestInfo.type,
status: eventValue,
}
fetchJSON('https://apollo.kevinmidboe.com/api/v1/plex/request/' + itemID, 'PUT', apiData)
.then((response) => {
console.log('Response, updateRequestStatus: ', response)
this.props.updateHandler()
})
}
generateStatusIndicator(status) {
switch (status) {
case 'requested':
// Yellow
return 'linear-gradient(to right, rgb(63, 195, 243) 0, rgb(63, 195, 243) 10px, #fff 4px, #fff 100%) no-repeat'
case 'downloading':
// Blue
return 'linear-gradient(to right, rgb(255, 225, 77) 0, rgb(255, 225, 77) 10px, #fff 4px, #fff 100%) no-repeat'
case 'downloaded':
// Green
return 'linear-gradient(to right, #39aa56 0, #39aa56 10px, #fff 4px, #fff 100%) no-repeat'
default:
return 'linear-gradient(to right, grey 0, grey 10px, #fff 4px, #fff 100%) no-repeat'
}
}
generateTypeIcon(type) {
if (type === 'show')
return (
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><rect x="2" y="7" width="20" height="15" rx="2" ry="2"></rect><polyline points="17 2 12 7 7 2"></polyline></svg>
)
else if (type === 'movie')
return (
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><rect x="2" y="2" width="20" height="20" rx="2.18" ry="2.18"></rect><line x1="7" y1="2" x2="7" y2="22"></line><line x1="17" y1="2" x2="17" y2="22"></line><line x1="2" y1="12" x2="22" y2="12"></line><line x1="2" y1="7" x2="7" y2="7"></line><line x1="2" y1="17" x2="7" y2="17"></line><line x1="17" y1="17" x2="22" y2="17"></line><line x1="17" y1="7" x2="22" y2="7"></line></svg>
)
else
return (
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><circle cx="12" cy="12" r="10"></circle><line x1="12" y1="8" x2="12" y2="12"></line><line x1="12" y1="16" x2="12" y2="16"></line></svg>
)
}
toggleSummmaryLength() {
this.setState({
expandedSummary: !this.state.expandedSummary
})
}
generateSummary() {
// { this.state.movieInfo != undefined ? this.state.movieInfo.summary : 'Loading...' }
const info = this.state.movieInfo;
if (info !== undefined) {
const summary = this.state.movieInfo.summary
const summary_short = summary.slice(0, 180);
return (
<div>
<span><b>Matched: </b> {String(info.matchedInPlex)}</span> <br/>
<span><b>Rating: </b> {info.rating}</span> <br/>
<span><b>Popularity: </b> {info.popularity}</span> <br/>
{
(summary.length > 180 && this.state.expandedSummary === false) ?
<span><b>Summary: </b> { summary_short }<span onClick = {() => this.toggleSummmaryLength()}>... <span style={{color: 'blue', cursor: 'pointer'}}>Show more</span></span></span>
:
<span><b>Summary: </b> { summary }<span onClick = {() => this.toggleSummmaryLength()}><span style={{color: 'blue', cursor: 'pointer'}}> Show less</span></span></span>
}
</div>
)
} else {
return <span>Loading...</span>
}
}
requested_by_user(request_user) {
if (request_user === 'NULL')
return undefined
return (
<span><b>Requested by:</b> {request_user}</span>
)
}
fetchIteminfo() {
const itemID = this.requestInfo.id;
const type = this.requestInfo.type;
fetchJSON('https://apollo.kevinmidboe.com/api/v1/tmdb/' + itemID +'&type='+type, 'GET')
.then((response) => {
console.log('Response, getInfo:', response)
this.setState({
movieInfo: response
});
console.log(this.state.movieInfo)
})
}
displayInfo() {
const request = this.props.selectedRequest;
if (request) {
requestInfoCSS.info.background = this.generateStatusIndicator(request.status);
return (
<div style={requestInfoCSS.wrapper}>
<div style={requestInfoCSS.stick}>
<span style={requestInfoCSS.title}> {request.title} {request.year}</span>
<span style={{marginLeft: '2em'}}>
<span style={requestInfoCSS.type_icon}>{this.generateTypeIcon(request.type)}</span>
{/*<span style={style.type_text}>{request.type.capitalize()}</span> <br />*/}
</span>
</div>
<div style={requestInfoCSS.info}>
<div style={requestInfoCSS.info_poster}>
<img src={'https://image.tmdb.org/t/p/w185' + request.poster_path} style={requestInfoCSS.image} alt='Movie poster image'></img>
</div>
<div style={requestInfoCSS.info_request}>
<h3 style={requestInfoCSS.info_request_header}>Request info</h3>
<span><b>status:</b>{ request.status }</span><br />
<span><b>ip:</b>{ request.ip }</span><br />
<span><b>user_agent:</b>{ this.userAgent(request.user_agent) }</span><br />
<span><b>request_date:</b>{ request.requested_date}</span><br />
{ this.requested_by_user(request.requested_by) }<br />
{ this.generateStatusDropdown() }<br />
</div>
<div style={requestInfoCSS.info_movie}>
<h3 style={requestInfoCSS.info_movie_header}>Movie info</h3>
{ this.generateSummary() }
</div>
</div>
<PirateSearch style={requestInfoCSS.search} name={request.title} />
</div>
)
}
}
render() {
return (
<div>{this.displayInfo()}</div>
);
}
}
export default AdminRequestInfo;
@@ -0,0 +1,66 @@
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { login } from '../../redux/reducer.jsx';
class LoginForm extends Component {
constructor(props) {
super(props);
this.state = {};
this.onSubmit = this.onSubmit.bind(this);
}
render() {
let {email, password} = this.state;
let {isLoginPending, isLoginSuccess, loginError} = this.props;
return (
<form name="loginForm" onSubmit={this.onSubmit}>
<div className="form-group-collection">
<div className="form-group">
<label>Email:</label>
<input type="" name="email" onChange={e => this.setState({email: e.target.value})} value={email}/>
</div>
<div className="form-group">
<label>Password:</label>
<input type="password" name="password" onChange={e => this.setState({password: e.target.value})} value={password}/>
</div>
</div>
<input type="submit" value="Login" />
<div className="message">
{ isLoginPending && <div>Please wait...</div> }
{ isLoginSuccess && <div>Success.</div> }
{ loginError && <div>{loginError.message}</div> }
</div>
</form>
)
}
onSubmit(e) {
e.preventDefault();
let { email, password } = this.state;
this.props.login(email, password);
this.setState({
email: '',
password: ''
});
}
}
const mapStateToProps = (state) => {
return {
isLoginPending: state.isLoginPending,
isLoginSuccess: state.isLoginSuccess,
loginError: state.loginError
};
}
const mapDispatchToProps = (dispatch) => {
return {
login: (email, password) => dispatch(login(email, password))
};
}
export default connect(mapStateToProps, mapDispatchToProps)(LoginForm);
@@ -0,0 +1,95 @@
import React, { Component } from 'react';
import { fetchJSON } from '../http.jsx';
// Components
import TorrentTable from './TorrentTable.jsx'
// Stylesheets
import btnStylesheet from '../styles/buttons.jsx';
// Interactive button
import Interactive from 'react-interactive';
import Loading from '../images/loading.jsx'
class PirateSearch extends Component {
constructor() {
super();
this.state = {
torrentResponse: undefined,
name: '',
loading: null,
showButton: true,
}
}
componentWillReceiveProps(props) {
if (props.name != this.state.name) {
this.setState({
torrentResponse: undefined,
showButton: true,
})
}
}
searchTheBay() {
const query = this.props.name;
const type = this.props.type;
this.setState({
showButton: false,
loading: <Loading />,
})
fetchJSON('https://apollo.kevinmidboe.com/api/v1/pirate/search?query='+query+'&type='+type, 'GET')
// fetchJSON('http://localhost:31459/api/v1/pirate/search?query='+query+'&type='+type, 'GET')
.then((response) => {
console.log('this is the first response: ', response)
if (response.success === true) {
this.setState({
torrentResponse: response.torrents,
loading: null,
})
}
else {
console.error(response.message)
}
})
.catch((error) => {
console.error(error);
this.setState({
showButton: true,
})
})
}
render() {
btnStylesheet.submit.top = '50%'
btnStylesheet.submit.position = 'absolute'
btnStylesheet.submit.marginLeft = '-75px'
return (
<div>
{ this.state.showButton ?
<div style={{textAlign:'center'}}>
<Interactive
as='button'
onClick={() => {this.searchTheBay()}}
style={btnStylesheet.submit}
focus={btnStylesheet.submit_hover}
hover={btnStylesheet.submit_hover}>
<span style={{whiteSpace: 'nowrap'}}>Search for torrents</span>
</Interactive>
</div>
: null }
{ this.state.loading }
<TorrentTable response={this.state.torrentResponse} />
</div>
)
}
}
export default PirateSearch
@@ -0,0 +1,247 @@
import React, { Component } from 'react';
import { Link } from 'react-router-dom';
import Interactive from 'react-interactive';
import sidebarCSS from '../styles/adminSidebar.jsx'
class SidebarComponent extends Component {
constructor(props){
super(props)
// Constructor with states holding the search query and the element of reponse.
this.state = {
filterValue: '',
filterQuery: '',
requestItemsToBeDisplayed: [],
listItemSelected: '',
height: '0',
}
this.updateWindowDimensions = this.updateWindowDimensions.bind(this);
}
// Where we wait for api response to be delivered from parent through props
componentWillReceiveProps(props) {
this.state.listItemSelected = props.listItemSelected;
this.displayRequestedElementsInfo(props.requested_objects);
}
componentDidMount() {
this.updateWindowDimensions();
window.addEventListener('resize', this.updateWindowDimensions);
}
componentWillUnmount() {
window.removeEventListener('resize', this.updateWindowDimensions);
}
updateWindowDimensions() {
this.setState({ height: window.innerHeight });
}
// Inputs a date and returns a text string that matches how long it was since
convertDateToDaysSince(date) {
var oneDay = 24*60*60*1000;
var firstDate = new Date(date);
var secondDate = new Date();
var diffDays = Math.round(Math.abs((firstDate.getTime() - secondDate.getTime()) / oneDay));
switch (diffDays) {
case 0:
return 'Today';
case 1:
return '1 day ago'
default:
return diffDays + ' days ago'
}
}
// Called from our dropdown, receives a filter string and checks it with status field
// of our request objects.
filterItems(filterValue) {
let filteredRequestElements = this.props.requested_objects.map((item, index) => {
if (item.status === filterValue || filterValue === 'all')
return this.generateListElements(index, item);
})
this.setState({
requestItemsToBeDisplayed: filteredRequestElements,
filterValue: filterValue,
})
}
// Updates the internal state of the query filter and updates the list to only
// display names matching the query. This is real-time filtering.
updateFilterQuery(event) {
const query = event.target.value;
let filteredByQuery = this.props.requested_objects.map((item, index) => {
if (item.title.toLowerCase().indexOf(query.toLowerCase()) != -1)
return this.generateListElements(index, item);
})
this.setState({
requestItemsToBeDisplayed: filteredByQuery,
filterQuery: query,
});
}
generateFilterSearch() {
return (
<div style={sidebarCSS.searchSidebar}>
<div style={sidebarCSS.searchInner}>
<input
type="text"
id="search"
style={sidebarCSS.searchTextField}
placeholder="Search requested items"
onChange={event => this.updateFilterQuery(event)}
value={this.state.filterQuery}/>
<span>
<svg id="icon-search" style={sidebarCSS.searchIcon} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 15 15">
<g id="search">
<circle style={sidebarCSS.searchSVGIcon} cx="6.055" cy="5.805" r="5.305"></circle>
<path style={sidebarCSS.searchSVGIcon} d="M9.847 9.727l4.166 4.773"></path>
</g>
</svg>
</span>
</div>
</div>
)
}
generateNav() {
let filterValue = this.state.filterValue;
return (
<nav style={sidebarCSS.sidebar_navbar_underline}>
<ul style={sidebarCSS.ulFilterSelectors}>
<li>
<span style={sidebarCSS.aFilterSelectors} onClick = { event => this.filterItems('all') }>All</span>
{ (filterValue === 'all' || filterValue === '') && <span style={sidebarCSS.spanFilterSelectors}></span> }
</li>
<li>
<span style={sidebarCSS.aFilterSelectors} onClick = { event => this.filterItems('requested') }>Requested</span>
{ filterValue === 'requested' && <span style={sidebarCSS.spanFilterSelectors}></span> }
</li>
<li>
<span style={sidebarCSS.aFilterSelectors} onClick = { event => this.filterItems('downloading') }>Downloading</span>
{ filterValue === 'downloading' && <span style={sidebarCSS.spanFilterSelectors}></span> }
</li>
<li>
<span style={sidebarCSS.aFilterSelectors} onClick = { event => this.filterItems('downloaded') }>Downloaded</span>
{ filterValue === 'downloaded' && <span style={sidebarCSS.spanFilterSelectors}></span> }
</li>
</ul>
</nav>
)
}
generateBody(cards) {
let style = sidebarCSS.ulCard;
style.maxHeight = this.state.height - 160;
return (
<ul style={style}>
{ cards }
</ul>
)
}
generateListElements(index, item) {
let statusBar;
switch (item.status) {
case 'requested':
// Yellow
statusBar = { background: 'linear-gradient(to right, rgb(63, 195, 243) 0, rgb(63, 195, 243) 4px, #fff 4px, #fff 100%) no-repeat' }
break;
case 'downloading':
// Blue
statusBar = { background: 'linear-gradient(to right, rgb(255, 225, 77) 0, rgb(255, 225, 77) 4px, #fff 4px, #fff 100%) no-repeat' }
break;
case 'downloaded':
// Green
statusBar = { background: 'linear-gradient(to right, #39aa56 0, #39aa56 4px, #fff 4px, #fff 100%) no-repeat' }
break;
default:
statusBar = { background: 'linear-gradient(to right, grey 0, grey 4px, #fff 4px, #fff 100%) no-repeat' }
}
statusBar.listStyleType = 'none';
return (
<Link style={sidebarCSS.link} to={{ pathname: '/admin/'+String(index)}} key={index}>
<li style={statusBar}>
<Interactive
as='div'
style={ (index != this.state.listItemSelected) ? sidebarCSS.card : sidebarCSS.cardSelected }
hover={sidebarCSS.cardSelected}
focus={sidebarCSS.cardSelected}
active={sidebarCSS.cardSelected}>
<h2 style={sidebarCSS.titleCard}>
<span>{ item.title }</span>
</h2>
<p style={sidebarCSS.pCard}>
<span>Requested:
<time>
&nbsp;{ this.convertDateToDaysSince(item.requested_date) }
</time>
</span>
</p>
</Interactive>
</li>
</Link>
)
}
// This is our main loader that gets called when we receive api response through props from parent
displayRequestedElementsInfo(requested_objects) {
let requestedElement = requested_objects.map((item, index) => {
if (['requested', 'downloading', 'downloaded'].indexOf(this.state.filterValue) != -1) {
if (item.status === this.state.filterValue){
return this.generateListElements(index, item);
}
}
else if (this.state.filterQuery !== '') {
if (item.name.toLowerCase().indexOf(this.state.filterQuery.toLowerCase()) != -1)
return this.generateListElements(index, item);
}
else
return this.generateListElements(index, item);
})
this.setState({
requestItemsToBeDisplayed: this.generateBody(requestedElement)
})
}
render() {
// if (typeof InstallTrigger !== 'undefined')
// bodyCSS.width = '-moz-min-content';
return (
<div>
<h1 style={sidebarCSS.header}>Requested items</h1>
{ this.generateFilterSearch() }
{ this.generateNav() }
<div key='requestedTable' style={sidebarCSS.body}>
{ this.state.requestItemsToBeDisplayed }
</div>
</div>
);
}
}
export default SidebarComponent;
@@ -0,0 +1,209 @@
import React, { Component } from 'react';
import { fetchJSON } from '../http.jsx';
import torrentTableCSS from '../styles/adminTorrentTable.jsx';
class TorrentTable extends Component {
constructor() {
super();
this.state = {
torrentResponse: [],
listElements: undefined,
showTable: false,
filterQuery: '',
sortValue: 'name',
sortDesc: true,
}
this.UNITS = ['B', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
}
componentWillReceiveProps(props) {
if (props.response !== undefined && props.response !== this.state.torrentResponse) {
console.log('not called', props)
this.setState({
torrentResponse: props.response,
showTable: true,
})
} else {
this.setState({
showTable: false,
})
}
}
// BORROWED FROM GITHUB user sindresorhus
// Link to repo: https://github.com/sindresorhus/pretty-bytes
convertSizeToHumanSize(num) {
if (!Number.isFinite(num)) {
return num
// throw new TypeError(`Expected a finite number, got ${typeof num}: ${num}`);
}
const neg = num < 0;
if (neg) {
num = -num;
}
if (num < 1) {
return (neg ? '-' : '') + num + ' B';
}
const exponent = Math.min(Math.floor(Math.log10(num) / 3), this.UNITS.length - 1);
const numStr = Number((num / Math.pow(1000, exponent)).toPrecision(3));
const unit = this.UNITS[exponent];
return (neg ? '-' : '') + numStr + ' ' + unit;
}
convertHumanSizeToBytes(string) {
const [numStr, unit] = string.split(' ');
if (this.UNITS.indexOf(unit) === -1) {
return string
}
const exponent = this.UNITS.indexOf(unit) * 3
return numStr * (Math.pow(10, exponent))
}
sendToDownload(magnet) {
const apiData = {
magnet: magnet,
}
fetchJSON('https://apollo.kevinmidboe.com/api/v1/pirate/add', 'POST', apiData)
// fetchJSON('http://localhost:31459/api/v1/pirate/add', 'POST', apiData)
.then((response) => {
console.log('Response, addMagnet: ', response)
// TODO Display the feedback in a notification component (text, status)
})
}
// Updates the internal state of the query filter and updates the list to only
// display names matching the query. This is real-time filtering.
updateFilterQuery(event) {
const query = event.target.value;
let filteredByQuery = this.props.response.map((item, index) => {
if (item.name.toLowerCase().indexOf(query.toLowerCase()) != -1)
return item
})
this.setState({
torrentResponse: filteredByQuery,
filterQuery: query,
});
}
sortTable(col) {
let direction = this.state.sortDesc;
if (col === this.state.sortValue)
direction = !direction;
else
direction = true
let sortedItems = this.state.torrentResponse.sort((a,b) => {
// This is so we also can sort string that only contain numbers
let valueA = isNaN(a[col]) ? a[col] : parseInt(a[col])
let valueB = isNaN(b[col]) ? b[col] : parseInt(b[col])
valueA = (col == 'size') ? this.convertHumanSizeToBytes(valueA) : valueA
valueB = (col == 'size') ? this.convertHumanSizeToBytes(valueB) : valueB
if (direction)
return valueA<valueB? 1:valueA>valueB?-1:0;
else
return valueA>valueB? 1:valueA<valueB?-1:0;
})
this.setState({
torrentResponse: sortedItems,
sortDesc: direction,
sortValue: col,
})
}
generateFilterSearch() {
return (
<div style={torrentTableCSS.searchSidebar}>
<div style={torrentTableCSS.searchInner}>
<input
type="text"
id="search"
style={torrentTableCSS.searchTextField}
placeholder="Filter torrents by query"
onChange={event => this.updateFilterQuery(event)}
value={this.state.filterQuery}/>
<span>
<svg id="icon-search" style={torrentTableCSS.searchIcon} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 15 15">
<g id="search">
<circle style={torrentTableCSS.searchSVGIcon} cx="6.055" cy="5.805" r="5.305"></circle>
<path style={torrentTableCSS.searchSVGIcon} d="M9.847 9.727l4.166 4.773"></path>
</g>
</svg>
</span>
</div>
</div>
)
}
generateListElements() {
let listElements = this.state.torrentResponse.map((item, index) => {
if (item !== undefined) {
let title = item.name
let size = this.convertSizeToHumanSize(item.size)
return (
<tr key={index} style={torrentTableCSS.bodyCol}>
<td>{ item.name }</td>
<td>{ item.uploader }</td>
<td>{ size }</td>
<td>{ item.seed }</td>
<td><button onClick = { event => this.sendToDownload(item.magnet) }>Send to download</button></td>
</tr>
)
}
})
return listElements
}
render() {
return (
<div style= { this.state.showTable ? null : {display: 'none'}}>
{ this.generateFilterSearch() }
<table style={torrentTableCSS.table} cellSpacing="0" cellPadding="0">
<thead>
<tr>
<th style={torrentTableCSS.col} onClick = {event => this.sortTable('name') }>
Title
<svg style={ ( this.state.sortDesc && this.state.sortValue == 'name' ) ? null : {transform: 'rotate(180deg)'} } height="15" viewBox="0 3.5 10 13" version="1.1" width="25" aria-hidden="true"><path fillRule="evenodd" d="M10 10l-1.5 1.5L5 7.75 1.5 11.5 0 10l5-5z"></path></svg>
</th>
<th style={torrentTableCSS.col} onClick = {event => this.sortTable('uploader') }>
Uploader
<svg style={ ( this.state.sortDesc && this.state.sortValue == 'uploader' ) ? null : {transform: 'rotate(180deg)'} } height="15" viewBox="0 3.5 10 13" version="1.1" width="25" aria-hidden="true"><path fillRule="evenodd" d="M10 10l-1.5 1.5L5 7.75 1.5 11.5 0 10l5-5z"></path></svg>
</th>
<th style={torrentTableCSS.col} onClick = {event => this.sortTable('size') }>
Size
<svg style={ ( this.state.sortDesc && this.state.sortValue == 'size' ) ? null : {transform: 'rotate(180deg)'} } height="15" viewBox="0 3.5 10 13" version="1.1" width="25" aria-hidden="true"><path fillRule="evenodd" d="M10 10l-1.5 1.5L5 7.75 1.5 11.5 0 10l5-5z"></path></svg>
</th>
<th style={torrentTableCSS.col} onClick = {event => this.sortTable('seed') }>
Seeds
<svg style={ ( this.state.sortDesc && this.state.sortValue == 'seed' ) ? null : {transform: 'rotate(180deg)'} } height="15" viewBox="0 3.5 10 13" version="1.1" width="25" aria-hidden="true"><path fillRule="evenodd" d="M10 10l-1.5 1.5L5 7.75 1.5 11.5 0 10l5-5z"></path></svg>
</th>
<th style={torrentTableCSS.col}>Magnet</th>
</tr>
</thead>
<tbody>
{this.generateListElements()}
</tbody>
</table>
</div>
)
}
}
export default TorrentTable;
@@ -0,0 +1,52 @@
import React, { Component } from 'react';
import Interactive from 'react-interactive';
import buttonsCSS from '../styles/buttons.jsx';
class InfoButton extends Component {
constructor(props) {
super(props);
if (props) {
this.state = {
id: props.id,
type: props.type,
}
}
}
componentWillReceiveProps(props) {
this.setState({
id: props.id,
type: props.type,
})
}
getTMDBLink() {
const id = this.state.id;
const type = this.state.type;
if (type === 'movie')
return 'https://www.themoviedb.org/movie/' + id
else if (type === 'show')
return 'https://www.themoviedb.org/tv/' + id
}
render() {
return (
<a href={this.getTMDBLink()}>
<Interactive
as='button'
hover={buttonsCSS.info_hover}
focus={buttonsCSS.info_hover}
style={buttonsCSS.info}>
<span>More info</span>
</Interactive>
</a>
);
}
}
export default InfoButton;
@@ -0,0 +1,22 @@
import React from 'react';
class RequestButton extends React.Component {
constructor() {
super();
this.state = {textColor: 'white'};
}
render() {
return (
<Text
style={{color: this.state.textColor}}
onEnter={() => this.setState({textColor: 'red'})}
onExit={() => this.setState({textColor: 'white'})}>
This text will turn red when you look at it.
</Text>
);
}
}
export default RequestButton;
+53
View File
@@ -0,0 +1,53 @@
import React from 'react';
import { getCookie } from './Cookie.jsx';
// class http {
// dispatch(obj) {
// console.log(obj);
// }
function checkStatus(response) {
const hasError = (response.status < 200 || response.status >= 300)
if (hasError) {
throw response.text();
}
return response;
}
function parseJSON(response) { return response.json(); }
// *
// * Retrieve search results from tmdb with added seasoned information.
// * @param {String} uri query you want to search for
// * @param {Number} page representing pagination of results
// * @returns {Promise} succeeds if results were found
// fetchSearch(uri) {
// fetch(uri, {
// method: 'GET',
// headers: {
// 'authorization': getCookie('token')
// },
// })
// .then(response => {
// });
// }
// }
// export default http;
export function fetchJSON(url, method, data) {
return fetch(url, {
method: method,
headers: new Headers({
'Content-Type': 'application/json',
'authorization': getCookie('token'),
'loggedinuser': getCookie('loggedInUser'),
}),
body: JSON.stringify(data)
}).then(checkStatus).then(parseJSON);
}
@@ -0,0 +1,34 @@
import React from 'react';
function Loading() {
return (
<div style={{textAlign: 'center'}}>
<svg version="1.1"
style={{height: '75px'}}
xmlns="http://www.w3.org/2000/svg"
x="0px"
y="0px"
viewBox="0 0 80 80">
<path
fill="#e9a131"
d="M40,72C22.4,72,8,57.6,8,40C8,22.4,
22.4,8,40,8c17.6,0,32,14.4,32,32c0,1.1-0.9,2-2,2
s-2-0.9-2-2c0-15.4-12.6-28-28-28S12,24.6,12,40s12.6,
28,28,28c1.1,0,2,0.9,2,2S41.1,72,40,72z">
<animateTransform
attributeType="xml"
attributeName="transform"
type="rotate"
from="0 40 40"
to="360 40 40"
dur="1.0s"
repeatCount="indefinite"/>
</path>
</svg>
</div>
)
}
export default Loading;
@@ -0,0 +1,109 @@
import { setCookie } from '../Cookie.jsx';
const SET_LOGIN_PENDING = 'SET_LOGIN_PENDING';
const SET_LOGIN_SUCCESS = 'SET_LOGIN_SUCCESS';
const SET_LOGIN_ERROR = 'SET_LOGIN_ERROR';
export function login(email, password) {
return dispatch => {
dispatch(setLoginPending(true));
dispatch(setLoginSuccess(false));
dispatch(setLoginError(null));
callLoginApi(email, password, error => {
dispatch(setLoginPending(false));
if (!error) {
dispatch(setLoginSuccess(true));
} else {
dispatch(setLoginError(error));
}
});
}
}
function setLoginPending(isLoginPending) {
return {
type: SET_LOGIN_PENDING,
isLoginPending
};
}
function setLoginSuccess(isLoginSuccess) {
return {
type: SET_LOGIN_SUCCESS,
isLoginSuccess
};
}
function setLoginError(loginError) {
return {
type: SET_LOGIN_ERROR,
loginError
}
}
function callLoginApi(username, password, callback) {
Promise.resolve()
fetch('https://apollo.kevinmidboe.com/api/v1/user/login', {
method: 'POST',
headers: {
'Content-type': 'application/json'
},
body: JSON.stringify({
username: username,
password: password,
})
})
.then(response => {
switch (response.status) {
case 200:
response.json()
.then((data) => {
if (data.success === true) {
let token = data.token;
setCookie('token', token, 10);
setCookie('logged_in', true, 10);
setCookie('loggedInUser', username, 10);
window.location.reload();
}
return callback(null);
})
case 401:
return callback(new Error(response.statusText));
}
})
.catch(error => {
return callback(new Error('Invalid username and password'));
});
}
export default function reducer(state = {
isLoginSuccess: false,
isLoginPending: false,
loginError: null
}, action) {
switch (action.type) {
case SET_LOGIN_PENDING:
return Object.assign({}, state, {
isLoginPending: action.isLoginPending
});
case SET_LOGIN_SUCCESS:
return Object.assign({}, state, {
isLoginSuccess: action.isLoginSuccess
});
case SET_LOGIN_ERROR:
return Object.assign({}, state, {
loginError: action.loginError
});
default:
return state;
}
}
@@ -0,0 +1,7 @@
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import logger from 'redux-logger';
import reducer from './reducer.jsx';
const store = createStore(reducer, {}, applyMiddleware(thunk, logger));
export default store;
@@ -0,0 +1,16 @@
export default {
sidebar: {
float: 'left',
width: '18%',
minWidth: '250px',
fontFamily: '"Open Sans", sans-serif',
fontSize: '14px',
borderRight: '2px solid #f2f2f2',
},
selectedObjectPanel: {
width: '80%',
float: 'right',
fontFamily: '"Open Sans", sans-serif',
marginTop: '1em',
}
}
@@ -0,0 +1,58 @@
export default {
wrapper: {
width: '100%',
},
stick: {
marginBottom: '1em',
},
title: {
fontSize: '2em',
},
image: {
width: '105px',
borderRadius: '4px',
},
info: {
paddingTop: '1em',
paddingBottom: '0.5em',
marginRight: '2em',
backgroundColor: 'white',
border: '1px solid #d0d0d0',
borderRadius: '2px',
display: 'flex',
},
type_icon: {
marginLeft: '-0.2em',
marginRight: '0.7em',
},
type_text: {
verticalAlign: 'super',
},
info_poster: {
marginLeft: '2em',
flex: '0 1 10%'
},
info_request: {
flex: '0 1 auto'
},
info_request_header: {
margin: '0',
marginBottom: '0.5em',
},
info_movie: {
maxWidth: '70%',
marginLeft: '1em',
flex: '0 1 auto',
},
info_movie_header: {
margin: '0',
marginBottom: '0.5em',
}
}
@@ -0,0 +1,153 @@
export default {
header: {
textAlign: 'center',
},
body: {
backgroundColor: 'white',
},
parentElement: {
display: 'inline-block',
width: '100%',
border: '1px solid grey',
borderRadius: '2px',
padding: '4px',
margin: '4px',
marginLeft: '4px',
backgroundColor: 'white',
},
parentElement_hover: {
backgroundColor: '#f8f8f8',
pointer: 'hand',
},
parentElement_active: {
textDecoration: 'none',
},
parentElement_selected: {
display: 'inline-block',
width: '100%',
border: '1px solid grey',
borderRadius: '2px',
padding: '4px',
margin: '4px 0px 4px 4px',
marginLeft: '10px',
backgroundColor: 'white',
},
title: {
maxWidth: '65%',
display: 'inline-flex',
},
link: {
color: 'black',
textDecoration: 'none',
},
rightContainer: {
float: 'right',
},
searchSidebar: {
height: '4em',
},
searchInner: {
top: '0',
right: '0',
left: '0',
bottom: '0',
margin: 'auto',
width: '90%',
minWidth: '280px',
height: '30px',
border: '1px solid #d0d0d0',
borderRadius: '4px',
overflow: 'hidden'
},
searchTextField: {
display: 'inline-block',
width: '90%',
padding: '.3em',
verticalAlign: 'middle',
border: 'none',
background: '#fff',
fontSize: '14px',
marginTop: '-7px',
},
searchIcon: {
width: '15px',
height: '16px',
marginRight: '4px',
marginTop: '7px',
},
searchSVGIcon: {
fill: 'none',
stroke: '#9d9d9d',
strokeLinecap: 'round',
strokeLinejoin: 'round',
strokeMiterlimit: '10',
},
ulFilterSelectors: {
borderBottom: '2px solid #f1f1f1',
display: 'flex',
padding: '0',
margin: '0',
listStyle: 'none',
justifyContent: 'space-evenly',
},
aFilterSelectors: {
color: '#3eaaaf',
fontSize: '16px',
cursor: 'pointer',
},
spanFilterSelectors: {
content: '""',
bottom: '-2px',
display: 'block',
width: '100%',
height: '2px',
backgroundColor: '#3eaaaa',
},
ulCard: {
margin: '1em 0 0 0',
padding: '0',
listStyle: 'none',
borderBottom: '.46rem solid #f1f1f',
backgroundColor: '#f1f1f1',
overflow: 'scroll',
},
card: {
padding: '.1em .5em .8em 1.5em',
marginBottom: '.26rem',
height: 'auto',
cursor: 'pointer',
},
cardSelected: {
padding: '.1em .5em .8em 1.5em',
marginBottom: '.26rem',
height: 'auto',
cursor: 'pointer',
backgroundColor: '#f9f9f9',
},
titleCard: {
fontSize: '15px',
fontWeight: '400',
whiteSpace: 'no-wrap',
textDecoration: 'none',
},
pCard: {
margin: '0',
},
}
@@ -0,0 +1,59 @@
export default {
table: {
width: '80%',
marginRight: 'auto',
marginLeft: 'auto',
},
tableHeader: {
},
col: {
cursor: 'pointer',
borderBottom: '1px solid #e0e0e0',
paddingBottom: '0.5em',
textAlign: 'left',
},
bodyCol: {
marginTop: '0.5em',
},
searchSidebar: {
height: '4em',
marginTop: '1em',
},
searchInner: {
top: '0',
right: '0',
left: '0',
bottom: '0',
margin: 'auto',
width: '50%',
minWidth: '280px',
height: '30px',
border: '1px solid #d0d0d0',
borderRadius: '4px',
overflow: 'hidden'
},
searchTextField: {
display: 'inline-block',
width: '95%',
padding: '.3em',
verticalAlign: 'middle',
border: 'none',
background: '#fff',
fontSize: '14px',
marginTop: '-7px',
},
searchIcon: {
width: '15px',
height: '16px',
marginRight: '4px',
marginTop: '7px',
},
searchSVGIcon: {
fill: 'none',
stroke: '#9d9d9d',
strokeLinecap: 'round',
strokeLinejoin: 'round',
strokeMiterlimit: '10',
},
}
@@ -0,0 +1,80 @@
export default {
submit: {
color: '#e9a131',
marginRight: '10px',
backgroundColor: 'white',
border: '#e9a131 2px solid',
borderColor: '#e9a131',
borderRadius: '4px',
textAlign: 'center',
padding: '10px',
minWidth: '100px',
float: 'left',
fontSize: '13px',
fontWeight: '800',
cursor: 'pointer',
},
submit_hover: {
backgroundColor: '#e9a131',
color: 'white',
},
info: {
color: '#00d17c',
marginRight: '10px',
backgroundColor: 'white',
border: '#00d17c 2px solid',
borderRadius: '4px',
textAlign: 'center',
padding: '10px',
minWidth: '100px',
float: 'left',
fontSize: '13px',
fontWeight: '800',
cursor: 'pointer',
},
info_hover: {
backgroundColor: '#00d17c',
color: 'white',
},
edit: {
color: '#4a95da',
marginRight: '10px',
backgroundColor: 'white',
border: '#4a95da 2px solid',
borderRadius: '4px',
textAlign: 'center',
padding: '10px',
minWidth: '100px',
float: 'left',
fontSize: '13px',
fontWeight: '800',
cursor: 'pointer',
},
edit_small: {
color: '#4a95da',
marginRight: '10px',
backgroundColor: 'white',
border: '#4a95da 2px solid',
borderRadius: '4px',
textAlign: 'center',
padding: '4px',
minWidth: '50px',
float: 'left',
fontSize: '13px',
fontWeight: '800',
cursor: 'pointer',
},
edit_hover: {
backgroundColor: '#4a95da',
color: 'white',
},
}
@@ -0,0 +1,24 @@
export default {
bodyDiv: {
display: 'flex',
flexDirection: 'row',
flexWrap: 'wrap',
flexFlow: 'row wrap',
justifyContent: 'space-around',
},
wrappingDiv: {
},
requestPoster: {
height: '150px',
},
infoDiv: {
marginTop: 0,
marginLeft: '10px',
float: 'right',
},
}
@@ -0,0 +1,62 @@
export default {
container: {
maxWidth: '95%',
margin: '0 auto',
minHeight: '230px'
},
title_large: {
color: 'black',
fontSize: '2em',
},
title_small: {
color: 'black',
fontSize: '22px',
},
stats_large: {
fontSize: '0.8em'
},
stats_small: {
marginTop: '5px',
fontSize: '0.8em'
},
posterContainer: {
float: 'left',
zIndex: '3',
position: 'relative',
marginRight: '30px'
},
posterImage: {
border: '2px none',
borderRadius: '2px',
width: '150px'
},
backgroundImage: {
width: '100%'
},
buttons: {
paddingTop: '20px',
},
summary: {
fontSize: '15px',
},
dividerRow: {
width: '100%'
},
itemDivider: {
width: '90%',
borderBottom: '1px solid grey',
margin: '2rem auto'
}
}
@@ -0,0 +1,177 @@
export default {
body: {
fontFamily: "'Open Sans', sans-serif",
backgroundColor: '#f7f7f7',
margin: 0,
padding: 0,
minHeight: '100%',
},
backgroundLargeHeader: {
width: '100%',
minHeight: '180px',
backgroundColor: 'rgb(1, 28, 35)',
// backgroundImage: 'radial-gradient(circle, #004c67 0, #005771 120%)',
zIndex: 1,
marginBottom: '70px'
},
backgroundSmallHeader: {
width: '100%',
minHeight: '120px',
backgroundColor: '#011c23',
zIndex: 1,
marginBottom: '40px'
},
requestWrapper: {
maxWidth: '1200px',
margin: 'auto',
paddingTop: '10px',
backgroundColor: 'white',
position: 'relative',
zIndex: '10',
boxShadow: '0 1px 2px grey',
},
pageTitle: {
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
textAlign: 'center',
},
pageTitleLargeSpan: {
color: 'white',
fontSize: '3em',
marginTop: '4vh',
marginBottom: '6vh'
},
pageTitleSmallSpan: {
color: 'white',
fontSize: '2em',
marginTop: '3vh',
marginBottom: '3vh'
},
searchLargeContainer: {
height: '52px',
width: '77%',
paddingLeft: '23%',
backgroundColor: 'white',
boxShadow: 'grey 0px 1px 2px',
},
searchSmallContainer: {
},
searchIcon: {
position: 'absolute',
fontSize: '1.6em',
marginTop: '7px',
color: '#4f5b66',
display: 'block',
},
searchLargeBar: {
width: '50%',
height: '50px',
background: '#ffffff',
border: 'none',
fontSize: '12pt',
float: 'left',
color: '#63717f',
paddingLeft: '40px',
},
searchSmallBar: {
width: '100%',
height: '50px',
background: '#ffffff',
border: 'none',
fontSize: '11pt',
float: 'left',
color: '#63717f',
paddingLeft: '65px',
marginLeft: '-25px',
borderRadius: '5px',
},
// Dropdown for selecting tmdb lists
controls: {
textAlign: 'left',
paddingTop: '8px',
width: '33.3333%',
marginLeft: '0',
marginRight: '0',
},
withData: {
boxSizing: 'border-box',
marginBottom: '0',
display: 'block',
padding: '0',
verticalAlign: 'baseline',
font: 'inherit',
textAlign: 'left',
boxSizing: 'border-box',
},
sortOptions: {
border: '1px solid #000',
maxWidth: '100%',
overflow: 'hidden',
lineHeight: 'normal',
textAlign: 'left',
padding: '4px 12px',
paddingRight: '2rem',
backgroundImage: 'url("data:image/svg+xml;base64,PHN2ZyBpZD0iTGF5ZXJfMSIgZGF0YS1uYW1lPSJMYXllciAxIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxOCAxOCI+CiAgPHRpdGxlPmFycm93LWRvd24tbWljcm88L3RpdGxlPgogIDxwb2x5bGluZSBwb2ludHM9IjE0IDQuNjcgOSAxMy4zMyA0IDQuNjciIHN0eWxlPSJmaWxsOiBub25lO3N0cm9rZTogIzAwMDtzdHJva2UtbWl0ZXJsaW1pdDogMTA7c3Ryb2tlLXdpZHRoOiAycHgiLz4KPC9zdmc+Cg==")',
backgroundSize: '18px 18px',
backgroundPosition: 'right 8px center',
backgroundRepeat: 'no-repeat',
width: 'auto',
display: 'inline-block',
outline: 'none',
boxSizing: 'border-box',
fontSize: '15px',
WebkitAppearance: 'none',
MozAppearance: 'none',
appearance: 'none',
},
searchFilterActive: {
color: '#00d17c',
fontSize: '1em',
marginLeft: '10px',
cursor: 'pointer'
},
searchFilterNotActive: {
color: 'white',
fontSize: '1em',
marginLeft: '10px',
cursor: 'pointer'
},
filter: {
color: 'white',
paddingLeft: '40px',
width: '60%',
},
resultLargeHeader: {
color: 'black',
fontSize: '1.6em',
width: '20%',
},
resultSmallHeader: {
paddingLeft: '12px',
color: 'black',
fontSize: '1.4em',
},
}
+15
View File
@@ -0,0 +1,15 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<link href="https://fonts.googleapis.com/css?family=Open+Sans:300" rel="stylesheet">
<link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.1.0/css/font-awesome.min.css" rel="stylesheet">
<meta name="viewport" content="width=device-width, initial-scale=1.0 maximum-scale=1.0, user-scalable=0">
<title>seasoned Shows</title>
</head>
<body style='margin: 0'>
<div id="root">
</div>
</body>
</html>
+20
View File
@@ -0,0 +1,20 @@
/*
* @Author: KevinMidboe
* @Date: 2017-06-01 21:08:55
* @Last Modified by: KevinMidboe
* @Last Modified time: 2017-10-20 19:24:52
./client/index.js
which is the webpack entry file
*/
import React from 'react';
import { render } from 'react-dom';
import { HashRouter } from 'react-router-dom';
import Root from './Root.jsx';
render((
<HashRouter>
<Root />
</HashRouter>
), document.getElementById('root'));
+44
View File
@@ -0,0 +1,44 @@
{
"name": "seasoned",
"version": "1.0.0",
"main": "index.js",
"repository": "https://github.com/KevinMidboe/seasonedShows",
"author": "Kevin Midboe",
"license": "MIT",
"scripts": {
"start": "webpack-dev-server --open --config webpack.dev.js",
"build": "NODE_ENV=production webpack --config webpack.prod.js",
"build_dev": "webpack --config webpack.dev.js"
},
"dependencies": {
"clean-webpack-plugin": "^0.1.17",
"css-loader": "^1.0.0",
"html-webpack-plugin": "^2.28.0",
"path": "^0.12.7",
"react": "^15.6.1",
"react-burger-menu": "^2.1.6",
"react-dom": "^15.5.4",
"react-infinite-scroller": "^1.0.15",
"react-interactive": "^0.8.1",
"react-notify-toast": "^0.3.2",
"react-redux": "^5.0.6",
"react-responsive": "^1.3.4",
"react-router": "^4.2.0",
"react-router-dom": "^4.2.2",
"redux": "^3.7.2",
"redux-logger": "^3.0.6",
"redux-thunk": "^2.2.0",
"urijs": "^1.18.12",
"webfontloader": "^1.6.28",
"webpack": "^4.0.0",
"webpack-dev-server": "^3.1.11",
"webpack-merge": "^4.1.0"
},
"devDependencies": {
"babel-core": "^6.26.0",
"babel-loader": "^7.1.2",
"babel-preset-env": "^1.6.0",
"babel-preset-es2015": "^6.24.1",
"babel-preset-react": "^6.24.1"
}
}
+33
View File
@@ -0,0 +1,33 @@
/*
* @Author: KevinMidboe
* @Date: 2017-06-01 19:09:16
* @Last Modified by: KevinMidboe
* @Last Modified time: 2017-10-24 21:55:41
*/
const path = require('path');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: {
app: './app/index.js',
},
plugins: [
new CleanWebpackPlugin(['dist']),
new HtmlWebpackPlugin({
template: './app/index.html',
})
],
module: {
loaders: [
{ test: /\.(js|jsx)$/, loader: 'babel-loader', exclude: /node_modules/ },
]
},
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist')
}
};
+17
View File
@@ -0,0 +1,17 @@
/*
* @Author: KevinMidboe
* @Date: 2017-06-01 19:09:16
* @Last Modified by: KevinMidboe
* @Last Modified time: 2017-10-24 22:12:52
*/
const merge = require('webpack-merge');
const common = require('./webpack.common.js');
module.exports = merge(common, {
devtool: 'inline-source-map',
devServer: {
contentBase: './dist',
headers: {'Access-Control-Allow-Origin': '*'}
}
});;
+28
View File
@@ -0,0 +1,28 @@
/*
* @Author: KevinMidboe
* @Date: 2017-06-01 19:09:16
* @Last Modified by: KevinMidboe
* @Last Modified time: 2017-10-24 22:26:29
*/
const merge = require('webpack-merge');
const UglifyJSPlugin = require('uglifyjs-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const common = require('./webpack.common.js');
var webpack = require('webpack')
module.exports = merge(common, {
plugins: [
new UglifyJSPlugin(),
new HtmlWebpackPlugin({
template: './app/index.html',
title: 'Caching'
}),
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development')
}),
],
output: {
filename: '[name].[chunkhash].js',
}
});

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

+1
View File
@@ -0,0 +1 @@
**/node_modules
+111
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
+35
View File
@@ -0,0 +1,35 @@
{
"root": true,
"parserOptions": {
"parser": "@typescript-eslint/parser",
"ecmaVersion": 2020,
"sourceType": "module"
},
"extends": [
"eslint-config-airbnb-base",
"plugin:@typescript-eslint/recommended",
"plugin:prettier/recommended"
],
"plugins": ["mocha", "@typescript-eslint"],
"rules": {
"lines-between-class-members": [
"error",
"always",
{ "exceptAfterSingleLine": true }
],
"max-classes-per-file": 1,
"no-empty": [
2,
{
"allowEmptyCatch": true
}
],
"no-promise-executor-return": 1,
"no-shadow": "off",
"no-underscore-dangle": "off",
"@typescript-eslint/no-var-requires": "off"
},
"env": {
"mocha": true
}
}
+18
View File
@@ -0,0 +1,18 @@
## What kind of an issue is this?
- [ ] Bug report
- [ ] Feature request
## Expected behaviour?
## Current behaviour?
*if this is a bug report*
## Steps to reproduce behaviour?
*if this is a bug report*
## Screenshot? 📷
*A image tells a thousands words*
+1
View File
@@ -0,0 +1 @@
theme: jekyll-theme-cayman
+10 -8
View File
@@ -1,10 +1,12 @@
__pycache__
nohup.out
shows.db
.DS_Store
env_variables.py
conf/classedOutput.log
configurations/development.json
configurations/production.json
.env
shows.db
lib/
node_modules
*.pyc
npm-debug.log
webpage/js/env_variables.js
*/package-lock.json
.nyc_output
yarn-error.log
+9
View File
@@ -0,0 +1,9 @@
{
"tabWidth": 2,
"useTabs": false,
"semi": true,
"singleQuote": false,
"bracketSpacing": true,
"arrowParens": "avoid",
"trailingComma": "none"
}
+19
View File
@@ -0,0 +1,19 @@
language: node_js
node_js: '11.9.0'
git:
submodules: true
script:
- yarn test
- yarn coverage
before_install:
- cd seasoned_api
- cp conf/development.json.example conf/development.json
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
+21
View File
@@ -0,0 +1,21 @@
FROM node:18
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"]
LABEL org.opencontainers.image.source https://github.com/kevinmidboe/seasoned
+136 -2
View File
@@ -1,6 +1,140 @@
# *Seasoned*: an intelligent organizer for your shows
*Seasoned* 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.
<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://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=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>
</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
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
## 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.
-23
View File
@@ -1,23 +0,0 @@
/*
./app/components/App.jsx
<FetchData url={"https://apollo.kevinmidboe.com/api/v1/plex/playing"} />
*/
import React from 'react';
import FetchData from './FetchData.js';
import ListStrays from './ListStrays.jsx'
export default class App extends React.Component {
render() {
return (
<div>
<div style={{textAlign: 'center'}}>
<h1>Welcome to Seasoned</h1>
</div>
<ListStrays />
<FetchData />
</div>
);
}
}
-58
View File
@@ -1,58 +0,0 @@
import React from 'react';
class FetchData extends React.Component {
constructor(props){
super(props)
this.state = {
imgUrls: [],
hei: '1',
intervalId: null,
url: ''
}
}
componentDidMount(){
var that = this;
fetch("https://apollo.kevinmidboe.com/api/v1/plex/playing").then(
function(response){
response.json().then(function(data){
console.log(data.size);
that.setState({
imgUrls: that.state.imgUrls.concat(data.video)
})
console.log(data.video.title);
})
}
)
}
componentWillUnmount() {
// use intervalId from the state to clear the interval
clearInterval(this.state.intervalId);
}
getPlaying() {
console.log('Should not reach')
// Need callback to work
// Should try to clear out old requests to limit mem use
}
render(){
return(
<div className="FetchData">
{this.state.imgUrls.map((imgObj) => {
return ([
<span>{imgObj.title}</span>,
<span>{imgObj.season}</span>,
<span>{imgObj.episode}</span>,
]);
})}
</div>
);
}
}
export default FetchData;
-12
View File
@@ -1,12 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>seasoned Shows</title>
</head>
<body>
<div id="root">
</div>
</body>
</html>
-15
View File
@@ -1,15 +0,0 @@
/*
* @Author: KevinMidboe
* @Date: 2017-06-01 21:08:55
* @Last Modified by: KevinMidboe
* @Last Modified time: 2017-06-01 21:34:32
./client/index.js
which is the webpack entry file
*/
import React from 'react';
import ReactDOM from 'react-dom';
import App from './components/App.jsx';
ReactDOM.render(<App />, document.getElementById('root'));
-26
View File
@@ -1,26 +0,0 @@
{
"name": "seasoned",
"version": "1.0.0",
"main": "index.js",
"repository": "https://github.com/KevinMidboe/seasonedShows",
"author": "Kevin Midboe",
"license": "MIT",
"scripts": {
"start": "webpack-dev-server"
},
"dependencies": {
"html-webpack-plugin": "^2.28.0",
"path": "^0.12.7",
"react": "^15.5.4",
"react-dom": "^15.5.4",
"webpack": "^2.6.1",
"webpack-dev-server": "^2.4.5"
},
"devDependencies": {
"babel-core": "^6.24.1",
"babel-loader": "^7.0.0",
"babel-preset-env": "^1.5.1",
"babel-preset-es2015": "^6.24.1",
"babel-preset-react": "^6.24.1"
}
}
-33
View File
@@ -1,33 +0,0 @@
/*
* @Author: KevinMidboe
* @Date: 2017-06-01 19:09:16
* @Last Modified by: KevinMidboe
* @Last Modified time: 2017-06-01 22:11:51
*/
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const HtmlWebpackPluginConfig = new HtmlWebpackPlugin({
template: './app/index.html',
filename: 'index.html',
inject: 'body'
})
module.exports = {
entry: './app/index.js',
output: {
path: path.resolve('dist'),
filename: 'index_bundle.js'
},
devServer: {
headers: { "Access-Control-Allow-Origin": "*" }
},
module: {
loaders: [
{ test: /\.js$/, loader: 'babel-loader', exclude: /node_modules/ },
{ test: /\.jsx$/, loader: 'babel-loader', exclude: /node_modules/ }
]
},
plugins: [HtmlWebpackPluginConfig]
}
-3323
View File
File diff suppressed because it is too large Load Diff
View File
-11
View File
@@ -1,11 +0,0 @@
{
"database": {
"host": "shows.db"
},
"webserver": {
"port": 31459
},
"tmdb": {
"apiKey": "9fa154f5355c37a1b9b57ac06e7d6712"
}
}
+31
View File
@@ -0,0 +1,31 @@
{
"database": {
"host": "../shows.db"
},
"redis": {
"host": "localhost",
"port": 6379
},
"webserver": {
"port": 31459,
"origins": []
},
"tmdb": {
"apiKey": ""
},
"plex": {
"ip": "localhost",
"token": ""
},
"tautulli": {
"apiKey": "",
"ip": "",
"port": ""
},
"raven": {
"DSN": ""
},
"authentication": {
"secret": "secret"
}
}
+31
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"
}
}
-60
View File
@@ -1,60 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# @Author: KevinMidboe
# @Date: 2017-04-12 23:27:51
# @Last Modified by: KevinMidboe
# @Last Modified time: 2017-04-13 16:22:23
import sys, sqlite3, json, os
import env_variables as env
class episode(object):
def __init__(self, id):
self.id = id
self.getVarsFromDB()
def getVarsFromDB(self):
c = sqlite3.connect(env.db_path).cursor()
c.execute('SELECT parent, name, season, episode, video_files, subtitles, trash FROM stray_eps WHERE id = ?', (self.id,))
returnMsg = c.fetchone()
self.parent = returnMsg[0]
self.name = returnMsg[1]
self.season = returnMsg[2]
self.episode = returnMsg[3]
self.video_files = json.loads(returnMsg[4])
self.subtitles = json.loads(returnMsg[5])
self.trash = json.loads(returnMsg[6])
c.close()
self.queries = {
'parent': [env.show_dir, self.parent],
'season': [env.show_dir, self.name, self.name + ' Season ' + "%02d" % self.season],
'episode': [env.show_dir, self.name, self.name + ' Season ' + "%02d" % self.season, \
self.name + ' S' + "%02d" % self.season + 'E' + "%02d" % self.episode],
}
def typeDir(self, dType, create=False, mergeItem=None):
url = '/'.join(self.queries[dType])
if create and not os.path.isdir(url):
os.makedirs(url)
if mergeItem:
return '/'.join([url, str(mergeItem)])
return url
def moveStray(strayId):
ep = episode(strayId)
for item in ep.video_files:
os.rename(ep.typeDir('parent', mergeItem=item[0]), ep.typeDir('episode', mergeItem=item[1], create=True))
for item in ep.subtitles:
os.rename(ep.typeDir('parent', mergeItem=item[0]), ep.typeDir('episode', mergeItem=item[1], create=True))
for item in ep.trash:
os.remove(ep.typeDir('parent', mergeItem=item))
os.rmdir(ep.typeDir('parent'))
if __name__ == '__main__':
moveStray(sys.argv[-1])
+58 -17
View File
@@ -1,19 +1,60 @@
{
"name": "node-api",
"main": "src/webserver/server.js",
"scripts": {
"start": "cross-env SEASONED_CONFIG=conf/development.json NODE_PATH=. node src/webserver/server.js"
},
"dependencies": {
"express": "~4.0.0",
"mongoose": "~3.6.13",
"body-parser": "~1.0.1",
"cross-env": "^3.1.3",
"sqlite": "^2.5.0",
"request": "^2.81.0",
"python-shell": "^0.4.0",
"moviedb": "^0.2.7",
"node-cache": "^4.1.1",
"request-promise": "^4.2"
}
"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": "SEASONED_CONFIG=configurations/production.json NODE_ENV=production node lib/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",
"build": "yarn tsc",
"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/production.json node scripts/updateRequestsInPlex.js",
"docs": "yarn apiDocs; yarn classDocs",
"apiDocs": "",
"classDocs": "scripts/generate-class-docs.sh",
"postbuild": "cp -r src/database/schemas lib/database"
},
"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",
"python-shell": "^0.5.0",
"raven": "^2.4.2",
"redis": "^3.0.2",
"sqlite3": "^5.0.1",
"typescript": "^4.7.4"
},
"devDependencies": {
"@babel/core": "^7.5.5",
"@babel/preset-env": "^7.5.5",
"@babel/register": "^7.5.5",
"@types/node": "^18.7.8",
"@typescript-eslint/eslint-plugin": "^5.33.1",
"@typescript-eslint/parser": "^5.33.1",
"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"
}
}
+44
View File
@@ -0,0 +1,44 @@
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));
-1
View File
@@ -1 +0,0 @@
So to get people to sign up for a account could be to have them sign up for shows that they can be alerted when are added. They can choose by SMS, twitter, email or maybe newsletter.
+78
View File
@@ -0,0 +1,78 @@
const configuration = require("../config/configuration").getInstance();
let client;
const mockCache = {};
try {
const redis = require("redis"); // eslint-disable-line global-require
console.log("Trying to connect with redis.."); // eslint-disable-line no-console
const host = configuration.get("redis", "host");
const port = configuration.get("redis", "port");
client = redis.createClient({
url: `redis://${host}:${port}`
});
client.on("connect", () => console.log("Redis connection established!")); // eslint-disable-line no-console
client.on("error", () => {
client.quit();
console.error("Unable to connect to redis, setting up redis-mock."); // eslint-disable-line no-console
client = {
get(key, callback) {
console.log(`redis-dummy get: ${key}`); // eslint-disable-line no-console
const hit = mockCache[key];
return Promise.resolve().then(callback(null, hit));
},
set(key, json, callback) {
console.log(`redis-dummy set: ${key}`); // eslint-disable-line no-console
mockCache[key] = JSON.stringify(json);
return Promise.resolve().then(callback(null, "OK"));
},
expire(key, TTL) {
console.log(`redis-dummy expire: ${key} with TTL ${TTL}`); // eslint-disable-line no-console
}
};
});
} catch (e) {}
function set(key, value, TTL = 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, TTL, "NX", e => {
if (e)
// eslint-disable-next-line no-console
console.error(
"Unexpected error while setting expiration for key:",
key,
". Error:",
error
);
});
}
});
return value;
}
function get(key) {
return new Promise((resolve, reject) => {
client.get(key, (error, reply) => {
if (reply === null) {
return reject();
}
return resolve(JSON.parse(reply));
});
});
}
module.exports = {
get,
set
};
+36 -26
View File
@@ -1,38 +1,48 @@
const path = require('path');
const Field = require('./field.js');
const path = require("path");
const Field = require("./field");
let instance = null;
class Config {
constructor() {
this.location = Config.determineLocation();
this.fields = require(`${this.location}`);
}
constructor() {
this.location = Config.determineLocation();
// eslint-disable-next-line import/no-dynamic-require, global-require
this.fields = require(`${this.location}`);
}
static getInstance() {
if (instance == null) {
instance = new Config();
}
return instance;
}
static getInstance() {
if (instance == null) {
instance = new Config();
}
return instance;
}
static determineLocation() {
return path.join(__dirname, '..', '..', process.env.SEASONED_CONFIG);
}
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(`Filed "${section} => ${option}" does not exist.`);
}
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])
const field = new Field(this.fields[section][option]);
if (field.value === undefined) {
throw new Error(`${section} => ${option} is empty.`);
}
const envField =
process.env[[section.toUpperCase(), option.toUpperCase()].join("_")];
if (envField !== undefined && envField.length !== 0) {
return envField;
}
return field.value;
}
if (field.value === undefined) {
throw new Error(`${section} => ${option} is empty.`);
}
return field.value;
}
}
module.exports = Config;
module.exports = Config;
-1
View File
@@ -1,5 +1,4 @@
class EnvironmentVariables {
constructor(variables) {
this.variables = variables || process.env;
}
+42 -39
View File
@@ -1,50 +1,53 @@
const Filters = require('./filters.js');
const EnvironmentVariables = require('./environmentVariables.js');
const Filters = require("./filters");
const EnvironmentVariables = require("./environmentVariables");
class Field {
constructor(rawValue, environmentVariables) {
this.rawValue = rawValue;
this.filters = new Filters(rawValue);
this.valueWithoutFilters = this.filters.removeFiltersFromValue();
this.environmentVariables = new EnvironmentVariables(environmentVariables);
}
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;
}
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.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.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('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;
}
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;
}
return this.valueWithoutFilters;
}
static base64Decode(string) {
return new Buffer(string, 'base64').toString('utf-8');
}
static base64Decode(string) {
return Buffer.from(string, "base64").toString("utf-8");
}
}
module.exports = Field;
module.exports = Field;
+5 -6
View File
@@ -1,8 +1,7 @@
class Filters {
constructor(value) {
this.value = value;
this.delimiter = '|';
this.delimiter = "|";
}
get filters() {
@@ -10,7 +9,7 @@ class Filters {
}
isEmpty() {
return !this.hasValidType() || this.filters.length === 0;
return !this.hasValidType() || this.value.length === 0;
}
has(filter) {
@@ -18,7 +17,7 @@ class Filters {
}
hasValidType() {
return (typeof this.value === 'string');
return typeof this.value === "string";
}
removeFiltersFromValue() {
@@ -27,8 +26,8 @@ class Filters {
}
let filtersCombined = this.filters.join(this.delimiter);
filtersCombined += this.filters.length >= 1 ? this.delimiter : '';
return this.value.replace(filtersCombined, '');
filtersCombined += this.filters.length >= 1 ? this.delimiter : "";
return this.value.replace(filtersCombined, "");
}
}
+4 -6
View File
@@ -1,15 +1,13 @@
const configuration = require('src/config/configuration').getInstance();
const SqliteDatabase = require('src/database/sqliteDatabase');
const database = new SqliteDatabase(configuration.get('database', 'host'));
const configuration = require("../config/configuration").getInstance();
const SqliteDatabase = require("./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.connect())
// .then(() => database.setUp());
Promise.resolve().then(() => database.setUp());
module.exports = database;
+95
View File
@@ -0,0 +1,95 @@
CREATE TABLE IF NOT EXISTS user (
user_name varchar(127) UNIQUE,
password varchar(127),
admin boolean DEFAULT 0,
email varchar(127) UNIQUE,
primary key (user_name)
);
CREATE TABLE IF NOT EXISTS settings (
user_name varchar(127) UNIQUE,
dark_mode boolean DEFAULT 0,
plex_userid varchar(127) DEFAULT NULL,
emoji varchar(16) DEFAULT NULL,
foreign key(user_name) REFERENCES user(user_name) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS cache (
key varchar(255),
value blob,
time_to_live INTEGER DEFAULT 60,
created_at DATE DEFAULT (datetime('now','localtime')),
primary key(key)
);
CREATE TABLE IF NOT EXISTS search_history (
id integer,
user_name varchar(127),
search_query varchar(255),
primary key (id),
foreign key(user_name) REFERENCES user(user_name) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS requests (
id NUMBER,
title TEXT,
year NUMBER,
poster_path TEXT DEFAULT NULL,
background_path TEXT DEFAULT NULL,
requested_by varchar(127) DEFAULT NULL,
ip TEXT,
date DATE DEFAULT CURRENT_TIMESTAMP,
status CHAR(25) DEFAULT 'requested' NOT NULL,
user_agent CHAR(255) DEFAULT NULL,
type CHAR(50) DEFAULT 'movie'
-- foreign key(requested_by) REFERENCES user(user_name) ON DELETE SET NULL
);
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,
parent TEXT,
path TEXT,
name TEXT,
season NUMBER,
episode NUMBER,
video_files TEXT,
subtitles TEXT,
trash TEXT,
verified BOOLEAN DEFAULT 0,
primary key(id)
);
CREATE TABLE IF NOT EXISTS shows(
show_names TEXT,
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
)
+5
View File
@@ -0,0 +1,5 @@
DROP TABLE IF EXISTS user;
DROP TABLE IF EXISTS settings;
DROP TABLE IF EXISTS search_history;
DROP TABLE IF EXISTS requests;
DROP TABLE IF EXISTS request;
+108 -22
View File
@@ -1,32 +1,118 @@
const fs = require('fs');
const path = require('path');
const sqlite = require('sqlite');
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");
}
constructor(host) {
this.host = host;
this.connection = sqlite;
/**
* Connect to the database.
* @returns {Promise} succeeds if connection was established
*/
// connect() {
// let database = ;
// this.connection = database;
// return database;
// }
// this.schemaDirectory = path.join(__dirname, 'schemas');
}
/**
* 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);
});
});
}
connect() {
return Promise.resolve()
.then(() => sqlite.open(this.host))
}
/**
* 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);
});
});
}
all(sql, parameters) {
return this.connection.all(sql, parameters);
}
/**
* 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);
});
});
}
get(sql, parameters) {
return this.connection.get(sql, parameters);
}
/**
* Run a SQL query against the database and retrieve the status.
* @param {String} sql SQL query
* @returns {Promise}
*/
execute(sql) {
return new Promise((resolve, reject) => {
this.connection.exec(sql, err => {
if (err) {
console.log("ERROR: ", err);
reject(err);
}
resolve();
});
});
}
run(sql, parameters) {
return this.connection.run(sql, parameters);
}
/**
* 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;
module.exports = SqliteDatabase;

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