diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 000000000..419d87940 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,8 @@ +# These are supported funding model platforms + +github: [tidusjar] +patreon: tidusjar +#open_collective: # Replace with a single Open Collective username +#ko_fi: # Replace with a single Ko-fi username +#tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel +custom: https://paypal.me/PlexRequestsNet diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md deleted file mode 100644 index 2684ccc3e..000000000 --- a/.github/ISSUE_TEMPLATE.md +++ /dev/null @@ -1,48 +0,0 @@ - - -#### Ombi build Version: - -V 3.0.XX - -#### Update Branch: - -Open Beta - -#### Media Sever: - -Plex/Emby - -#### Media Server Version: - - - -#### Operating System: - -(Place text here) - - -#### Ombi Applicable Logs (from `/logs/` directory or the Admin page): - -``` - -(Logs go here. Don't remove the ' tags for showing your logs correctly. Please make sure you remove any personal information from the logs) - -``` - -#### Problem Description: - -(Place text here) - -#### Reproduction Steps: - -Please include any steps to reproduce the issue, this the request that is causing the problem etc. diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 000000000..2236cc395 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,37 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: '' +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Logs (Logs directory where Ombi is located)** +If applicable, a snippet of the logs that seems relevant to the bug if present. + +**Desktop (please complete the following information):** + - OS: [e.g. iOS] + +**Ombi Version (please complete the following information):** + - Version [e.g. 3.0.1158] +- Media Server [e.g. Plex] + +**Additional context** +Add any other context about the problem here. diff --git a/.github/stale.yml b/.github/stale.yml new file mode 100644 index 000000000..b3deb4a73 --- /dev/null +++ b/.github/stale.yml @@ -0,0 +1,23 @@ +# Number of days of inactivity before an issue becomes stale +daysUntilStale: 60 +# Number of days of inactivity before a stale issue is closed +daysUntilClose: 7 +# Issues with these labels will never be considered stale +exemptLabels: + - pinned + - security + - bug / issue + - help wanted + - possible feature + - planned + - in progress + - enhancement +# Label to use when marking an issue as stale +staleLabel: wontfix +# Comment to post when marking an issue as stale. Set to `false` to disable +markComment: > + This issue has been automatically marked as stale because it has not had + recent activity. It will be closed if no further activity occurs. Thank you + for your contributions. +# Comment to post when closing a stale issue. Set to `false` to disable +closeComment: false diff --git a/.github/workflows/aspnetcore.yml b/.github/workflows/aspnetcore.yml new file mode 100644 index 000000000..e562216cc --- /dev/null +++ b/.github/workflows/aspnetcore.yml @@ -0,0 +1,18 @@ +name: ASP.NET Core CI + +on: [push, pull_request] + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v1 + - name: Setup .NET Core + uses: actions/setup-dotnet@v1 + with: + dotnet-version: 2.2.108 + + - name: Build Backend + run: ./build.sh --settings_skipverification=true diff --git a/.github/workflows/test.workflow b/.github/workflows/test.workflow new file mode 100644 index 000000000..7c88813d1 --- /dev/null +++ b/.github/workflows/test.workflow @@ -0,0 +1,9 @@ +workflow "New workflow" { + on = "push" + resolves = [".NET Core CLI"] +} + +action ".NET Core CLI" { + uses = "baruchiro/github-actions@0.0.1" + args = "build src/Ombi.sln" +} diff --git a/.gitignore b/.gitignore index 771a7bacd..587f09568 100644 --- a/.gitignore +++ b/.gitignore @@ -243,3 +243,6 @@ _Pvt_Extensions # CAKE - C# Make /Tools/* *.db-journal + +# Ignore local vscode config +*.vscode diff --git a/CHANGELOG.md b/CHANGELOG.md index fb91d9d96..ae8417bae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,1346 @@ ### **New Features** +- Added better support for Jellyfin, we will now auto detect if it's a jellyfin server after pressing the discover button. [tidusjar] + +- Update aspnetcore.yml. [Jamie] + +- Update aspnetcore.yml. [Jamie] + +- Update aspnetcore.yml. [Jamie] + +- Update aspnetcore.yml. [Jamie] + +- Update aspnetcore.yml. [Jamie] + +- Update and rename .github/workflows to .github/.github/workflows/test.workflow. [Jamie] + +- Update aspnetcore.yml. [Jamie] + +- Update aspnetcore.yml. [Jamie] + +- Update aspnetcore.yml. [Jamie] + +- Added a bit more logging into the recently added scan. [tidusjar] + +- Update emby.component.html. [sorano] + +- Update EmbyHelper.cs. [sorano] + +- Update CHANGELOG.md. [Jamie] + +### **Fixes** + +- Fixed #3078. [tidusjar] + +- Fixes issue #3195 The new string extension method ToHttpsUrl ensures that URLs starting with "https" are no longer turned into "httpss" The commit also replaces all occurances of the error prone .Replace("http", "https") in the whole solution. [msdeibel] + +- Create test.workflow. [Jamie] + +- Delete test.workflow. [Jamie] + +- New translations en.json (Norwegian) [Jamie] + +- New translations en.json (Norwegian) [Jamie] + +- Fix for #3183. [tidusjar] + +- Fixed an issue where running the recently added sync via the UI was running the full sync. [tidusjar] + +- Fixed #3143. [Jamie Rees] + +- New translations en.json (French) [Jamie] + +- New translations en.json (French) [Jamie] + +- New translations en.json (Russian) [Jamie] + +- New translations en.json (Russian) [Jamie] + +- New translations en.json (Russian) [Jamie] + +- New translations en.json (French) [Jamie] + +- New translations en.json (Russian) [Jamie] + +- New translations en.json (Russian) [Jamie] + +- New translations en.json (Russian) [Jamie] + +- New translations en.json (Russian) [Jamie] + +- New translations en.json (Russian) [Jamie] + +- New translations en.json (Polish) [Jamie] + +- Fixed the issue when we are logging errors in the logs incorrectly. [Jamie] + +- Removed the lanuage profile from the Lidarr integration. [tidusjar] + +- Try and clear up the issue #2998. [tidusjar] + +- Fixed an issue where shows that have no aired, episodes are not marked as monitored in Sonarr. [tidusjar] + +- Fixed an error when finishing the content sync. [tidusjar] + +- Fixed issue where using the API to request a movie/tv show would throw an exception when only using the API Key #3091. [tidusjar] + +- Put "Ombi" back as the product name for Plex oAuth. [tidusjar] + + +## v3.0.4680 (2019-07-17) + +### **New Features** + +- Update CHANGELOG.md. [Jamie] + +### **Fixes** + +- Fix Plex's (intentional) mistake #3073. [Jamie Rees] + +- #2994 Fixed the startup issue. [tidusjar] + +- #2994 - enable multithreading in the sql config. [Jamie Rees] + + +## v3.0.4659 (2019-07-02) + +### **New Features** + +- Update appsettings.json. [Jamie] + +- Update CHANGELOG.md. [Jamie] + + +## v3.0.4654 (2019-07-02) + +### **New Features** + +- Added further logging into the API's (debug logging) [tidusjar] + +- Added transactions around all of the CUD operations. [Jamie Rees] + +- Added some validation around the new crons. [Jamie Rees] + +- Added some defensive coding around when we create an artist for #2915. [tidusjar] + +- Update JobSetup.cs. [Jamie] + +- Update JobSetup.cs. [Jamie] + +- Added a global mutex (not used yet) and moved around the code for loggin in since I suspect the Get Roles call is using deffered execution on the database causing the lock when attempting to access straight away #2750. [Jamie Rees] + +- Added a lock on the database commit level to see if I can improve locked db's. [Jamie Rees] + +- Update dependancies. [TidusJar] + +- Update stale.yml. [Jamie] + +- Update README.md. [Dyson Parkes] + +- Update README.md. [Jamie] + +- Update README.md. [Jamie] + +- Update CHANGELOG.md. [Jamie] + +- Added stalebot. [tidusjar] + +### **Fixes** + +- Add back in the login time. [tidusjar] + +- New translations en.json (Spanish) [Jamie] + +- New translations en.json (Spanish) [Jamie] + +- New translations en.json (Spanish) [Jamie] + +- New translations en.json (Spanish) [Jamie] + +- New translations en.json (Swedish) [Jamie] + +- New translations en.json (Polish) [Jamie] + +- New translations en.json (Swedish) [Jamie] + +- New translations en.json (Spanish) [Jamie] + +- New translations en.json (Russian) [Jamie] + +- New translations en.json (Portuguese, Brazilian) [Jamie] + +- New translations en.json (Norwegian) [Jamie] + +- New translations en.json (Italian) [Jamie] + +- New translations en.json (Hungarian) [Jamie] + +- New translations en.json (German) [Jamie] + +- New translations en.json (French) [Jamie] + +- New translations en.json (Dutch) [Jamie] + +- New translations en.json (Danish) [Jamie] + +- New translations en.json (Bulgarian) [Jamie] + +- New translations en.json (Polish) [Jamie] + +- New translations en.json (Polish) [Jamie] + +- New translations en.json (Dutch) [Jamie] + +- New translations en.json (Dutch) [Jamie] + +- New translations en.json (Dutch) [Jamie] + +- New translations en.json (Dutch) [Jamie] + +- New translations en.json (Dutch) [Jamie] + +- New translations en.json (Spanish) [Jamie] + +- New translations en.json (French) [Jamie] + +- New translations en.json (French) [Jamie] + +- New translations en.json (Russian) [Jamie] + +- New translations en.json (Russian) [Jamie] + +- New translations en.json (Portuguese, Brazilian) [Jamie] + +- New translations en.json (Portuguese, Brazilian) [Jamie] + +- New translations en.json (Portuguese, Brazilian) [Jamie] + +- New translations en.json (Portuguese, Brazilian) [Jamie] + +- New translations en.json (Portuguese, Brazilian) [Jamie] + +- New translations en.json (German) [Jamie] + +- New translations en.json (Polish) [Jamie] + +- New translations en.json (Polish) [Jamie] + +- New translations en.json (Polish) [Jamie] + +- New translations en.json (Polish) [Jamie] + +- New translations en.json (Polish) [Jamie] + +- New translations en.json (Polish) [Jamie] + +- New translations en.json (Polish) [Jamie] + +- New translations en.json (Russian) [Jamie] + +- New translations en.json (Hungarian) [Jamie] + +- New translations en.json (Bulgarian) [Jamie] + +- New translations en.json (Swedish) [Jamie] + +- New translations en.json (Spanish) [Jamie] + +- New translations en.json (Portuguese, Brazilian) [Jamie] + +- New translations en.json (Polish) [Jamie] + +- New translations en.json (Norwegian) [Jamie] + +- New translations en.json (Italian) [Jamie] + +- New translations en.json (German) [Jamie] + +- New translations en.json (French) [Jamie] + +- New translations en.json (Dutch) [Jamie] + +- New translations en.json (Danish) [Jamie] + +- New translations en.json (Russian) [Jamie] + +- New translations en.json (Hungarian) [Jamie] + +- New translations en.json (Bulgarian) [Jamie] + +- New translations en.json (Swedish) [Jamie] + +- New translations en.json (Spanish) [Jamie] + +- New translations en.json (Portuguese, Brazilian) [Jamie] + +- New translations en.json (Polish) [Jamie] + +- New translations en.json (Norwegian) [Jamie] + +- New translations en.json (Italian) [Jamie] + +- New translations en.json (German) [Jamie] + +- New translations en.json (French) [Jamie] + +- New translations en.json (Dutch) [Jamie] + +- New translations en.json (Danish) [Jamie] + +- New translations en.json (Polish) [Jamie] + +- New translations en.json (Hungarian) [Jamie] + +- New translations en.json (Hungarian) [Jamie] + +- New translations en.json (Hungarian) [Jamie] + +- New translations en.json (Hungarian) [Jamie] + +- New translations en.json (Hungarian) [Jamie] + +- New translations en.json (Russian) [Jamie] + +- New translations en.json (Hungarian) [Jamie] + +- New translations en.json (Bulgarian) [Jamie] + +- New translations en.json (Dutch) [Jamie] + +- New translations en.json (Polish) [Jamie] + +- Fixed the issue where the recently added scan was actually calling the RefreshMedia which ends up wiping out the Plex cache instead of refreshing the metadata... i'm a dumbass #3023. [tidusjar] + +- Fix #3027. [Jamie] + +- Log the request. [tidusjar] + +- Really fixed #3010 this time. That's embarrassing. [Jamie] + +- Renamed "Extensions" for Spelling Mistake. [bdrumm1234] + +- #3010 - Make sure we only sync the Monitored Radarr movies... [tidusjar] + +- Fixed build. [Jamie Rees] + +- Fixed #2995. [Jamie Rees] + +- Fixed. [tidusjar] + +- Import System.IO. [Patrick Collins] + +- Add exception to handle unknown RequestType. [Patrick Collins] + +- Add braces to all if statements. [Patrick Collins] + +- Fix album-request-permission error message. [Patrick Collins] + +- Use string interpolation. [Austin Jackson] + +- Swagger index prepends configured baseurl. [Austin Jackson] + +- Add a internal retry when we have a locked db. [Jamie Rees] + +- Fixed #2374. [tidusjar] + +- Fixed #2950. [Jamie Rees] + +- Fixed #2967. [Jamie Rees] + +- Re-re fix the remove button. [goldenpipes] + +- Fixed the other error for #2955. [tidusjar] + +- Stuff. [tidusjar] + +- Logging. [tidusjar] + +- More to debug logging. [tidusjar] + +- Start the scheduler after the jobs have been assigned. [tidusjar] + +- Fixed some of the issues with the new scheduler not firing correctly. [Jamie Rees] + +- Placeholder Text for Search Boxes (#2939) [Kris Klosterman] + +- Reset all of the schedules due to Quartz using a different CRON system. Updated the UI code to reflect this. [tidusjar] + +- Fixed the scheduler! [tidusjar] + +- Attempting to get the new triggers working. [Jamie Rees] + +- Swap out the scheduler #2750. [Jamie Rees] + +- Moved the jobs to use quartz. [tidusjar] + +- Fixed the mixed content warnings and the error when removing a album request #2884. [tidusjar] + +- Fixed #2910. [tidusjar] + +- Fix for broken twitch url in readme file. [PotatoQuality] + +- Reverted the global app lock for the db #2750. [tidusjar] + +- #2750 stuff. [Jamie Rees] + +- More for #2750. [Jamie Rees] + +- Removed the auditing, was not used anyway #2750. [Jamie Rees] + +- Fixed #2803 in regards to the Request Button showing up. Still need to investiagte the availability side of things. [Jamie Rees] + +- Delete the schedules db on startup, we don't want it trying to recover the jobs. [tidusjar] + +- Fixed the issue where it was not picking up roles until the JWT was refreshed. [tidusjar] + +- Add Gotify as notification provider. [Guillaume Taquet Gasperini] + +- Fix cake build by setting Incubator version. [Guillaume Taquet Gasperini] + +- Set the View On Emby Url at runtime so the user can configure and change the URL and it will take effect straight away. [Jamie Rees] + +- Made use of the global mutex, this should now hopefully work #2750. [Jamie Rees] + +- Fixed #2636. [TidusJar] + +- Take out the lastlogindate update for now #2750. [tidusjar] + +- Fixed build. [Jamie Rees] + +- Fixed #2860 When a future series is unknown it should appear as available when we have the other seasons. [Jamie Rees] + +- New translations en.json (German) [Jamie] + +- New translations en.json (Norwegian) [Jamie] + +- Fixed a migration issue. [tidusjar] + +- Set the CommandTimeout longer to see if EF can get a handle on the SQLite file when it's locked #2750. [tidusjar] + +- Prevented #2848 from erroring out causing further issues. [TidusJar] + +- Fixed #2847. [TidusJar] + +- Fixed a regression issue where TV Shows couldn't be requested. [TidusJar] + +- New translations en.json (Polish) [Jamie] + +- New translations en.json (French) [Jamie] + +- Fix typo. [Jeff] + +- Fixed an issue where the Subscribe button was appearing on available movies. [TidusJar] + +- Converted the Plex Jobs to use Quartz. [Jamie] + +- Remove the need for the schedules.db #2994. [tidusjar] + +- Create FUNDING.yml. [Jamie] + +- Logging and slight change to the string matching now not dependant on Thread Culture #2866. [tidusjar] + + +## v3.0.4256 (2019-02-19) + +### **New Features** + +- Update CHANGELOG.md. [Jamie] + +### **Fixes** + +- Fixed #2826. [tidusjar] + + +## v3.0.4248 (2019-02-18) + +### **New Features** + +- Update discord link to follow the scheme of other links. [Tom McClellan] + +- Update issue templates. [Jamie] + +- Update README.md. [Jamie] + +- Update CHANGELOG.md. [Jamie] + +- Added the functionality to remove a user from getting notifications to their mobile device #2780. [tidusjar] + +- Added a demo mode, this will only show movies and shows that are in the public domain. Dam that stupid fruit company. [tidusjar] + +- Added Actor Searching for Movies! [TidusJar] + +- Added the ability to change where the View on Emby link goes to #2730. [TidusJar] + +- Added the request queue to the notifications UI so you can turn it off per notification agent #2747. [TidusJar] + +- Added new classes to the posters #2732. [TidusJar] + +### **Fixes** + +- New translations en.json (Spanish) [Jamie] + +- New translations en.json (Spanish) [Jamie] + +- New translations en.json (Spanish) [Jamie] + +- New translations en.json (Polish) [Jamie] + +- New translations en.json (Polish) [Jamie] + +- New translations en.json (Polish) [Jamie] + +- New translations en.json (Polish) [Jamie] + +- New translations en.json (German) [Jamie] + +- Fix: src/Ombi/package.json to reduce vulnerabilities. [snyk-bot] + +- Fixed #2801 this is when a season is not correctly monitored in sonarr when approved by an admin. [tidusjar] + +- Small improvements to try and mitigate #2750. [tidusjar] + +- Removed some areas where we clear out the cache. This should help with DB locking #2750. [tidusjar] + +- Fixed #2810. [tidusjar] + +- Cannot create an issue comment with the API #2811. [tidusjar] + +- Set the local domain if the Application URL is set for the HELO or EHLO commands. #2636. [tidusjar] + +- New translations en.json (Spanish) [Jamie] + +- Delete ISSUE_TEMPLATE.md. [Jamie] + +- More minor grammatical edits. [Andrew Metzger] + +- Minor grammatical edits. [Andrew Metzger] + +- Fixed #2802 the issue where "Issues" were not being deleted correctly. [tidusjar] + +- Fixed #2797. [tidusjar] + +- New translations en.json (Dutch) [Jamie] + +- New translations en.json (Spanish) [Jamie] + +- New translations en.json (Portuguese, Brazilian) [Jamie] + +- Fixed #2786. [tidusjar] + +- Fixed #2756. [tidusjar] + +- Ignore the UserName header as part of the Api is the value is an empty string. [tidusjar] + +- Fixed #2759. [tidusjar] + +- Did #2756. [TidusJar] + +- Fixed the exception that sometimes makes ombi fallover. [TidusJar] + +- New translations en.json (Swedish) [Jamie] + +- New translations en.json (Swedish) [Jamie] + +- New translations en.json (Swedish) [Jamie] + +- New translations en.json (Swedish) [Jamie] + +- New translations en.json (Swedish) [Jamie] + +- New translations en.json (Swedish) [Jamie] + +- New translations en.json (Swedish) [Jamie] + +- New translations en.json (Swedish) [Jamie] + +- New translations en.json (Swedish) [Jamie] + +- New translations en.json (Swedish) [Jamie] + +- Log the error to the ui to figure out what's going on with #2755. [tidusjar] + +- Small fix when denying a request with a reason, we wasn't updating the ui. [TidusJar] + +- Make sure we can only set the ApiAlias when using the API Key. [tidusjar] + +- #2363 Added the ability to pass any username into the API using the ApiAlias header. [tidusjar] + +- Removed the Add user to Plex from Ombi. [tidusjar] + + +## v3.0.4119 (2019-01-09) + +### **New Features** + +- Update CHANGELOG.md. [Jamie] + +- Added a page where the admin can write/style/basically do whatever they want with e.g. FAQ for the users #2715 This needs to be enabled in the Customization Settings and then it's all configured on the page. [TidusJar] + +- Updated the AspnetCore.App package to remove the CVE-2019-0564 vulnerability. [TidusJar] + +- Added a global language flag that now applies to the search by default. [tidusjar] + +- Updated the frontend packages (Using Angular 7 now) [TidusJar] + +- Added capture of anonymous analytical data. [tidusjar] + +- Added {AvailableDate} as a Notification Variable, this is the date the request was marked as available. See here: https://github.com/tidusjar/Ombi/wiki/Notification-Template-Variables. [tidusjar] + +- Added the ability to search movies via the movie db with a different language! [tidusjar] + +- Added the ability to specify a year when searching for movies. [tidusjar] + +- Update NewsletterTemplate.html. [d1slact0r] + +- Update NewsletterTemplate.html. [d1slact0r] + +- Update NewsletterTemplate.html. [d1slact0r] + +- Update HtmlTemplateGenerator.cs. [d1slact0r] + +- Update NewsletterTemplate.html. [d1slact0r] + +- Update HtmlTemplateGenerator.cs. [d1slact0r] + +- Update NewsletterTemplate.html. [d1slact0r] + +- Update NewsletterTemplate.html. [d1slact0r] + +- Update NewsletterTemplate.html. [d1slact0r] + +- Update HtmlTemplateGenerator.cs. [d1slact0r] + +- Updated boostrap #2694. [Jamie] + +- Added the ability to deny a request with a reason. [TidusJar] + +- Update EmbyEpisodeSync.cs. [Jamie] + +- Updated to .net core 2.2 and included a linux-arm64 build. [TidusJar] + +### **Fixes** + +- There is now a new Job in ombi that will clear out the Plex/Emby data and recache. This will prevent the issues going forward that we have when Ombi and the Media server fall out of sync with deletions/updates #2641 #2362 #1566. [TidusJar] + +- Potentially fix #2726. [TidusJar] + +- New translations en.json (Spanish) [Jamie] + +- New translations en.json (Spanish) [Jamie] + +- New translations en.json (Dutch) [Jamie] + +- Fixed #2725 and #2721. [TidusJar] + +- Made the newsletter use the default lanuage code set in the Ombi settings for movie information. [TidusJar] + +- Save the language code against the request so we can use it later e.g. Sending to the DVR apps. [tidusjar] + +- Fixed #2716. [tidusjar] + +- Make the newsletter BCC the users rather than creating a million newsletters (Hopefully will stop SMTP providers from marking as spam). This does mean that the custom user customization in the newsletter will no longer work. [TidusJar] + +- If we don't know the Plex agent, then see if it's a ImdbId, if it's not check the string for any episode and season hints #2695. [tidusjar] + +- New translations en.json (Swedish) [Jamie] + +- New translations en.json (Spanish) [Jamie] + +- New translations en.json (Portuguese, Brazilian) [Jamie] + +- New translations en.json (Polish) [Jamie] + +- New translations en.json (Norwegian) [Jamie] + +- New translations en.json (Italian) [Jamie] + +- New translations en.json (German) [Jamie] + +- New translations en.json (French) [Jamie] + +- New translations en.json (Dutch) [Jamie] + +- New translations en.json (Danish) [Jamie] + +- New translations en.json (Dutch) [Jamie] + +- New translations en.json (Dutch) [Jamie] + +- New translations en.json (Dutch) [Jamie] + +- Made the search results the language specified in the search refinement. [tidusjar] + +- Fixed #2704. [tidusjar] + +- Now it is fixed :) [d1slact0r] + +- Android please be nice now. [d1slact0r] + +- Fixed title bit better. [d1slact0r] + +- Fixed titles. [d1slact0r] + +- This should fix the build for sure (stupid quotes) [d1slact0r] + +- Fixes build. [d1slact0r] + +- Rewritten the whole newsletter template. [d1slact0r] + +- Fixed #2697. [tidusjar] + +- Add linux-arm runtime identifier. [aptalca] + +- Add back arm packages. [aptalca] + +- Add arm32 package. [aptalca] + +- Fixed #2691. [tidusjar] + +- Fixed linting. [TidusJar] + +- Fixed the Plex OAuth when going through the wizard. [TidusJar] + +- Fixed #2678. [TidusJar] + +- Deny reason for movie requests. [TidusJar] + +- Set the landing and login pages background refresh to 15 seconds rather than 10 and 7. [TidusJar] + +- Fixed a bug with us thinking future dated emby episodes are not available, Consoldated the emby and plex search rules (since they have the same logic) [TidusJar] + +- Fixed build. [TidusJar] + + +## v3.0.4036 (2018-12-11) + +### **New Features** + +- Changelog. [Jamie] + +- Added Sonarr v3 #2359. [TidusJar] + +### **Fixes** + +- !changelog. [Jamie] + +- Fixed a missing translation. [Jamie] + +- Fixed a potential security vulnerability. [Jamie] + +- Sorted out some of the settings pages, trying to make it consistent. [Jamie] + +- #2669 Fixed missing translations. [TidusJar] + +- Maps alias email variable for welcome emails. [Victor Usoltsev] + +- Increased the logo size on the landing page to match the container below it. [Jamie] + +- Think the request queue is done! [Jamie] + +- Finished off the job. [TidusJar] + + +## v3.0.3988 (2018-11-23) + +### **New Features** + +- Updated the emby api since we no longer need the extra parameters to send to emby to log in a local user #2546. [Jamie] + +- Added the ability to get the ombi user via a Plex Token #2591. [Jamie] + +- Update CHANGELOG.md. [Jamie] + +### **Fixes** + +- !changelog. [Jamie] + +- Made the subscribe/unsubscribe button more obvious on the UI #2309. [Jamie] + +- Fixed #2603. [Jamie] + +- Fixed the issue with the user overrides #2646. [Jamie] + +- Fixed the issue where we could sometimes allow the request of a whole series when the user shouldn't be able to. [Jamie] + +- Fixed the issue where we were marking episodes as available with the Emby connection when they have not yet aired #2417 #2623. [TidusJar] + +- Fixed the issue where we were marking the whole season as wanted in Sonarr rather than the individual episode #2629. [TidusJar] + +- Fixed #2623. [Jamie] + +- Fixed #2633. [TidusJar] + +- Fixed #2639. [Jamie] + +- Show the TV show as available when we have all the episodes but future episodes have not aired. #2585. [Jamie] + + +## v3.0.3945 (2018-10-25) + +### **New Features** + +- Update Readme for Lidarr. [Qstick] + +- Update CHANGELOG.md. [Jamie] + +### **Fixes** + +- New translations en.json (French) [Jamie] + +- New translations en.json (French) [Jamie] + +- New translations en.json (French) [Jamie] + +- New translations en.json (Dutch) [Jamie] + +- Fixed the issue with mobile notifications. [Jamie] + +- Fixed #2514. [Jamie] + + +## v3.0.3923 (2018-10-19) + +### **New Features** + +- Update CHANGELOG.md. [Jamie] + +### **Fixes** + +- Fixed #2601. [Jamie] + + +## v3.0.3919 (2018-10-17) + +### **New Features** + +- Added automation tests for the voting feature. [TidusJar] + +- Update LidarrAvailabilityChecker.cs. [Jamie] + +- Update CHANGELOG.md. [Jamie] + +- Changes language selector to always show native language name. [Victor Usoltsev] + +- Updated test dependancies. [TidusJar] + +- Added in the external repo so we can rip out external stuff. [TidusJar] + +- Added the ability to purge/remove issues. [TidusJar] + +### **Fixes** + +- New translations en.json (French) [Jamie] + +- New translations en.json (French) [Jamie] + +- New translations en.json (French) [Jamie] + +- New translations en.json (French) [Jamie] + +- New translations en.json (Swedish) [Jamie] + +- New translations en.json (Spanish) [Jamie] + +- New translations en.json (Portuguese, Brazilian) [Jamie] + +- New translations en.json (Polish) [Jamie] + +- New translations en.json (Norwegian) [Jamie] + +- New translations en.json (Italian) [Jamie] + +- New translations en.json (German) [Jamie] + +- New translations en.json (French) [Jamie] + +- New translations en.json (Dutch) [Jamie] + +- New translations en.json (Danish) [Jamie] + +- When a users requests content and the voting is enabled, the user who requested is an automatic +1 vote. [TidusJar] + +- Revert, no idea how this happened. [TidusJar] + +- Fixed the build. Thanks Matt! [TidusJar] + +- Fixes untickable mass email checkboxes in Safari. [Victor Usoltsev] + +- [ImgBot] optimizes images. [ImgBotApp] + +- Revert "Feature/purge issues" [Jamie] + +- Fixed the issue where user preferences was not being inported into some notifications. [TidusJar] + +- New role to enable users to remove their own requests. [Anojh] + +- Users can now remove their own requests. [Anojh] + +- New translations en.json (Danish) [Jamie] + +- Fixed lidarr newsletter bug. [Jamie] + +- Potentially fix the user profiles issue. [Jamie] + +- Hides Radarr options on movie requests page if only 1 option available. [Victor Usoltsev] + +- Hides Sonarr options on tv requests page if only 1 option available. [Victor Usoltsev] + +- Fixed the issue where we could not delete users #2558. [TidusJar] + +- New translations en.json (German) [Jamie] + +- New translations en.json (Swedish) [Jamie] + +- New translations en.json (Spanish) [Jamie] + +- New translations en.json (Portuguese, Brazilian) [Jamie] + +- New translations en.json (Polish) [Jamie] + +- New translations en.json (Norwegian) [Jamie] + +- New translations en.json (Italian) [Jamie] + +- New translations en.json (German) [Jamie] + +- New translations en.json (French) [Jamie] + +- New translations en.json (Dutch) [Jamie] + +- New translations en.json (Danish) [Jamie] + +- Subscribe the user to the request when they vote on it. [TidusJar] + +- Fixed #2555. [Jamie] + +- Fixed #2549. [Jamie] + +- Removed the pinID from the OAuth url #2548. [Jamie] + +- Put the issue purge limit on the issues page. [Jamie] + +- Date and times are now in the local users date time. [TidusJar] + +- Fixed the migration. [TidusJar] + +- ExternalContext migrations. [TidusJar] + +- The settings have now been split out of the main db. [TidusJar] + +- Search for the Lidarr Album when it's a new artist. [TidusJar] + +- The album in Lidarr does not need to be marked as monitored for us to pick up it's available. Fixes #2536. [Jamie] + +- Truncate the request title. [Jamie] + +- Fixed #2535. [Jamie] + + +## v3.0.3795 (2018-09-23) + +### **New Features** + +- Update CHANGELOG.md. [Jamie] + +### **Fixes** + +- Fixed the issue with notifications not sending. [Jamie] + +- Removes Legacy command result variables. [Qstick] + + +## v3.0.3786 (2018-09-22) + +### **New Features** + +- Update CHANGELOG.md. [Jamie] + +### **Fixes** + +- New translations en.json (Swedish) [Jamie] + +- New translations en.json (Spanish) [Jamie] + +- New translations en.json (Portuguese, Brazilian) [Jamie] + +- New translations en.json (Polish) [Jamie] + +- New translations en.json (Norwegian) [Jamie] + +- New translations en.json (Italian) [Jamie] + +- New translations en.json (German) [Jamie] + +- New translations en.json (French) [Jamie] + +- New translations en.json (Dutch) [Jamie] + +- New translations en.json (Danish) [Jamie] + +- New translations en.json (Swedish) [Jamie] + +- New translations en.json (Spanish) [Jamie] + +- New translations en.json (Portuguese, Brazilian) [Jamie] + +- New translations en.json (Polish) [Jamie] + +- New translations en.json (Norwegian) [Jamie] + +- New translations en.json (Italian) [Jamie] + +- New translations en.json (German) [Jamie] + +- New translations en.json (French) [Jamie] + +- New translations en.json (Dutch) [Jamie] + +- New translations en.json (Danish) [Jamie] + +- New translations en.json (Swedish) [Jamie] + +- New translations en.json (Spanish) [Jamie] + +- New translations en.json (Portuguese, Brazilian) [Jamie] + +- New translations en.json (Polish) [Jamie] + +- New translations en.json (Norwegian) [Jamie] + +- New translations en.json (Italian) [Jamie] + +- New translations en.json (German) [Jamie] + +- New translations en.json (French) [Jamie] + +- New translations en.json (Dutch) [Jamie] + +- New translations en.json (Danish) [Jamie] + +- New translations en.json (Swedish) [Jamie] + +- New translations en.json (German) [Jamie] + +- New translations en.json (German) [Jamie] + +- New translations en.json (Swedish) [Jamie] + +- New translations en.json (Spanish) [Jamie] + +- New translations en.json (Portuguese, Brazilian) [Jamie] + +- New translations en.json (Polish) [Jamie] + +- New translations en.json (Norwegian) [Jamie] + +- New translations en.json (Italian) [Jamie] + +- New translations en.json (German) [Jamie] + +- New translations en.json (French) [Jamie] + +- New translations en.json (Dutch) [Jamie] + +- New translations en.json (Danish) [Jamie] + +- New translations en.json (Portuguese, Brazilian) [Jamie] + +- Fix #2529 - Change data type to long. [Anojh] + +- Fix #2527 - Music request not triggering search and failing. [Anojh] + + +## v3.0.3776 (2018-09-21) + +### **New Features** + +- Update settingsmenu.component.html. [Jamie] + +- Added the request limits in the ui for music. [Jamie] + +- Added the root folders and qualities per user! [Jamie] + +- Updated all the MS packages. [TidusJar] + +- Update the .net core packages to fix "CVE-2018-8409: ASP.NET Core Denial Of Service Vulnerability" [TidusJar] + +- Change way remainingrequests component is notified. [Kenton Royal] + +- Added the music request limits. [TidusJar] + +- Added the Notification Preferences to the user. [TidusJar] + +- Added the API to add user notification preferences. [TidusJar] + +- Added more logging into the updater. [Jamie] + +- Update CHANGELOG.md. [Jamie] + +### **Fixes** + +- Fixed #2518. [TidusJar] + +- Fixed #2522. [TidusJar] + +- Fixed #2485. [TidusJar] + +- Fixed #2516. [TidusJar] + +- Fix bug in which requested TV wasn't logging for some users. [Kenton Royal] + +- Add to translations. [Kenton Royal] + +- Add html for displaying remaining requests on users page. [Kenton Royal] + +- Add quota fields to user view model. [Kenton Royal] + +- Users can now see the music search tab #2493. [TidusJar] + +- Add href to a tags so that a pointer cursor shows on requests page. [Stephen Panzer] + +- Allow Lidarr to specify if we should search for the album. [TidusJar] + +- Fixed the issue if in Radarr we only want to add and monitor, if the movie already exists we search for it. [TidusJar] + +- Fix bug causing wrong time to be displayed for next request. [Kenton Royal] + +- Bodge fix test to prevent compile error. [Kenton Royal] + +- Fix displaying year in issue dialog. [Stephen Panzer] + +- Add clearfix class. Closes #2486. [Stephen Panzer] + +- Correct path of lidarr component import for unix systems. [Kenton Royal] + +- Refactor code. [Kenton Royal] + +- Fix formatting error. [Kenton Royal] + +- Revert "Revert request.service.ts to version on upstream/develop" [Kenton Royal] + +- Revert request.service.ts to version on upstream/develop. [Kenton Royal] + +- Fix lint errors. [Kenton Royal] + +- Move logic for notifying when reuqest is complete. [Kenton Royal] + +- Remove import. [Kenton Royal] + +- Remove unused module. [Kenton Royal] + +- Refactor code. [Kenton Royal] + +- Add text to translation file. [Kenton Royal] + +- Fix query for fetching requested tv shows. [Kenton Royal] + +- Add vscode to gitignore. [Kenton Royal] + +- Fix lint errors. [Kenton Royal] + +- Remove unused methods from SearchController. [Kenton Royal] + +- Remove local vscode files. [Kenton Royal] + +- Fix bug when submitting requests for multiple episodes accross multiple seasons. [Kenton Royal] + +- Fix bug with TV requests in which requesting a seasion would treat request as single episode. [Kenton Royal] + +- Fix issues with remaining count updating. [Kenton Royal] + +- Trigger update of request limit on new request. [Kenton Royal] + +- Add logic for movie request count. [Kenton Royal] + +- Add logic for retriving request information. [Kenton Royal] + +- Move to seperate component and display for both TV and movies. [Kenton Royal] + +- Add dummy for request counter. [Kenton Royal] + +- Fix scss import for unix systems. [Kenton Royal] + +- Add methods to interface and add model class. [Kenton Royal] + +- !fixed lint. [TidusJar] + +- Fixed #2481. [TidusJar] + +- New translations en.json (Swedish) [Jamie] + +- New translations en.json (Spanish) [Jamie] + +- New translations en.json (Portuguese, Brazilian) [Jamie] + +- New translations en.json (Polish) [Jamie] + +- New translations en.json (Norwegian) [Jamie] + +- New translations en.json (Italian) [Jamie] + +- New translations en.json (German) [Jamie] + +- New translations en.json (French) [Jamie] + +- New translations en.json (Dutch) [Jamie] + +- New translations en.json (Danish) [Jamie] + +- Fixed #2475. [Jamie] + +- Stript out certain characters when sending a pushover message #2385. [TidusJar] + +- Add default values for Priority and Sound. [David Pooley] + +- Allow for the ability to set Pushover notification sound and priority from within Ombi. [David Pooley] + +- It works now when we request an album when we do not have the artist in Lidarr. Waiting on https://github.com/lidarr/Lidarr/issues/459 to do when we have the artist. [Jamie] + +- Fix non-Windows builds. Fixes #2453. [Joe Groocock] + + +## v3.0.3587 (2018-08-19) + +### **New Features** + +- Added the ability to invite Plex Friends from the user management screen. [Jamie] + +- Added rich notifications for mobile. [Jamie] + +- Updater fixes. [Jamie] + +- Added updater test mode. [Jamie Rees] + +- Added a new API method to delete issue comments. [TidusJar] + +- Updated @ngu/carousel to beta version to remove rxjs-compat dependency. [Matt Jeanes] + +- Update to Angular 6/Webpack 4. [Matt Jeanes] + +- Update CHANGELOG.md. [Jamie] + +- Updated the way we create the wizard user, errors show now be fed back to the user. [Jamie] + +- Added Brazillian Portuguese as a language and also Polish. [Jamie] + +- Updated swagger. [Jamie] + +- Updated to 2.1.1. [Jamie] + +### **Fixes** + +- Now include the release year in the issue title #2381. [TidusJar] + +- Made the OAuth a Popout to work with Org. [Jamie] + +- Fixed #2418. [TidusJar] + +- #2408 Added the feature to delete comments on issues. [Jamie] + +- New translations en.json (Swedish) [Jamie] + +- New translations en.json (French) [Jamie] + +- Fixed #2440. [TidusJar] + +- Delete cake.config. [Chris Pritchard] + +- Initial attempt at getting anime seriestype working. [Chris Pritchard] + +- Add cake.config. [Chris Pritchard] + +- Fixed the issue where we wouldn't correctly mark some shows as available when there was no provider id #2429. [Jamie] + +- Fixed the 'loop' in the cacher #2429. [Jamie] + +- Fixed #2427. [Jamie] + +- Fixed #2424. [Jamie] + +- Fixed #2409. [Jamie] + +- More updater. [Jamie] + +- Humanize the request type enum in notifications e.g. TvShow will now appear as "Tv Show" #2416. [TidusJar] + +- Made the quality override and root folder override load when we load the show (It will now appear) [Jamie] + +- Fixed #2415 where power users could not set the Sonarr Quality Override or Root Folder Override. [Jamie] + +- #2371 Fixed the issue where certain actions would not setup the series correctly in Sonarr. [Jamie] + +- Tightened up the security from an API perspecitve. [TidusJar] + +- Stop the root folder and profile calls from erroring. [TidusJar] + +- New translations en.json (Polish) [Jamie] + +- New translations en.json (Polish) [Jamie] + +- New translations en.json (Polish) [Jamie] + +- New translations en.json (Portuguese, Brazilian) [Jamie] + +- New translations en.json (Portuguese, Brazilian) [Jamie] + +- New translations en.json (Portuguese, Brazilian) [Jamie] + +- New translations en.json (Portuguese, Brazilian) [Jamie] + +- New translations en.json (Portuguese, Brazilian) [Jamie] + +- Fixed all linting. [TidusJar] + +- Comment out envparam stuff. [Matt Jeanes] + +- Fixed prod build issue. [Matt Jeanes] + +- Missed a tiny bit. [Matt Jeanes] + +- Fix test. [Matt Jeanes] + +- Fix test build. [Matt Jeanes] + +- Linting + remove debug. [Matt Jeanes] + +- Switch to Yarn and disable auto publish in release mode. [Matt Jeanes] + +- Fix for #2409. [TidusJar] + +- New translations en.json (Swedish) [Jamie] + +- New translations en.json (Spanish) [Jamie] + +- New translations en.json (Portuguese, Brazilian) [Jamie] + +- New translations en.json (Polish) [Jamie] + +- New translations en.json (Norwegian) [Jamie] + +- New translations en.json (Italian) [Jamie] + +- New translations en.json (German) [Jamie] + +- New translations en.json (French) [Jamie] + +- New translations en.json (Dutch) [Jamie] + +- New translations en.json (Danish) [Jamie] + +- Possible fix for #2298. [D34DC3N73R] + +- Fixed the text for #2370. [Jamie] + +- Fixed where you couldn't bulk edit the limits to 0 #2318. [Jamie] + +- Upgraded to .net 2.1.2 (Includes security fixes) [Jamie] + + +## v3.0.3477 (2018-07-18) + +### **New Features** + - Updated the Emby availability checker to bring it more in line with what we do with Plex. [TidusJar] - Added the ability to impersonate a user when using the API Key. This allows people to use the API and request as a certain user. #2363. [Jamie Rees] diff --git a/README.md b/README.md index cfc0ad23c..e8e269e7f 100644 --- a/README.md +++ b/README.md @@ -9,11 +9,19 @@ ____ [![Patreon](https://img.shields.io/badge/patreon-donate-yellow.svg)](https://patreon.com/tidusjar/Ombi) [![Paypal](https://img.shields.io/badge/paypal-donate-yellow.svg)](https://paypal.me/PlexRequestsNet) +___ + +[![Twitter](https://img.shields.io/twitter/follow/tidusjar.svg?style=social)](https://twitter.com/intent/follow?screen_name=tidusjar) + +Follow me developing Ombi! + +[![Twitch](https://img.shields.io/badge/Twitch-Watch-blue.svg?style=flat-square&logo=twitch)](https://www.twitch.tv/tidusjar) + + ___ Get it on Google Play - -Get it on App Store - +
+_**Note:** There is no longer an iOS app due to complications outside of our control._ ___ @@ -32,8 +40,9 @@ ___ # Features Here are some of the features Ombi V3 has: * Now working without crashes on Linux. -* Lets users request Movies and TV Shows (whether it being the entire series, an entire season, or even single episodes.) +* Lets users request Movies, Music, and TV Shows (whether it being the entire series, an entire season, or even single episodes.) * Easily manage your requests +* Allows you to set specific users to automatically have requests approved and added to the relevant service (Sonarr/Radarr/Lidarr/Couchpotato etc) * User management system (supports plex.tv, Emby and local accounts) * A landing page that will give you the availability of your Plex/Emby server and also add custom notification text to inform your users of downtime. * Allows your users to get custom notifications! @@ -41,7 +50,7 @@ Here are some of the features Ombi V3 has: * Will show if the request is already on plex or even if it's already monitored. * Automatically updates the status of requests when they are available on Plex/Emby * Slick, responsive and mobile friendly UI -* Ombi will automatically update itself :) +* Ombi will automatically update itself :) (YMMV) * Very fast! ### Integration @@ -50,6 +59,7 @@ We integrate with the following applications: * Emby * Sonarr * Radarr +* Lidarr * DogNzb * Couch Potato @@ -58,6 +68,7 @@ We integrate with the following applications: Supported notifications: * SMTP Notifications (Email) * Discord +* Gotify * Slack * Pushbullet * Pushover @@ -87,6 +98,7 @@ We are planning to bring back these features in V3 but for now you can find a li | DogNzb | Yes | No | | Issues | Yes | Yes | | Headphones | No | Yes | +| Lidarr | Yes | No | # Feature Requests Feature requests are handled on FeatHub. @@ -115,13 +127,12 @@ Please feel free to submit a pull request! # Donation If you feel like donating you can donate with the below buttons! -[![Patreon](https://www.ombi.io/img/patreondonate.svg)](https://patreon.com/tidusjar/Ombi) -[![Paypal](https://www.ombi.io/img/paypaldonate.svg)](https://paypal.me/PlexRequestsNet) + +[![Patreon](https://img.shields.io/badge/patreon-donate-yellow.svg)](https://patreon.com/tidusjar/Ombi) +[![Paypal](https://img.shields.io/badge/paypal-donate-yellow.svg)](https://paypal.me/PlexRequestsNet) ### A massive thanks to everyone for all their help! -## Stats -[![Throughput Graph](https://graphs.waffle.io/tidusjar/PlexRequests.Net/throughput.svg)](https://waffle.io/tidusjar/PlexRequests.Net/metrics/throughput) ### Sponsors ### - [JetBrains](http://www.jetbrains.com/) for providing us with free licenses to their great tools diff --git a/appveyor.yml b/appveyor.yml index 862993a21..3c60a0006 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -3,34 +3,46 @@ configuration: Release os: Visual Studio 2017 environment: nodejs_version: "9.8.0" + typescript_version: "3.0.1" + github_auth_token: + secure: H/7uCrjmWHGJxgN3l9fbhhdVjvvWI8VVF4ZzQqeXuJwAf+PgSNBdxv4SS+rMQ+RH + sonarrcloudtoken: + secure: WGkIog4wuMSx1q5vmSOgIBXMtI/leMpLbZhi9MJnJdBBuDfcv12zwXg3LQwY0WbE install: # Get the latest stable version of Node.js or io.js - ps: Install-Product node $env:nodejs_version + + - cmd: set path=%programfiles(x86)%\\Microsoft SDKs\TypeScript\3.0;%path% + - cmd: tsc -v build_script: + # - dotnet tool install --global dotnet-sonarscanner + #- ps: if ($env:APPVEYOR_PULL_REQUEST_NUMBER) { dotnet sonarscanner begin /k:"tidusjar_Ombi" /d:sonar.organization="tidusjar-github" /d:sonar.host.url="https://sonarcloud.io" /d:sonar.login="$env.sonarrcloud_token" /d:sonar.analysis.mode="preview" /d:sonar.github.pullRequest="$env:APPVEYOR_PULL_REQUEST_NUMBER" /d:sonar.github.repository="https://github.com/tidusjar/ombi" /d:sonar.github.oauth="$env.github_auth_token" } + # - ps: if (-Not $env:APPVEYOR_PULL_REQUEST_NUMBER) { dotnet sonarscanner begin /k:"tidusjar_Ombi" /d:sonar.organization="tidusjar-github" /d:sonar.host.url="https://sonarcloud.io" /d:sonar.login="$env.SONARRCLOUDTOKEN" } - ps: ./build.ps1 --settings_skipverification=true + # - dotnet sonarscanner end /d:sonar.login="%sonarrcloudtoken%" test: off - + after_build: - cmd: >- - appveyor PushArtifact "%APPVEYOR_BUILD_FOLDER%\src\Ombi\bin\Release\netcoreapp2.1\windows.zip" + appveyor PushArtifact "%APPVEYOR_BUILD_FOLDER%\src\Ombi\bin\Release\netcoreapp2.2\windows.zip" - appveyor PushArtifact "%APPVEYOR_BUILD_FOLDER%\src\Ombi\bin\Release\netcoreapp2.1\osx.tar.gz" + appveyor PushArtifact "%APPVEYOR_BUILD_FOLDER%\src\Ombi\bin\Release\netcoreapp2.2\osx.tar.gz" - appveyor PushArtifact "%APPVEYOR_BUILD_FOLDER%\src\Ombi\bin\Release\netcoreapp2.1\linux.tar.gz" + appveyor PushArtifact "%APPVEYOR_BUILD_FOLDER%\src\Ombi\bin\Release\netcoreapp2.2\linux.tar.gz" + + + appveyor PushArtifact "%APPVEYOR_BUILD_FOLDER%\src\Ombi\bin\Release\netcoreapp2.2\linux-arm.tar.gz" + + + appveyor PushArtifact "%APPVEYOR_BUILD_FOLDER%\src\Ombi\bin\Release\netcoreapp2.2\windows-32bit.zip" - appveyor PushArtifact "%APPVEYOR_BUILD_FOLDER%\src\Ombi\bin\Release\netcoreapp2.1\linux-arm.tar.gz" - - - appveyor PushArtifact "%APPVEYOR_BUILD_FOLDER%\src\Ombi\bin\Release\netcoreapp2.1\windows-32bit.zip" - - -# appveyor PushArtifact "%APPVEYOR_BUILD_FOLDER%\src\Ombi\bin\Release\netcoreapp2.0\linux-arm64.tar.gz" + appveyor PushArtifact "%APPVEYOR_BUILD_FOLDER%\src\Ombi\bin\Release\netcoreapp2.2\linux-arm64.tar.gz" diff --git a/assets/music-placeholder.psd b/assets/music-placeholder.psd new file mode 100644 index 000000000..5715f850a Binary files /dev/null and b/assets/music-placeholder.psd differ diff --git a/build.cake b/build.cake index d200eac98..b2ae8842c 100644 --- a/build.cake +++ b/build.cake @@ -1,10 +1,10 @@ #tool "nuget:?package=GitVersion.CommandLine" #addin "Cake.Gulp" -#addin "nuget:?package=Cake.Npm&version=0.13.0" #addin "SharpZipLib" #addin nuget:?package=Cake.Compression&version=0.1.4 -#addin "Cake.Incubator" +#addin "Cake.Incubator&version=3.1.0" +#addin "Cake.Yarn" ////////////////////////////////////////////////////////////////////// // ARGUMENTS @@ -26,7 +26,7 @@ var csProj = "./src/Ombi/Ombi.csproj"; // Path to the project.csproj var solutionFile = "Ombi.sln"; // Solution file if needed GitVersion versionInfo = null; -var frameworkVer = "netcoreapp2.1"; +var frameworkVer = "netcoreapp2.2"; var buildSettings = new DotNetCoreBuildSettings { @@ -81,9 +81,9 @@ Task("SetVersionInfo") versionInfo = GitVersion(settings); - Information("GitResults -> {0}", versionInfo.Dump()); +// Information("GitResults -> {0}", versionInfo.Dump()); - Information(@"Build:{0}",AppVeyor.Environment.Build.Dump()); +//Information(@"Build:{0}",AppVeyor.Environment.Build.Dump()); var buildVersion = string.Empty; if(string.IsNullOrEmpty(AppVeyor.Environment.Build.Version)) @@ -122,36 +122,19 @@ Task("SetVersionInfo") Task("NPM") .Does(() => { - var settings = new NpmInstallSettings { - LogLevel = NpmLogLevel.Silent, - WorkingDirectory = webProjDir, - Production = true - }; - - NpmInstall(settings); + Yarn.FromPath(webProjDir).Install(); }); Task("Gulp Publish") .IsDependentOn("NPM") - .Does(() => { - - var runScriptSettings = new NpmRunScriptSettings { - ScriptName="publish", - WorkingDirectory = webProjDir, - }; - - NpmRunScript(runScriptSettings); + .Does(() => { + Yarn.FromPath(webProjDir).RunScript("publish"); }); Task("TSLint") .Does(() => { - var settings = new NpmRunScriptSettings { - WorkingDirectory = webProjDir, - ScriptName = "lint" - }; - - NpmRunScript(settings); + Yarn.FromPath(webProjDir).RunScript("lint"); }); Task("PrePublish") @@ -168,7 +151,7 @@ Task("Package") GZipCompress(osxArtifactsFolder, artifactsFolder + "osx.tar.gz"); GZipCompress(linuxArtifactsFolder, artifactsFolder + "linux.tar.gz"); GZipCompress(linuxArmArtifactsFolder, artifactsFolder + "linux-arm.tar.gz"); - //GZipCompress(linuxArm64BitArtifactsFolder, artifactsFolder + "linux-arm64.tar.gz"); + GZipCompress(linuxArm64BitArtifactsFolder, artifactsFolder + "linux-arm64.tar.gz"); }); Task("Publish") @@ -178,7 +161,7 @@ Task("Publish") .IsDependentOn("Publish-OSX") .IsDependentOn("Publish-Linux") .IsDependentOn("Publish-Linux-ARM") - //.IsDependentOn("Publish-Linux-ARM-64Bit") + .IsDependentOn("Publish-Linux-ARM-64Bit") .IsDependentOn("Package"); Task("Publish-Windows") @@ -189,6 +172,8 @@ Task("Publish-Windows") DotNetCorePublish("./src/Ombi/Ombi.csproj", publishSettings); CopyFile(buildDir + "/"+frameworkVer+"/win10-x64/Swagger.xml", buildDir + "/"+frameworkVer+"/win10-x64/published/Swagger.xml"); + + publishSettings.OutputDirectory = Directory(buildDir) + Directory(frameworkVer +"/win10-x64/published/updater"); DotNetCorePublish("./src/Ombi.Updater/Ombi.Updater.csproj", publishSettings); }); @@ -200,6 +185,9 @@ Task("Publish-Windows-32bit") DotNetCorePublish("./src/Ombi/Ombi.csproj", publishSettings); CopyFile(buildDir + "/"+frameworkVer+"/win10-x86/Swagger.xml", buildDir + "/"+frameworkVer+"/win10-x86/published/Swagger.xml"); + + + publishSettings.OutputDirectory = Directory(buildDir) + Directory(frameworkVer +"/win10-x86/published/updater"); DotNetCorePublish("./src/Ombi.Updater/Ombi.Updater.csproj", publishSettings); }); @@ -211,6 +199,8 @@ Task("Publish-OSX") DotNetCorePublish("./src/Ombi/Ombi.csproj", publishSettings); CopyFile(buildDir + "/"+frameworkVer+"/osx-x64/Swagger.xml", buildDir + "/"+frameworkVer+"/osx-x64/published/Swagger.xml"); + + publishSettings.OutputDirectory = Directory(buildDir) + Directory(frameworkVer +"/osx-x64/published/updater"); DotNetCorePublish("./src/Ombi.Updater/Ombi.Updater.csproj", publishSettings); }); @@ -222,6 +212,8 @@ Task("Publish-Linux") DotNetCorePublish("./src/Ombi/Ombi.csproj", publishSettings); CopyFile(buildDir + "/"+frameworkVer+"/linux-x64/Swagger.xml", buildDir + "/"+frameworkVer+"/linux-x64/published/Swagger.xml"); + + publishSettings.OutputDirectory = Directory(buildDir) + Directory(frameworkVer +"/linux-x64/published/updater"); DotNetCorePublish("./src/Ombi.Updater/Ombi.Updater.csproj", publishSettings); }); @@ -235,6 +227,8 @@ Task("Publish-Linux-ARM") CopyFile( buildDir + "/"+frameworkVer+"/linux-arm/Swagger.xml", buildDir + "/"+frameworkVer+"/linux-arm/published/Swagger.xml"); + + publishSettings.OutputDirectory = Directory(buildDir) + Directory(frameworkVer +"/linux-arm/published/updater"); DotNetCorePublish("./src/Ombi.Updater/Ombi.Updater.csproj", publishSettings); }); @@ -248,6 +242,8 @@ Task("Publish-Linux-ARM-64Bit") CopyFile( buildDir + "/"+frameworkVer+"/linux-arm64/Swagger.xml", buildDir + "/"+frameworkVer+"/linux-arm64/published/Swagger.xml"); + + publishSettings.OutputDirectory = Directory(buildDir) + Directory(frameworkVer +"/linux-arm64/published/updater"); DotNetCorePublish("./src/Ombi.Updater/Ombi.Updater.csproj", publishSettings); }); diff --git a/src/Ombi.Api.Emby/EmbyApi.cs b/src/Ombi.Api.Emby/EmbyApi.cs index fcb989094..7cb702fbc 100644 --- a/src/Ombi.Api.Emby/EmbyApi.cs +++ b/src/Ombi.Api.Emby/EmbyApi.cs @@ -46,6 +46,17 @@ namespace Ombi.Api.Emby return obj; } + public async Task GetPublicInformation(string baseUrl) + { + var request = new Request("emby/System/Info/public", baseUrl, HttpMethod.Get); + + AddHeaders(request, string.Empty); + + var obj = await Api.Request(request); + + return obj; + } + public async Task LogIn(string username, string password, string apiKey, string baseUri) { var request = new Request("emby/users/authenticatebyname", baseUri, HttpMethod.Post); @@ -53,8 +64,6 @@ namespace Ombi.Api.Emby { username, pw = password, - password = password.GetSha1Hash().ToLower(), - passwordMd5 = password.CalcuateMd5Hash() }; request.AddJsonBody(body); @@ -98,7 +107,7 @@ namespace Ombi.Api.Emby request.AddQueryString("Fields", "ProviderIds,Overview"); - request.AddQueryString("VirtualItem", "False"); + request.AddQueryString("IsVirtualItem", "False"); return await Api.Request>(request); } @@ -126,6 +135,7 @@ namespace Ombi.Api.Emby { return await GetInformation(mediaId, apiKey, userId, baseUrl); } + public async Task GetEpisodeInformation(string mediaId, string apiKey, string userId, string baseUrl) { return await GetInformation(mediaId, apiKey, userId, baseUrl); @@ -149,7 +159,7 @@ namespace Ombi.Api.Emby request.AddQueryString("IncludeItemTypes", type); request.AddQueryString("Fields", includeOverview ? "ProviderIds,Overview" : "ProviderIds"); - request.AddQueryString("VirtualItem", "False"); + request.AddQueryString("IsVirtualItem", "False"); AddHeaders(request, apiKey); @@ -167,7 +177,7 @@ namespace Ombi.Api.Emby request.AddQueryString("startIndex", startIndex.ToString()); request.AddQueryString("limit", count.ToString()); - request.AddQueryString("VirtualItem", "False"); + request.AddQueryString("IsVirtualItem", "False"); AddHeaders(request, apiKey); diff --git a/src/Ombi.Api.Emby/IEmbyApi.cs b/src/Ombi.Api.Emby/IEmbyApi.cs index b4641ea5f..3c29878b7 100644 --- a/src/Ombi.Api.Emby/IEmbyApi.cs +++ b/src/Ombi.Api.Emby/IEmbyApi.cs @@ -29,5 +29,6 @@ namespace Ombi.Api.Emby Task GetSeriesInformation(string mediaId, string apiKey, string userId, string baseUrl); Task GetMovieInformation(string mediaId, string apiKey, string userId, string baseUrl); Task GetEpisodeInformation(string mediaId, string apiKey, string userId, string baseUrl); + Task GetPublicInformation(string baseUrl); } } \ No newline at end of file diff --git a/src/Ombi.Api.Emby/Models/Media/Tv/EmbyEpisodes.cs b/src/Ombi.Api.Emby/Models/Media/Tv/EmbyEpisodes.cs index d76915923..83d64ed15 100644 --- a/src/Ombi.Api.Emby/Models/Media/Tv/EmbyEpisodes.cs +++ b/src/Ombi.Api.Emby/Models/Media/Tv/EmbyEpisodes.cs @@ -16,6 +16,7 @@ namespace Ombi.Api.Emby.Models.Media.Tv public int ProductionYear { get; set; } public bool IsPlaceHolder { get; set; } public int IndexNumber { get; set; } + public int? IndexNumberEnd { get; set; } public int ParentIndexNumber { get; set; } public bool IsHD { get; set; } public bool IsFolder { get; set; } diff --git a/src/Ombi.Api.Emby/Models/PublicInfo.cs b/src/Ombi.Api.Emby/Models/PublicInfo.cs new file mode 100644 index 000000000..01432d3c5 --- /dev/null +++ b/src/Ombi.Api.Emby/Models/PublicInfo.cs @@ -0,0 +1,19 @@ +namespace Ombi.Api.Emby.Models +{ + public class PublicInfo + { + public string LocalAddress { get; set; } + public string ServerName { get; set; } + public string Version { get; set; } + /// + /// Only populated for Jellyfin + /// + public string ProductName { get; set; } + + public bool IsJellyfin => !string.IsNullOrEmpty(ProductName) && ProductName.Contains("Jellyfin"); + + public string OperatingSystem { get; set; } + public string Id { get; set; } + } + +} \ No newline at end of file diff --git a/src/Ombi.Api.Github/GithubApi.cs b/src/Ombi.Api.Github/GithubApi.cs index f7b3fbcbf..6865b33fe 100644 --- a/src/Ombi.Api.Github/GithubApi.cs +++ b/src/Ombi.Api.Github/GithubApi.cs @@ -24,16 +24,5 @@ namespace Ombi.Api.Github request.AddHeader("User-Agent", "Ombi"); return await _api.Request>(request); } - - public async Task GetThemesRawContent(string url) - { - var sections = url.Split('/'); - var lastPart = sections.Last(); - url = url.Replace(lastPart, string.Empty); - var request = new Request(lastPart, url, HttpMethod.Get); - request.AddHeader("Accept", "application/vnd.github.v3+json"); - request.AddHeader("User-Agent", "Ombi"); - return await _api.RequestContent(request); - } } } diff --git a/src/Ombi.Api.Github/IGithubApi.cs b/src/Ombi.Api.Github/IGithubApi.cs index 307158b72..1cca37f02 100644 --- a/src/Ombi.Api.Github/IGithubApi.cs +++ b/src/Ombi.Api.Github/IGithubApi.cs @@ -7,6 +7,5 @@ namespace Ombi.Api.Github public interface IGithubApi { Task> GetCakeThemes(); - Task GetThemesRawContent(string url); } } \ No newline at end of file diff --git a/src/Ombi.Api.Gotify/GotifyApi.cs b/src/Ombi.Api.Gotify/GotifyApi.cs new file mode 100644 index 000000000..8cd79a689 --- /dev/null +++ b/src/Ombi.Api.Gotify/GotifyApi.cs @@ -0,0 +1,36 @@ +using System.Net.Http; +using System.Threading.Tasks; + +namespace Ombi.Api.Gotify +{ + public class GotifyApi : IGotifyApi + { + public GotifyApi(IApi api) + { + _api = api; + } + + private readonly IApi _api; + + public async Task PushAsync(string baseUrl, string accessToken, string subject, string body, sbyte priority) + { + var request = new Request("/message", baseUrl, HttpMethod.Post); + request.AddQueryString("token", accessToken); + + request.AddHeader("Access-Token", accessToken); + request.ApplicationJsonContentType(); + + + var jsonBody = new + { + message = body, + title = subject, + priority = priority + }; + + request.AddJsonBody(jsonBody); + + await _api.Request(request); + } + } +} diff --git a/src/Ombi.Api.Gotify/IGotifyApi.cs b/src/Ombi.Api.Gotify/IGotifyApi.cs new file mode 100644 index 000000000..e6a6b4060 --- /dev/null +++ b/src/Ombi.Api.Gotify/IGotifyApi.cs @@ -0,0 +1,9 @@ +using System.Threading.Tasks; + +namespace Ombi.Api.Gotify +{ + public interface IGotifyApi + { + Task PushAsync(string endpoint, string accessToken, string subject, string body, sbyte priority); + } +} \ No newline at end of file diff --git a/src/Ombi.Api.Gotify/Ombi.Api.Gotify.csproj b/src/Ombi.Api.Gotify/Ombi.Api.Gotify.csproj new file mode 100644 index 000000000..ce5475fae --- /dev/null +++ b/src/Ombi.Api.Gotify/Ombi.Api.Gotify.csproj @@ -0,0 +1,15 @@ + + + + netstandard2.0 + 3.0.0.0 + 3.0.0.0 + + + + + + + + + diff --git a/src/Ombi.Api.Lidarr/ILidarrApi.cs b/src/Ombi.Api.Lidarr/ILidarrApi.cs new file mode 100644 index 000000000..b542ff0a0 --- /dev/null +++ b/src/Ombi.Api.Lidarr/ILidarrApi.cs @@ -0,0 +1,27 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using Ombi.Api.Lidarr.Models; + +namespace Ombi.Api.Lidarr +{ + public interface ILidarrApi + { + Task> AlbumLookup(string searchTerm, string apiKey, string baseUrl); + Task> ArtistLookup(string searchTerm, string apiKey, string baseUrl); + Task> GetProfiles(string apiKey, string baseUrl); + Task> GetRootFolders(string apiKey, string baseUrl); + Task GetArtist(int artistId, string apiKey, string baseUrl); + Task GetArtistByForeignId(string foreignArtistId, string apiKey, string baseUrl); + Task GetAlbumsByArtist(string foreignArtistId); + Task GetAlbumByForeignId(string foreignArtistId, string apiKey, string baseUrl); + Task> GetArtists(string apiKey, string baseUrl); + Task> GetAllAlbums(string apiKey, string baseUrl); + Task AddArtist(ArtistAdd artist, string apiKey, string baseUrl); + Task MontiorAlbum(int albumId, string apiKey, string baseUrl); + Task> GetAllAlbumsByArtistId(int artistId, string apiKey, string baseUrl); + Task> GetMetadataProfile(string apiKey, string baseUrl); + Task Status(string apiKey, string baseUrl); + Task AlbumSearch(int[] albumIds, string apiKey, string baseUrl); + Task AlbumInformation(string albumId, string apiKey, string baseUrl); + } +} \ No newline at end of file diff --git a/src/Ombi.Api.Lidarr/LidarrApi.cs b/src/Ombi.Api.Lidarr/LidarrApi.cs new file mode 100644 index 000000000..dd589c64d --- /dev/null +++ b/src/Ombi.Api.Lidarr/LidarrApi.cs @@ -0,0 +1,188 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Ombi.Api.Lidarr.Models; + +namespace Ombi.Api.Lidarr +{ + public class LidarrApi : ILidarrApi + { + public LidarrApi(ILogger logger, IApi api) + { + Api = api; + Logger = logger; + } + + private IApi Api { get; } + private ILogger Logger { get; } + + private const string ApiVersion = "/api/v1"; + + public Task> GetProfiles(string apiKey, string baseUrl) + { + var request = new Request($"{ApiVersion}/qualityprofile", baseUrl, HttpMethod.Get); + + AddHeaders(request, apiKey); + return Api.Request>(request); + } + + public Task> GetRootFolders(string apiKey, string baseUrl) + { + var request = new Request($"{ApiVersion}/rootfolder", baseUrl, HttpMethod.Get); + + AddHeaders(request, apiKey); + return Api.Request>(request); + } + + public async Task> ArtistLookup(string searchTerm, string apiKey, string baseUrl) + { + var request = new Request($"{ApiVersion}/Artist/lookup", baseUrl, HttpMethod.Get); + request.AddQueryString("term", searchTerm); + + AddHeaders(request, apiKey); + return await Api.Request>(request); + } + + public Task> AlbumLookup(string searchTerm, string apiKey, string baseUrl) + { + var request = new Request($"{ApiVersion}/Album/lookup", baseUrl, HttpMethod.Get); + request.AddQueryString("term", searchTerm); + + AddHeaders(request, apiKey); + return Api.Request>(request); + } + + public Task GetArtist(int artistId, string apiKey, string baseUrl) + { + var request = new Request($"{ApiVersion}/artist/{artistId}", baseUrl, HttpMethod.Get); + + AddHeaders(request, apiKey); + return Api.Request(request); + } + + public async Task GetArtistByForeignId(string foreignArtistId, string apiKey, string baseUrl) + { + var request = new Request($"{ApiVersion}/artist/lookup", baseUrl, HttpMethod.Get); + + request.AddQueryString("term", $"lidarr:{foreignArtistId}"); + AddHeaders(request, apiKey); + return (await Api.Request>(request)).FirstOrDefault(); + } + + public async Task GetAlbumByForeignId(string foreignArtistId, string apiKey, string baseUrl) + { + var request = new Request($"{ApiVersion}/album/lookup", baseUrl, HttpMethod.Get); + + request.AddQueryString("term", $"lidarr:{foreignArtistId}"); + AddHeaders(request, apiKey); + var albums = await Api.Request>(request); + return albums.FirstOrDefault(); + } + + public Task GetAlbumsByArtist(string foreignArtistId) + { + var request = new Request(string.Empty, $"https://api.lidarr.audio/api/v0.4/artist/{foreignArtistId}", + HttpMethod.Get) {IgnoreBaseUrlAppend = true}; + return Api.Request(request); + } + + public Task> GetArtists(string apiKey, string baseUrl) + { + var request = new Request($"{ApiVersion}/artist", baseUrl, HttpMethod.Get); + + AddHeaders(request, apiKey); + return Api.Request>(request); + } + + public Task> GetAllAlbums(string apiKey, string baseUrl) + { + var request = new Request($"{ApiVersion}/album", baseUrl, HttpMethod.Get); + + AddHeaders(request, apiKey); + return Api.Request>(request); + } + + public async Task AlbumInformation(string albumId, string apiKey, string baseUrl) + { + var request = new Request($"{ApiVersion}/album", baseUrl, HttpMethod.Get); + request.AddQueryString("foreignAlbumId", albumId); + AddHeaders(request, apiKey); + var albums = await Api.Request>(request); + return albums.FirstOrDefault(); + } + + + /// + /// THIS ONLY SUPPORTS ALBUMS THAT THE ARTIST IS IN LIDARR + /// + /// + /// + /// + /// + public Task> GetTracksForAlbum(int albumId, string apiKey, string baseUrl) + { + var request = new Request($"{ApiVersion}/album", baseUrl, HttpMethod.Get); + request.AddQueryString("albumId", albumId.ToString()); + AddHeaders(request, apiKey); + return Api.Request>(request); + } + + public Task AddArtist(ArtistAdd artist, string apiKey, string baseUrl) + { + var request = new Request($"{ApiVersion}/artist", baseUrl, HttpMethod.Post); + request.AddJsonBody(artist); + AddHeaders(request, apiKey); + return Api.Request(request); + } + + public async Task MontiorAlbum(int albumId, string apiKey, string baseUrl) + { + var request = new Request($"{ApiVersion}/album/monitor", baseUrl, HttpMethod.Put); + request.AddJsonBody(new + { + albumIds = new[] { albumId }, + monitored = true + }); + AddHeaders(request, apiKey); + return (await Api.Request>(request)).FirstOrDefault(); + } + + public Task> GetAllAlbumsByArtistId(int artistId, string apiKey, string baseUrl) + { + var request = new Request($"{ApiVersion}/album", baseUrl, HttpMethod.Get); + request.AddQueryString("artistId", artistId.ToString()); + AddHeaders(request, apiKey); + return Api.Request>(request); + } + + public Task> GetMetadataProfile(string apiKey, string baseUrl) + { + var request = new Request($"{ApiVersion}/metadataprofile", baseUrl, HttpMethod.Get); + AddHeaders(request, apiKey); + return Api.Request>(request); + } + + public Task Status(string apiKey, string baseUrl) + { + var request = new Request($"{ApiVersion}/system/status", baseUrl, HttpMethod.Get); + AddHeaders(request, apiKey); + return Api.Request(request); + } + + public Task AlbumSearch(int[] albumIds, string apiKey, string baseUrl) + { + var request = new Request($"{ApiVersion}/command/", baseUrl, HttpMethod.Post); + request.AddJsonBody(new { name = "AlbumSearch", albumIds }); + AddHeaders(request, apiKey); + return Api.Request(request); + } + + private void AddHeaders(Request request, string key) + { + request.AddHeader("X-Api-Key", key); + } + } +} diff --git a/src/Ombi.Api.Lidarr/Models/AlbumByArtistResponse.cs b/src/Ombi.Api.Lidarr/Models/AlbumByArtistResponse.cs new file mode 100644 index 000000000..62f19651f --- /dev/null +++ b/src/Ombi.Api.Lidarr/Models/AlbumByArtistResponse.cs @@ -0,0 +1,34 @@ +namespace Ombi.Api.Lidarr.Models +{ + public class AlbumByArtistResponse + { + public Album[] Albums { get; set; } + public string ArtistName { get; set; } + public string Disambiguation { get; set; } + public string Id { get; set; } + public Image[] Images { get; set; } + public Link[] Links { get; set; } + public string Overview { get; set; } + public Rating Rating { get; set; } + public string SortName { get; set; } + public string Status { get; set; } + public string Type { get; set; } + } + + public class Rating + { + public int Count { get; set; } + public decimal Value { get; set; } + } + + public class Album + { + public string Disambiguation { get; set; } + public string Id { get; set; } + public string ReleaseDate { get; set; } + public string[] ReleaseStatuses { get; set; } + public string[] SecondaryTypes { get; set; } + public string Title { get; set; } + public string Type { get; set; } + } +} \ No newline at end of file diff --git a/src/Ombi.Api.Lidarr/Models/AlbumByForeignId.cs b/src/Ombi.Api.Lidarr/Models/AlbumByForeignId.cs new file mode 100644 index 000000000..27a479d2f --- /dev/null +++ b/src/Ombi.Api.Lidarr/Models/AlbumByForeignId.cs @@ -0,0 +1,31 @@ +using System; +using System.Net.Mime; + +namespace Ombi.Api.Lidarr.Models +{ + public class AlbumByForeignId + { + public string title { get; set; } + public string disambiguation { get; set; } + public string overview { get; set; } + public int artistId { get; set; } + public string foreignAlbumId { get; set; } + public bool monitored { get; set; } + public bool anyReleaseOk { get; set; } + public int profileId { get; set; } + public int duration { get; set; } + public string albumType { get; set; } + public object[] secondaryTypes { get; set; } + public int mediumCount { get; set; } + public Ratings ratings { get; set; } + public DateTime releaseDate { get; set; } + public Release[] releases { get; set; } + public object[] genres { get; set; } + public Medium[] media { get; set; } + public Artist artist { get; set; } + public Image[] images { get; set; } + public Link[] links { get; set; } + public Statistics statistics { get; set; } + public int id { get; set; } + } +} \ No newline at end of file diff --git a/src/Ombi.Api.Lidarr/Models/AlbumLookup.cs b/src/Ombi.Api.Lidarr/Models/AlbumLookup.cs new file mode 100644 index 000000000..1e909cf4c --- /dev/null +++ b/src/Ombi.Api.Lidarr/Models/AlbumLookup.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; + +namespace Ombi.Api.Lidarr.Models +{ + public class AlbumLookup + { + public string title { get; set; } + public string status { get; set; } + public string artistType { get; set; } + public string disambiguation { get; set; } + public List links { get; set; } + public int artistId { get; set; } + public string foreignAlbumId { get; set; } + public bool monitored { get; set; } + public int profileId { get; set; } + public int duration { get; set; } + public string albumType { get; set; } + public string[] secondaryTypes { get; set; } + public int mediumCount { get; set; } + public Ratings ratings { get; set; } + public DateTime releaseDate { get; set; } + //public object[] releases { get; set; } + public object[] genres { get; set; } + //public object[] media { get; set; } + public Artist artist { get; set; } + public Image[] images { get; set; } + public string remoteCover { get; set; } + } +} \ No newline at end of file diff --git a/src/Ombi.Api.Lidarr/Models/AlbumResponse.cs b/src/Ombi.Api.Lidarr/Models/AlbumResponse.cs new file mode 100644 index 000000000..f9d35c43b --- /dev/null +++ b/src/Ombi.Api.Lidarr/Models/AlbumResponse.cs @@ -0,0 +1,27 @@ +using System; + +namespace Ombi.Api.Lidarr.Models +{ + public class AlbumResponse + { + public string title { get; set; } + public string disambiguation { get; set; } + public int artistId { get; set; } + public string foreignAlbumId { get; set; } + public bool monitored { get; set; } + public int profileId { get; set; } + public int duration { get; set; } + public string albumType { get; set; } + public object[] secondaryTypes { get; set; } + public int mediumCount { get; set; } + public Ratings ratings { get; set; } + public DateTime releaseDate { get; set; } + public Currentrelease currentRelease { get; set; } + public Release[] releases { get; set; } + public object[] genres { get; set; } + public Medium[] media { get; set; } + public Image[] images { get; set; } + public Statistics statistics { get; set; } + public int id { get; set; } + } +} \ No newline at end of file diff --git a/src/Ombi.Api.Lidarr/Models/Artist.cs b/src/Ombi.Api.Lidarr/Models/Artist.cs new file mode 100644 index 000000000..bc6afc20e --- /dev/null +++ b/src/Ombi.Api.Lidarr/Models/Artist.cs @@ -0,0 +1,25 @@ +using System; + +namespace Ombi.Api.Lidarr.Models +{ + public class Artist + { + public string status { get; set; } + public bool ended { get; set; } + public string artistName { get; set; } + public string foreignArtistId { get; set; } + public int tadbId { get; set; } + public int discogsId { get; set; } + public object[] links { get; set; } + public object[] images { get; set; } + public int qualityProfileId { get; set; } + public int languageProfileId { get; set; } + public int metadataProfileId { get; set; } + public bool albumFolder { get; set; } + public bool monitored { get; set; } + public object[] genres { get; set; } + public object[] tags { get; set; } + public DateTime added { get; set; } + public Statistics statistics { get; set; } + } +} \ No newline at end of file diff --git a/src/Ombi.Api.Lidarr/Models/ArtistAdd.cs b/src/Ombi.Api.Lidarr/Models/ArtistAdd.cs new file mode 100644 index 000000000..e292e8905 --- /dev/null +++ b/src/Ombi.Api.Lidarr/Models/ArtistAdd.cs @@ -0,0 +1,48 @@ +using System; +using System.Net.Mime; + +namespace Ombi.Api.Lidarr.Models +{ + public class ArtistAdd + { + public string status { get; set; } + public bool ended { get; set; } + public string artistName { get; set; } + public string foreignArtistId { get; set; } + public int tadbId { get; set; } + public int discogsId { get; set; } + public string overview { get; set; } + public string disambiguation { get; set; } + public Link[] links { get; set; } + public Image[] images { get; set; } + public string remotePoster { get; set; } + public int qualityProfileId { get; set; } + public int metadataProfileId { get; set; } + public bool albumFolder { get; set; } + public bool monitored { get; set; } + public string cleanName { get; set; } + public string sortName { get; set; } + public object[] tags { get; set; } + public DateTime added { get; set; } + public Ratings ratings { get; set; } + public Statistics statistics { get; set; } + public Addoptions addOptions { get; set; } + public string rootFolderPath { get; set; } + } + + public class Addoptions + { + /// + /// Future = 1 + /// Missing = 2 + /// Existing = 3 + /// First = 5 + /// Latest = 4 + /// None = 6 + /// + public int selectedOption { get; set; } + public bool monitored { get; set; } + public bool searchForMissingAlbums { get; set; } + public string[] AlbumsToMonitor { get; set; } // Uses the MusicBrainzAlbumId! + } +} \ No newline at end of file diff --git a/src/Ombi.Api.Lidarr/Models/ArtistLookup.cs b/src/Ombi.Api.Lidarr/Models/ArtistLookup.cs new file mode 100644 index 000000000..aa454c0a0 --- /dev/null +++ b/src/Ombi.Api.Lidarr/Models/ArtistLookup.cs @@ -0,0 +1,32 @@ +using System; +using System.Net.Mime; + +namespace Ombi.Api.Lidarr.Models +{ + public class ArtistLookup + { + public string status { get; set; } + public bool ended { get; set; } + public string artistName { get; set; } + public string foreignArtistId { get; set; } + public int tadbId { get; set; } + public int discogsId { get; set; } + public string overview { get; set; } + public string artistType { get; set; } + public string disambiguation { get; set; } + public Link[] links { get; set; } + public Image[] images { get; set; } + public string remotePoster { get; set; } + public int qualityProfileId { get; set; } + public int languageProfileId { get; set; } + public int metadataProfileId { get; set; } + public bool albumFolder { get; set; } + public bool monitored { get; set; } + public string cleanName { get; set; } + public string sortName { get; set; } + public object[] tags { get; set; } + public DateTime added { get; set; } + public Ratings ratings { get; set; } + public Statistics statistics { get; set; } + } +} \ No newline at end of file diff --git a/src/Ombi.Api.Lidarr/Models/ArtistResult.cs b/src/Ombi.Api.Lidarr/Models/ArtistResult.cs new file mode 100644 index 000000000..32b3aaab5 --- /dev/null +++ b/src/Ombi.Api.Lidarr/Models/ArtistResult.cs @@ -0,0 +1,93 @@ +using System; + +namespace Ombi.Api.Lidarr.Models +{ + + public class ArtistResult + { + public string status { get; set; } + public bool ended { get; set; } + public DateTime lastInfoSync { get; set; } + public string artistName { get; set; } + public string foreignArtistId { get; set; } + public int tadbId { get; set; } + public int discogsId { get; set; } + public string overview { get; set; } + public string artistType { get; set; } + public string disambiguation { get; set; } + public Link[] links { get; set; } + public Nextalbum nextAlbum { get; set; } + public Image[] images { get; set; } + public string path { get; set; } + public int qualityProfileId { get; set; } + public int languageProfileId { get; set; } + public int metadataProfileId { get; set; } + public bool albumFolder { get; set; } + public bool monitored { get; set; } + public object[] genres { get; set; } + public string cleanName { get; set; } + public string sortName { get; set; } + public object[] tags { get; set; } + public DateTime added { get; set; } + public Ratings ratings { get; set; } + public Statistics statistics { get; set; } + public int id { get; set; } + } + + public class Nextalbum + { + public string foreignAlbumId { get; set; } + public int artistId { get; set; } + public string title { get; set; } + public string disambiguation { get; set; } + public string cleanTitle { get; set; } + public DateTime releaseDate { get; set; } + public int profileId { get; set; } + public int duration { get; set; } + public bool monitored { get; set; } + public object[] images { get; set; } + public object[] genres { get; set; } + public Medium[] media { get; set; } + public DateTime lastInfoSync { get; set; } + public DateTime added { get; set; } + public string albumType { get; set; } + public object[] secondaryTypes { get; set; } + public Ratings ratings { get; set; } + public Release[] releases { get; set; } + public Currentrelease currentRelease { get; set; } + public int id { get; set; } + } + + public class Currentrelease + { + public string id { get; set; } + public string title { get; set; } + public DateTime releaseDate { get; set; } + public int trackCount { get; set; } + public int mediaCount { get; set; } + public string disambiguation { get; set; } + public string[] country { get; set; } + public string format { get; set; } + public string[] label { get; set; } + } + + public class Medium + { + public int number { get; set; } + public string name { get; set; } + public string format { get; set; } + } + + public class Release + { + public string id { get; set; } + public string title { get; set; } + public DateTime releaseDate { get; set; } + public int trackCount { get; set; } + public int mediaCount { get; set; } + public string disambiguation { get; set; } + public string[] country { get; set; } + public string format { get; set; } + public string[] label { get; set; } + } +} \ No newline at end of file diff --git a/src/Ombi.Api.Lidarr/Models/CommandResult.cs b/src/Ombi.Api.Lidarr/Models/CommandResult.cs new file mode 100644 index 000000000..5271de91f --- /dev/null +++ b/src/Ombi.Api.Lidarr/Models/CommandResult.cs @@ -0,0 +1,15 @@ +using System; + +namespace Ombi.Api.Lidarr.Models +{ + + public class CommandResult + { + public string name { get; set; } + public DateTime queued { get; set; } + public DateTime stateChangeTime { get; set; } + public bool sendUpdatesToClient { get; set; } + public string status { get; set; } + public int id { get; set; } + } +} diff --git a/src/Ombi.Api.Lidarr/Models/Image.cs b/src/Ombi.Api.Lidarr/Models/Image.cs new file mode 100644 index 000000000..172a13fe9 --- /dev/null +++ b/src/Ombi.Api.Lidarr/Models/Image.cs @@ -0,0 +1,8 @@ +namespace Ombi.Api.Lidarr.Models +{ + public class Image + { + public string coverType { get; set; } + public string url { get; set; } + } +} \ No newline at end of file diff --git a/src/Ombi.Api.Lidarr/Models/LidarrLinks.cs b/src/Ombi.Api.Lidarr/Models/LidarrLinks.cs new file mode 100644 index 000000000..e8bdb8975 --- /dev/null +++ b/src/Ombi.Api.Lidarr/Models/LidarrLinks.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Ombi.Api.Lidarr.Models +{ + public class LidarrLinks + { + public string url { get; set; } + public string name { get; set; } + } +} diff --git a/src/Ombi.Api.Lidarr/Models/LidarrProfile.cs b/src/Ombi.Api.Lidarr/Models/LidarrProfile.cs new file mode 100644 index 000000000..19ebda5a6 --- /dev/null +++ b/src/Ombi.Api.Lidarr/Models/LidarrProfile.cs @@ -0,0 +1,23 @@ +using System.Collections.Generic; + +namespace Ombi.Api.Lidarr.Models +{ + public class Quality + { + public int id { get; set; } + public string name { get; set; } + } + + public class Item + { + public Quality quality { get; set; } + public bool allowed { get; set; } + } + + public class LidarrProfile +{ + public string name { get; set; } + public List items { get; set; } + public int id { get; set; } + } +} \ No newline at end of file diff --git a/src/Ombi.Api.Lidarr/Models/LidarrRatings.cs b/src/Ombi.Api.Lidarr/Models/LidarrRatings.cs new file mode 100644 index 000000000..7728d58bf --- /dev/null +++ b/src/Ombi.Api.Lidarr/Models/LidarrRatings.cs @@ -0,0 +1,8 @@ +namespace Ombi.Api.Lidarr.Models +{ + public class LidarrRatings + { + public int votes { get; set; } + public decimal value { get; set; } + } +} diff --git a/src/Ombi.Api.Lidarr/Models/LidarrRootFolder.cs b/src/Ombi.Api.Lidarr/Models/LidarrRootFolder.cs new file mode 100644 index 000000000..a3a252f04 --- /dev/null +++ b/src/Ombi.Api.Lidarr/Models/LidarrRootFolder.cs @@ -0,0 +1,11 @@ +namespace Ombi.Api.Lidarr.Models +{ + public class LidarrRootFolder + { + public string path { get; set; } + public long freeSpace { get; set; } + public object[] unmappedFolders { get; set; } + public int id { get; set; } + } + +} \ No newline at end of file diff --git a/src/Ombi.Api.Lidarr/Models/LidarrStatus.cs b/src/Ombi.Api.Lidarr/Models/LidarrStatus.cs new file mode 100644 index 000000000..27f6c1820 --- /dev/null +++ b/src/Ombi.Api.Lidarr/Models/LidarrStatus.cs @@ -0,0 +1,31 @@ +using System; + +namespace Ombi.Api.Lidarr.Models +{ + public class LidarrStatus + { + public string version { get; set; } + public DateTime buildTime { get; set; } + public bool isDebug { get; set; } + public bool isProduction { get; set; } + public bool isAdmin { get; set; } + public bool isUserInteractive { get; set; } + public string startupPath { get; set; } + public string appData { get; set; } + public string osName { get; set; } + public string osVersion { get; set; } + public bool isMonoRuntime { get; set; } + public bool isMono { get; set; } + public bool isLinux { get; set; } + public bool isOsx { get; set; } + public bool isWindows { get; set; } + public string mode { get; set; } + public string branch { get; set; } + public string authentication { get; set; } + public string sqliteVersion { get; set; } + public int migrationVersion { get; set; } + public string urlBase { get; set; } + public string runtimeVersion { get; set; } + public string runtimeName { get; set; } + } +} \ No newline at end of file diff --git a/src/Ombi.Api.Lidarr/Models/LidarrTrack.cs b/src/Ombi.Api.Lidarr/Models/LidarrTrack.cs new file mode 100644 index 000000000..367e8cc7c --- /dev/null +++ b/src/Ombi.Api.Lidarr/Models/LidarrTrack.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Ombi.Api.Lidarr.Models +{ + public class LidarrTrack + { + public int artistId { get; set; } + public int trackFileId { get; set; } + public int albumId { get; set; } + public bool _explicit { get; set; } + public int absoluteTrackNumber { get; set; } + public string trackNumber { get; set; } + public string title { get; set; } + public int duration { get; set; } + public int mediumNumber { get; set; } + public bool hasFile { get; set; } + public bool monitored { get; set; } + public int id { get; set; } + } +} diff --git a/src/Ombi.Api.Lidarr/Models/Link.cs b/src/Ombi.Api.Lidarr/Models/Link.cs new file mode 100644 index 000000000..492ac0426 --- /dev/null +++ b/src/Ombi.Api.Lidarr/Models/Link.cs @@ -0,0 +1,8 @@ +namespace Ombi.Api.Lidarr.Models +{ + public class Link + { + public string url { get; set; } + public string name { get; set; } + } +} \ No newline at end of file diff --git a/src/Ombi.Api.Lidarr/Models/MetadataProfile.cs b/src/Ombi.Api.Lidarr/Models/MetadataProfile.cs new file mode 100644 index 000000000..bda3333f1 --- /dev/null +++ b/src/Ombi.Api.Lidarr/Models/MetadataProfile.cs @@ -0,0 +1,8 @@ +namespace Ombi.Api.Lidarr.Models +{ + public class MetadataProfile + { + public string name { get; set; } + public int id { get; set; } + } +} \ No newline at end of file diff --git a/src/Ombi.Api.Lidarr/Models/Ratings.cs b/src/Ombi.Api.Lidarr/Models/Ratings.cs new file mode 100644 index 000000000..f2aac4203 --- /dev/null +++ b/src/Ombi.Api.Lidarr/Models/Ratings.cs @@ -0,0 +1,8 @@ +namespace Ombi.Api.Lidarr.Models +{ + public class Ratings + { + public int votes { get; set; } + public decimal value { get; set; } + } +} \ No newline at end of file diff --git a/src/Ombi.Api.Lidarr/Models/Statistics.cs b/src/Ombi.Api.Lidarr/Models/Statistics.cs new file mode 100644 index 000000000..77c6b5217 --- /dev/null +++ b/src/Ombi.Api.Lidarr/Models/Statistics.cs @@ -0,0 +1,12 @@ +namespace Ombi.Api.Lidarr.Models +{ + public class Statistics + { + public int albumCount { get; set; } + public int trackFileCount { get; set; } + public int trackCount { get; set; } + public int totalTrackCount { get; set; } + public long sizeOnDisk { get; set; } + public decimal percentOfEpisodes { get; set; } + } +} \ No newline at end of file diff --git a/src/Ombi.Api.Lidarr/Ombi.Api.Lidarr.csproj b/src/Ombi.Api.Lidarr/Ombi.Api.Lidarr.csproj new file mode 100644 index 000000000..a3651df3c --- /dev/null +++ b/src/Ombi.Api.Lidarr/Ombi.Api.Lidarr.csproj @@ -0,0 +1,11 @@ + + + + netstandard2.0 + + + + + + + diff --git a/src/Ombi.Api.Mattermost/Ombi.Api.Mattermost.csproj b/src/Ombi.Api.Mattermost/Ombi.Api.Mattermost.csproj index 83318be7b..98292f463 100644 --- a/src/Ombi.Api.Mattermost/Ombi.Api.Mattermost.csproj +++ b/src/Ombi.Api.Mattermost/Ombi.Api.Mattermost.csproj @@ -9,7 +9,7 @@ - + diff --git a/src/Ombi.Api.Notifications/IOneSignalApi.cs b/src/Ombi.Api.Notifications/IOneSignalApi.cs index 2e3ef106e..6de64d11e 100644 --- a/src/Ombi.Api.Notifications/IOneSignalApi.cs +++ b/src/Ombi.Api.Notifications/IOneSignalApi.cs @@ -6,6 +6,6 @@ namespace Ombi.Api.Notifications { public interface IOneSignalApi { - Task PushNotification(List playerIds, string message); + Task PushNotification(List playerIds, string message, bool isAdminNotification, int requestId, int requestType); } } \ No newline at end of file diff --git a/src/Ombi.Api.Notifications/Models/OneSignalNotificationBody.cs b/src/Ombi.Api.Notifications/Models/OneSignalNotificationBody.cs index 6c024fa67..e65222bd5 100644 --- a/src/Ombi.Api.Notifications/Models/OneSignalNotificationBody.cs +++ b/src/Ombi.Api.Notifications/Models/OneSignalNotificationBody.cs @@ -4,18 +4,22 @@ { public string app_id { get; set; } public string[] include_player_ids { get; set; } - public Data data { get; set; } + public object data { get; set; } + public Button[] buttons { get; set; } public Contents contents { get; set; } } - public class Data - { - public string foo { get; set; } - } public class Contents { public string en { get; set; } } + public class Button + { + public string id { get; set; } + public string text { get; set; } + //public string icon { get; set; } + } + } \ No newline at end of file diff --git a/src/Ombi.Api.Notifications/Ombi.Api.Notifications.csproj b/src/Ombi.Api.Notifications/Ombi.Api.Notifications.csproj index a3651df3c..7b890e2dd 100644 --- a/src/Ombi.Api.Notifications/Ombi.Api.Notifications.csproj +++ b/src/Ombi.Api.Notifications/Ombi.Api.Notifications.csproj @@ -4,6 +4,10 @@ netstandard2.0 + + + + diff --git a/src/Ombi.Api.Notifications/OneSignalApi.cs b/src/Ombi.Api.Notifications/OneSignalApi.cs index d4760bb09..ee5c7e44a 100644 --- a/src/Ombi.Api.Notifications/OneSignalApi.cs +++ b/src/Ombi.Api.Notifications/OneSignalApi.cs @@ -20,13 +20,13 @@ namespace Ombi.Api.Notifications private readonly IApplicationConfigRepository _appConfig; private const string ApiUrl = "https://onesignal.com/api/v1/notifications"; - public async Task PushNotification(List playerIds, string message) + public async Task PushNotification(List playerIds, string message, bool isAdminNotification, int requestId, int requestType) { if (!playerIds.Any()) { return null; } - var id = await _appConfig.Get(ConfigurationTypes.Notification); + var id = await _appConfig.GetAsync(ConfigurationTypes.Notification); var request = new Request(string.Empty, ApiUrl, HttpMethod.Post); var body = new OneSignalNotificationBody @@ -39,6 +39,17 @@ namespace Ombi.Api.Notifications include_player_ids = playerIds.ToArray() }; + if (isAdminNotification) + { + // Add the action buttons + body.data = new { requestid = requestId, requestType = requestType}; + body.buttons = new[] + { + new Button {id = "approve", text = "Approve Request"}, + new Button {id = "deny", text = "Deny Request"}, + }; + } + request.AddJsonBody(body); var result = await _api.Request(request); diff --git a/src/Ombi.Api.Plex/IPlexApi.cs b/src/Ombi.Api.Plex/IPlexApi.cs index 2dd1a638f..c79ec50c9 100644 --- a/src/Ombi.Api.Plex/IPlexApi.cs +++ b/src/Ombi.Api.Plex/IPlexApi.cs @@ -11,6 +11,7 @@ namespace Ombi.Api.Plex public interface IPlexApi { Task GetStatus(string authToken, string uri); + Task GetLibrariesForMachineId(string authToken, string machineId); Task SignIn(UserRequest user); Task GetServer(string authToken); Task GetLibrarySections(string authToken, string plexFullHost); @@ -23,6 +24,7 @@ namespace Ombi.Api.Plex Task GetAccount(string authToken); Task GetRecentlyAdded(string authToken, string uri, string sectionId); Task GetPin(int pinId); - Task GetOAuthUrl(int pinId, string code, string applicationUrl, bool wizard); + Task GetOAuthUrl(string code, string applicationUrl); + Task AddUser(string emailAddress, string serverId, string authToken, int[] libs); } } \ No newline at end of file diff --git a/src/Ombi.Api.Plex/Models/PlexAdd.cs b/src/Ombi.Api.Plex/Models/PlexAdd.cs new file mode 100644 index 000000000..fb0a550d0 --- /dev/null +++ b/src/Ombi.Api.Plex/Models/PlexAdd.cs @@ -0,0 +1,84 @@ +using System.Collections.Generic; +using System.Xml.Serialization; + +namespace Ombi.Api.Plex.Models +{ + [XmlRoot(ElementName = "Section")] + public class Section + { + [XmlAttribute(AttributeName = "id")] + public string Id { get; set; } + [XmlAttribute(AttributeName = "key")] + public string Key { get; set; } + [XmlAttribute(AttributeName = "title")] + public string Title { get; set; } + [XmlAttribute(AttributeName = "type")] + public string Type { get; set; } + [XmlAttribute(AttributeName = "shared")] + public string Shared { get; set; } + } + + [XmlRoot(ElementName = "SharedServer")] + public class SharedServer + { + [XmlElement(ElementName = "Section")] + public List
Section { get; set; } + [XmlAttribute(AttributeName = "id")] + public string Id { get; set; } + [XmlAttribute(AttributeName = "username")] + public string Username { get; set; } + [XmlAttribute(AttributeName = "email")] + public string Email { get; set; } + [XmlAttribute(AttributeName = "userID")] + public string UserID { get; set; } + [XmlAttribute(AttributeName = "accessToken")] + public string AccessToken { get; set; } + [XmlAttribute(AttributeName = "name")] + public string Name { get; set; } + [XmlAttribute(AttributeName = "acceptedAt")] + public string AcceptedAt { get; set; } + [XmlAttribute(AttributeName = "invitedAt")] + public string InvitedAt { get; set; } + [XmlAttribute(AttributeName = "allowSync")] + public string AllowSync { get; set; } + [XmlAttribute(AttributeName = "allowCameraUpload")] + public string AllowCameraUpload { get; set; } + [XmlAttribute(AttributeName = "allowChannels")] + public string AllowChannels { get; set; } + [XmlAttribute(AttributeName = "allowTuners")] + public string AllowTuners { get; set; } + [XmlAttribute(AttributeName = "owned")] + public string Owned { get; set; } + } + + [XmlRoot(ElementName = "MediaContainer")] + public class PlexAdd + { + [XmlElement(ElementName = "SharedServer")] + public SharedServer SharedServer { get; set; } + [XmlAttribute(AttributeName = "friendlyName")] + public string FriendlyName { get; set; } + [XmlAttribute(AttributeName = "identifier")] + public string Identifier { get; set; } + [XmlAttribute(AttributeName = "machineIdentifier")] + public string MachineIdentifier { get; set; } + [XmlAttribute(AttributeName = "size")] + public string Size { get; set; } + } + + [XmlRoot(ElementName = "Response")] + public class AddUserError + { + [XmlAttribute(AttributeName = "code")] + public string Code { get; set; } + [XmlAttribute(AttributeName = "status")] + public string Status { get; set; } + } + + public class PlexAddWrapper + { + public PlexAdd Add { get; set; } + public AddUserError Error { get; set; } + public bool HasError => Error != null; + } +} \ No newline at end of file diff --git a/src/Ombi.Api.Plex/Models/PlexLibrariesForMachineId.cs b/src/Ombi.Api.Plex/Models/PlexLibrariesForMachineId.cs new file mode 100644 index 000000000..17ac59b81 --- /dev/null +++ b/src/Ombi.Api.Plex/Models/PlexLibrariesForMachineId.cs @@ -0,0 +1,66 @@ +namespace Ombi.Api.Plex.Models +{ + + using System; + using System.Xml.Serialization; + using System.Collections.Generic; + + [XmlRoot(ElementName = "Section")] + public class SectionLite + { + [XmlAttribute(AttributeName = "id")] + public string Id { get; set; } + [XmlAttribute(AttributeName = "key")] + public string Key { get; set; } + [XmlAttribute(AttributeName = "type")] + public string Type { get; set; } + [XmlAttribute(AttributeName = "title")] + public string Title { get; set; } + } + + [XmlRoot(ElementName = "Server")] + public class ServerLib + { + [XmlElement(ElementName = "Section")] + public List Section { get; set; } + [XmlAttribute(AttributeName = "name")] + public string Name { get; set; } + [XmlAttribute(AttributeName = "address")] + public string Address { get; set; } + [XmlAttribute(AttributeName = "port")] + public string Port { get; set; } + [XmlAttribute(AttributeName = "version")] + public string Version { get; set; } + [XmlAttribute(AttributeName = "scheme")] + public string Scheme { get; set; } + [XmlAttribute(AttributeName = "host")] + public string Host { get; set; } + [XmlAttribute(AttributeName = "localAddresses")] + public string LocalAddresses { get; set; } + [XmlAttribute(AttributeName = "machineIdentifier")] + public string MachineIdentifier { get; set; } + [XmlAttribute(AttributeName = "createdAt")] + public string CreatedAt { get; set; } + [XmlAttribute(AttributeName = "updatedAt")] + public string UpdatedAt { get; set; } + [XmlAttribute(AttributeName = "owned")] + public string Owned { get; set; } + [XmlAttribute(AttributeName = "synced")] + public string Synced { get; set; } + } + + [XmlRoot(ElementName = "MediaContainer")] + public class PlexLibrariesForMachineId + { + [XmlElement(ElementName = "Server")] + public ServerLib Server { get; set; } + [XmlAttribute(AttributeName = "friendlyName")] + public string FriendlyName { get; set; } + [XmlAttribute(AttributeName = "identifier")] + public string Identifier { get; set; } + [XmlAttribute(AttributeName = "machineIdentifier")] + public string MachineIdentifier { get; set; } + [XmlAttribute(AttributeName = "size")] + public string Size { get; set; } + } +} \ No newline at end of file diff --git a/src/Ombi.Api.Plex/PlexApi.cs b/src/Ombi.Api.Plex/PlexApi.cs index 4276f6203..eaafcd75f 100644 --- a/src/Ombi.Api.Plex/PlexApi.cs +++ b/src/Ombi.Api.Plex/PlexApi.cs @@ -41,7 +41,18 @@ namespace Ombi.Api.Plex } else { - _app = settings.ApplicationName; + // Check for non-ascii characters (New .Net Core HTTPLib does not allow this) + var chars = settings.ApplicationName.ToCharArray(); + var hasNonAscii = false; + foreach (var c in chars) + { + if (c > 128) + { + hasNonAscii = true; + } + } + + _app = hasNonAscii ? "Ombi" : settings.ApplicationName; } return _app; @@ -116,6 +127,13 @@ namespace Ombi.Api.Plex return await Api.Request(request); } + public async Task GetLibrariesForMachineId(string authToken, string machineId) + { + var request = new Request("", $"https://plex.tv/api/servers/{machineId}", HttpMethod.Get, ContentType.Xml); + await AddHeaders(request, authToken); + return await Api.Request(request); + } + /// // 192.168.1.69:32400/library/metadata/3662/allLeaves // The metadata ratingkey should be in the Cache @@ -199,22 +217,17 @@ namespace Ombi.Api.Plex return await Api.Request(request); } - public async Task GetOAuthUrl(int pinId, string code, string applicationUrl, bool wizard) + public async Task GetOAuthUrl(string code, string applicationUrl) { var request = new Request("auth#", "https://app.plex.tv", HttpMethod.Get); await AddHeaders(request); - var forwardUrl = wizard - ? new Request($"Wizard/OAuth/{pinId}", applicationUrl, HttpMethod.Get) - : new Request($"Login/OAuth/{pinId}", applicationUrl, HttpMethod.Get); - - request.AddQueryString("forwardUrl", forwardUrl.FullUri.ToString()); - request.AddQueryString("pinID", pinId.ToString()); + request.AddQueryString("code", code); request.AddQueryString("context[device][product]", ApplicationName); request.AddQueryString("context[device][environment]", "bundled"); request.AddQueryString("context[device][layout]", "desktop"); request.AddQueryString("context[device][platform]", "Web"); - request.AddQueryString("context[device][device]", "Ombi (Web)"); + request.AddQueryString("context[device][device]", "Ombi"); var s = await GetSettings(); await CheckInstallId(s); @@ -232,6 +245,34 @@ namespace Ombi.Api.Plex return request.FullUri; } + public async Task AddUser(string emailAddress, string serverId, string authToken, int[] libs) + { + var request = new Request(string.Empty, $"https://plex.tv/api/servers/{serverId}/shared_servers", HttpMethod.Post, ContentType.Xml); + await AddHeaders(request, authToken); + request.AddJsonBody(new + { + server_id = serverId, + shared_server = new + { + library_section_ids = libs.Length > 0 ? libs : new int[]{}, + invited_email = emailAddress + }, + sharing_settings = new { } + }); + var result = await Api.RequestContent(request); + try + { + var add = Api.DeserializeXml(result); + return new PlexAddWrapper{Add = add}; + } + catch (InvalidOperationException) + { + var error = Api.DeserializeXml(result); + return new PlexAddWrapper{Error = error}; + } + } + + /// /// Adds the required headers and also the authorization header /// @@ -254,7 +295,7 @@ namespace Ombi.Api.Plex request.AddHeader("X-Plex-Client-Identifier", s.InstallId.ToString("N")); request.AddHeader("X-Plex-Product", ApplicationName); request.AddHeader("X-Plex-Version", "3"); - request.AddHeader("X-Plex-Device", "Ombi (Web)"); + request.AddHeader("X-Plex-Device", "Ombi"); request.AddHeader("X-Plex-Platform", "Web"); request.AddContentHeader("Content-Type", request.ContentType == ContentType.Json ? "application/json" : "application/xml"); request.AddHeader("Accept", "application/json"); diff --git a/src/Ombi.Api.Pushover/IPushoverApi.cs b/src/Ombi.Api.Pushover/IPushoverApi.cs index 42e8e9060..554a15b60 100644 --- a/src/Ombi.Api.Pushover/IPushoverApi.cs +++ b/src/Ombi.Api.Pushover/IPushoverApi.cs @@ -5,6 +5,6 @@ namespace Ombi.Api.Pushover { public interface IPushoverApi { - Task PushAsync(string accessToken, string message, string userToken); + Task PushAsync(string accessToken, string message, string userToken, sbyte priority, string sound); } } \ No newline at end of file diff --git a/src/Ombi.Api.Pushover/PushoverApi.cs b/src/Ombi.Api.Pushover/PushoverApi.cs index fd6ca23ea..41df0f647 100644 --- a/src/Ombi.Api.Pushover/PushoverApi.cs +++ b/src/Ombi.Api.Pushover/PushoverApi.cs @@ -2,6 +2,7 @@ using System.Net; using System.Net.Http; using System.Threading.Tasks; +using System.Web; using Ombi.Api.Pushover.Models; namespace Ombi.Api.Pushover @@ -16,13 +17,9 @@ namespace Ombi.Api.Pushover private readonly IApi _api; private const string PushoverEndpoint = "https://api.pushover.net/1"; - public async Task PushAsync(string accessToken, string message, string userToken) + public async Task PushAsync(string accessToken, string message, string userToken, sbyte priority, string sound) { - if (message.Contains("'")) - { - message = message.Replace("'", "'"); - } - var request = new Request($"messages.json?token={accessToken}&user={userToken}&message={WebUtility.HtmlEncode(message)}", PushoverEndpoint, HttpMethod.Post); + var request = new Request($"messages.json?token={accessToken}&user={userToken}&priority={priority}&sound={sound}&message={WebUtility.UrlEncode(message)}", PushoverEndpoint, HttpMethod.Post); var result = await _api.Request(request); return result; diff --git a/src/Ombi.Api.Radarr/Ombi.Api.Radarr.csproj b/src/Ombi.Api.Radarr/Ombi.Api.Radarr.csproj index 0c615f301..9f25a7946 100644 --- a/src/Ombi.Api.Radarr/Ombi.Api.Radarr.csproj +++ b/src/Ombi.Api.Radarr/Ombi.Api.Radarr.csproj @@ -9,7 +9,7 @@ - + diff --git a/src/Ombi.Api.Radarr/RadarrApi.cs b/src/Ombi.Api.Radarr/RadarrApi.cs index 1f897b60b..fd4deb140 100644 --- a/src/Ombi.Api.Radarr/RadarrApi.cs +++ b/src/Ombi.Api.Radarr/RadarrApi.cs @@ -79,7 +79,7 @@ namespace Ombi.Api.Radarr tmdbId = tmdbId, qualityProfileId = qualityId, rootFolderPath = rootPath, - titleSlug = title, + titleSlug = title + year, monitored = true, year = year, minimumAvailability = minimumAvailability diff --git a/src/Ombi.Api.Service/Ombi.Api.Service.csproj b/src/Ombi.Api.Service/Ombi.Api.Service.csproj index 8cbddd874..bd53c9808 100644 --- a/src/Ombi.Api.Service/Ombi.Api.Service.csproj +++ b/src/Ombi.Api.Service/Ombi.Api.Service.csproj @@ -11,7 +11,7 @@ - + diff --git a/src/Ombi.Api.Sonarr/ISonarrV3Api.cs b/src/Ombi.Api.Sonarr/ISonarrV3Api.cs new file mode 100644 index 000000000..1d3ea3468 --- /dev/null +++ b/src/Ombi.Api.Sonarr/ISonarrV3Api.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using Ombi.Api.Sonarr.Models; +using System.Net.Http; +using Ombi.Api.Sonarr.Models.V3; + +namespace Ombi.Api.Sonarr +{ + public interface ISonarrV3Api : ISonarrApi + { + Task> LanguageProfiles(string apiKey, string baseUrl); + } +} \ No newline at end of file diff --git a/src/Ombi.Api.Sonarr/Models/Episode.cs b/src/Ombi.Api.Sonarr/Models/Episode.cs index c17f5486c..b01e6fd8c 100644 --- a/src/Ombi.Api.Sonarr/Models/Episode.cs +++ b/src/Ombi.Api.Sonarr/Models/Episode.cs @@ -6,6 +6,30 @@ namespace Ombi.Api.Sonarr.Models { public class Episode { + public Episode() + { + + } + + public Episode(Episode ep) + { + seriesId = ep.seriesId; + episodeFileId = ep.episodeFileId; + seasonNumber = ep.seasonNumber; + episodeNumber = ep.episodeNumber; + title = ep.title; + airDate = ep.airDate; + airDateUtc = ep.airDateUtc; + overview = ep.overview; + hasFile = ep.hasFile; + monitored = ep.monitored; + unverifiedSceneNumbering = ep.unverifiedSceneNumbering; + id = ep.id; + absoluteEpisodeNumber = ep.absoluteEpisodeNumber; + sceneAbsoluteEpisodeNumber = ep.sceneAbsoluteEpisodeNumber; + sceneEpisodeNumber = ep.sceneEpisodeNumber; + sceneSeasonNumber = ep.sceneSeasonNumber; + } public int seriesId { get; set; } public int episodeFileId { get; set; } public int seasonNumber { get; set; } @@ -27,6 +51,24 @@ namespace Ombi.Api.Sonarr.Models public class Episodefile { + public Episodefile() + { + + } + + public Episodefile(Episodefile e) + { + seriesId = e.seriesId; + seasonNumber = e.seasonNumber; + relativePath = e.relativePath; + path = e.path; + size = e.size; + dateAdded = e.dateAdded; + sceneName = e.sceneName; + quality = new EpisodeQuality(e.quality); + qualityCutoffNotMet = e.qualityCutoffNotMet; + id = e.id; + } public int seriesId { get; set; } public int seasonNumber { get; set; } public string relativePath { get; set; } @@ -41,12 +83,32 @@ namespace Ombi.Api.Sonarr.Models public class EpisodeQuality { + public EpisodeQuality() + { + + } + + public EpisodeQuality(EpisodeQuality e) + { + quality = new Quality(e.quality); + revision = new Revision(e.revision); + } public Quality quality { get; set; } public Revision revision { get; set; } } public class Revision { + public Revision() + { + + } + + public Revision(Revision r) + { + version = r.version; + real = r.real; + } public int version { get; set; } public int real { get; set; } } diff --git a/src/Ombi.Api.Sonarr/Models/NewSeries.cs b/src/Ombi.Api.Sonarr/Models/NewSeries.cs index 4d2c17308..d6f721b0b 100644 --- a/src/Ombi.Api.Sonarr/Models/NewSeries.cs +++ b/src/Ombi.Api.Sonarr/Models/NewSeries.cs @@ -23,9 +23,13 @@ namespace Ombi.Api.Sonarr.Models public string cleanTitle { get; set; } public string imdbId { get; set; } public string titleSlug { get; set; } + public string seriesType { get; set; } public int id { get; set; } public List images { get; set; } + // V3 Property + public int languageProfileId { get; set; } + /// /// This is for us /// diff --git a/src/Ombi.Api.Sonarr/Models/Quality.cs b/src/Ombi.Api.Sonarr/Models/Quality.cs index 76a1c92d8..9989a9c3e 100644 --- a/src/Ombi.Api.Sonarr/Models/Quality.cs +++ b/src/Ombi.Api.Sonarr/Models/Quality.cs @@ -2,6 +2,16 @@ namespace Ombi.Api.Sonarr.Models { public class Quality { + public Quality() + { + + } + + public Quality(Quality q) + { + id = q.id; + name = q.name; + } public int id { get; set; } public string name { get; set; } } diff --git a/src/Ombi.Api.Sonarr/Models/SonarrSeries.cs b/src/Ombi.Api.Sonarr/Models/SonarrSeries.cs index 568592734..3ade006d5 100644 --- a/src/Ombi.Api.Sonarr/Models/SonarrSeries.cs +++ b/src/Ombi.Api.Sonarr/Models/SonarrSeries.cs @@ -44,6 +44,7 @@ namespace Ombi.Api.Sonarr.Models public DateTime added { get; set; } public Ratings ratings { get; set; } public int qualityProfileId { get; set; } + public int languageProfileId { get; set; } public int id { get; set; } public DateTime nextAiring { get; set; } } diff --git a/src/Ombi.Api.Sonarr/Models/V3/LanguageProfiles.cs b/src/Ombi.Api.Sonarr/Models/V3/LanguageProfiles.cs new file mode 100644 index 000000000..aa3b199bd --- /dev/null +++ b/src/Ombi.Api.Sonarr/Models/V3/LanguageProfiles.cs @@ -0,0 +1,30 @@ +namespace Ombi.Api.Sonarr.Models.V3 +{ + public class LanguageProfiles + { + public string name { get; set; } + public bool upgradeAllowed { get; set; } + public Cutoff cutoff { get; set; } + public Languages[] languages { get; set; } + public int id { get; set; } + } + + public class Cutoff + { + public int id { get; set; } + public string name { get; set; } + } + + public class Languages + { + public Language languages { get; set; } + public bool allowed { get; set; } + } + + public class Language + { + public int id { get; set; } + public string name { get; set; } + } + +} \ No newline at end of file diff --git a/src/Ombi.Api.Sonarr/SonarrApi.cs b/src/Ombi.Api.Sonarr/SonarrApi.cs index 7fd74d2a3..0b0df4c15 100644 --- a/src/Ombi.Api.Sonarr/SonarrApi.cs +++ b/src/Ombi.Api.Sonarr/SonarrApi.cs @@ -16,18 +16,19 @@ namespace Ombi.Api.Sonarr Api = api; } - private IApi Api { get; } + protected IApi Api { get; } + protected virtual string ApiBaseUrl => "/api/"; public async Task> GetProfiles(string apiKey, string baseUrl) { - var request = new Request("/api/profile", baseUrl, HttpMethod.Get); + var request = new Request($"{ApiBaseUrl}profile", baseUrl, HttpMethod.Get); request.AddHeader("X-Api-Key", apiKey); return await Api.Request>(request); } public async Task> GetRootFolders(string apiKey, string baseUrl) { - var request = new Request("/api/rootfolder", baseUrl, HttpMethod.Get); + var request = new Request($"{ApiBaseUrl}rootfolder", baseUrl, HttpMethod.Get); request.AddHeader("X-Api-Key", apiKey); return await Api.Request>(request); } @@ -40,7 +41,7 @@ namespace Ombi.Api.Sonarr /// public async Task> GetSeries(string apiKey, string baseUrl) { - var request = new Request("/api/series", baseUrl, HttpMethod.Get); + var request = new Request($"{ApiBaseUrl}series", baseUrl, HttpMethod.Get); request.AddHeader("X-Api-Key", apiKey); var results = await Api.Request>(request); @@ -63,7 +64,7 @@ namespace Ombi.Api.Sonarr /// public async Task GetSeriesById(int id, string apiKey, string baseUrl) { - var request = new Request($"/api/series/{id}", baseUrl, HttpMethod.Get); + var request = new Request($"{ApiBaseUrl}series/{id}", baseUrl, HttpMethod.Get); request.AddHeader("X-Api-Key", apiKey); var result = await Api.Request(request); if (result?.seasons?.Length > 0) @@ -82,7 +83,7 @@ namespace Ombi.Api.Sonarr /// public async Task UpdateSeries(SonarrSeries updated, string apiKey, string baseUrl) { - var request = new Request("/api/series/", baseUrl, HttpMethod.Put); + var request = new Request($"{ApiBaseUrl}series/", baseUrl, HttpMethod.Put); request.AddHeader("X-Api-Key", apiKey); request.AddJsonBody(updated); return await Api.Request(request); @@ -94,7 +95,7 @@ namespace Ombi.Api.Sonarr { return new NewSeries { ErrorMessages = new List { seriesToAdd.Validate() } }; } - var request = new Request("/api/series/", baseUrl, HttpMethod.Post); + var request = new Request($"{ApiBaseUrl}series/", baseUrl, HttpMethod.Post); request.AddHeader("X-Api-Key", apiKey); request.AddJsonBody(seriesToAdd); @@ -120,7 +121,7 @@ namespace Ombi.Api.Sonarr /// public async Task> GetEpisodes(int seriesId, string apiKey, string baseUrl) { - var request = new Request($"/api/Episode?seriesId={seriesId}", baseUrl, HttpMethod.Get); + var request = new Request($"{ApiBaseUrl}Episode?seriesId={seriesId}", baseUrl, HttpMethod.Get); request.AddHeader("X-Api-Key", apiKey); return await Api.Request>(request); } @@ -134,14 +135,14 @@ namespace Ombi.Api.Sonarr /// public async Task GetEpisodeById(int episodeId, string apiKey, string baseUrl) { - var request = new Request($"/api/Episode/{episodeId}", baseUrl, HttpMethod.Get); + var request = new Request($"{ApiBaseUrl}Episode/{episodeId}", baseUrl, HttpMethod.Get); request.AddHeader("X-Api-Key", apiKey); return await Api.Request(request); } public async Task UpdateEpisode(Episode episodeToUpdate, string apiKey, string baseUrl) { - var request = new Request($"/api/Episode/", baseUrl, HttpMethod.Put); + var request = new Request($"{ApiBaseUrl}Episode/", baseUrl, HttpMethod.Put); request.AddHeader("X-Api-Key", apiKey); request.AddJsonBody(episodeToUpdate); return await Api.Request(request); @@ -189,7 +190,7 @@ namespace Ombi.Api.Sonarr private async Task Command(string apiKey, string baseUrl, object body) { - var request = new Request($"/api/Command/", baseUrl, HttpMethod.Post); + var request = new Request($"{ApiBaseUrl}Command/", baseUrl, HttpMethod.Post); request.AddHeader("X-Api-Key", apiKey); request.AddJsonBody(body); return await Api.Request(request); @@ -197,7 +198,7 @@ namespace Ombi.Api.Sonarr public async Task SystemStatus(string apiKey, string baseUrl) { - var request = new Request("/api/system/status", baseUrl, HttpMethod.Get); + var request = new Request($"{ApiBaseUrl}system/status", baseUrl, HttpMethod.Get); request.AddHeader("X-Api-Key", apiKey); return await Api.Request(request); @@ -217,7 +218,7 @@ namespace Ombi.Api.Sonarr ignoreEpisodesWithoutFiles = false, } }; - var request = new Request("/api/seasonpass", baseUrl, HttpMethod.Post); + var request = new Request($"{ApiBaseUrl}seasonpass", baseUrl, HttpMethod.Post); request.AddHeader("X-Api-Key", apiKey); request.AddJsonBody(seasonPass); diff --git a/src/Ombi.Api.Sonarr/SonarrV3Api.cs b/src/Ombi.Api.Sonarr/SonarrV3Api.cs new file mode 100644 index 000000000..64377ee4a --- /dev/null +++ b/src/Ombi.Api.Sonarr/SonarrV3Api.cs @@ -0,0 +1,25 @@ +using System.Net.Http; +using System.Collections.Generic; +using System.Threading.Tasks; +using Ombi.Api.Sonarr.Models.V3; + +namespace Ombi.Api.Sonarr +{ + public class SonarrV3Api : SonarrApi, ISonarrV3Api + { + public SonarrV3Api(IApi api) : base(api) + { + + } + + protected override string ApiBaseUrl => "/api/v3/"; + + public async Task> LanguageProfiles(string apiKey, string baseUrl) + { + var request = new Request($"{ApiBaseUrl}languageprofile", baseUrl, HttpMethod.Get); + request.AddHeader("X-Api-Key", apiKey); + + return await Api.Request>(request); + } + } +} diff --git a/src/Ombi.Api/Api.cs b/src/Ombi.Api/Api.cs index b0e7066a8..8748530ec 100644 --- a/src/Ombi.Api/Api.cs +++ b/src/Ombi.Api/Api.cs @@ -41,7 +41,7 @@ namespace Ombi.Api { if (!request.IgnoreErrors) { - LogError(request, httpResponseMessage); + await LogError(request, httpResponseMessage); } if (request.Retry) @@ -72,6 +72,7 @@ namespace Ombi.Api // do something with the response var receivedString = await httpResponseMessage.Content.ReadAsStringAsync(); + LogDebugContent(receivedString); if (request.ContentType == ContentType.Json) { request.OnBeforeDeserialization?.Invoke(receivedString); @@ -80,15 +81,20 @@ namespace Ombi.Api else { // XML - XmlSerializer serializer = new XmlSerializer(typeof(T)); - StringReader reader = new StringReader(receivedString); - var value = (T)serializer.Deserialize(reader); - return value; + return DeserializeXml(receivedString); } } } + public T DeserializeXml(string receivedString) + { + XmlSerializer serializer = new XmlSerializer(typeof(T)); + StringReader reader = new StringReader(receivedString); + var value = (T) serializer.Deserialize(reader); + return value; + } + public async Task RequestContent(Request request) { using (var httpRequestMessage = new HttpRequestMessage(request.HttpMethod, request.FullUri)) @@ -100,12 +106,12 @@ namespace Ombi.Api { if (!request.IgnoreErrors) { - LogError(request, httpResponseMessage); + await LogError(request, httpResponseMessage); } } // do something with the response var data = httpResponseMessage.Content; - + await LogDebugContent(httpResponseMessage); return await data.ReadAsStringAsync(); } @@ -117,21 +123,23 @@ namespace Ombi.Api { AddHeadersBody(request, httpRequestMessage); var httpResponseMessage = await _client.SendAsync(httpRequestMessage); + await LogDebugContent(httpResponseMessage); if (!httpResponseMessage.IsSuccessStatusCode) { if (!request.IgnoreErrors) { - LogError(request, httpResponseMessage); + await LogError(request, httpResponseMessage); } } } } - private static void AddHeadersBody(Request request, HttpRequestMessage httpRequestMessage) + private void AddHeadersBody(Request request, HttpRequestMessage httpRequestMessage) { // Add the Json Body if (request.JsonBody != null) { + LogDebugContent("REQUEST: " + request.JsonBody); httpRequestMessage.Content = new JsonContent(request.JsonBody); httpRequestMessage.Content.Headers.ContentType = new MediaTypeHeaderValue("application/json"); // Emby connect fails if we have the charset in the header @@ -144,10 +152,28 @@ namespace Ombi.Api } } - private void LogError(Request request, HttpResponseMessage httpResponseMessage) + private async Task LogError(Request request, HttpResponseMessage httpResponseMessage) { Logger.LogError(LoggingEvents.Api, $"StatusCode: {httpResponseMessage.StatusCode}, Reason: {httpResponseMessage.ReasonPhrase}, RequestUri: {request.FullUri}"); + await LogDebugContent(httpResponseMessage); + } + + private async Task LogDebugContent(HttpResponseMessage message) + { + if (Logger.IsEnabled(LogLevel.Debug)) + { + var content = await message.Content.ReadAsStringAsync(); + Logger.LogDebug(content); + } + } + + private void LogDebugContent(string message) + { + if (Logger.IsEnabled(LogLevel.Debug)) + { + Logger.LogDebug(message); + } } } } diff --git a/src/Ombi.Api/HttpRequestExtnesions.cs b/src/Ombi.Api/HttpRequestExtensions.cs similarity index 100% rename from src/Ombi.Api/HttpRequestExtnesions.cs rename to src/Ombi.Api/HttpRequestExtensions.cs diff --git a/src/Ombi.Api/IApi.cs b/src/Ombi.Api/IApi.cs index 2b7f71bb8..e573d2d07 100644 --- a/src/Ombi.Api/IApi.cs +++ b/src/Ombi.Api/IApi.cs @@ -7,5 +7,6 @@ namespace Ombi.Api Task Request(Request request); Task Request(Request request); Task RequestContent(Request request); + T DeserializeXml(string receivedString); } } \ No newline at end of file diff --git a/src/Ombi.Api/Ombi.Api.csproj b/src/Ombi.Api/Ombi.Api.csproj index a37c128fb..c7b905522 100644 --- a/src/Ombi.Api/Ombi.Api.csproj +++ b/src/Ombi.Api/Ombi.Api.csproj @@ -9,9 +9,9 @@ - - - + + + diff --git a/src/Ombi.Api/Request.cs b/src/Ombi.Api/Request.cs index cfd284f54..fd888d0d2 100644 --- a/src/Ombi.Api/Request.cs +++ b/src/Ombi.Api/Request.cs @@ -28,6 +28,7 @@ namespace Ombi.Api public bool IgnoreErrors { get; set; } public bool Retry { get; set; } public List StatusCodeToRetry { get; set; } = new List(); + public bool IgnoreBaseUrlAppend { get; set; } public Action OnBeforeDeserialization { get; set; } @@ -38,7 +39,7 @@ namespace Ombi.Api var sb = new StringBuilder(); if (!string.IsNullOrEmpty(BaseUrl)) { - sb.Append(!BaseUrl.EndsWith("/") ? string.Format("{0}/", BaseUrl) : BaseUrl); + sb.Append(!BaseUrl.EndsWith("/") && !IgnoreBaseUrlAppend ? string.Format("{0}/", BaseUrl) : BaseUrl); } sb.Append(Endpoint.StartsWith("/") ? Endpoint.Remove(0, 1) : Endpoint); return sb.ToString(); diff --git a/src/Ombi.Core.Tests/Engine/VoteEngineTests.cs b/src/Ombi.Core.Tests/Engine/VoteEngineTests.cs new file mode 100644 index 000000000..ad4c33131 --- /dev/null +++ b/src/Ombi.Core.Tests/Engine/VoteEngineTests.cs @@ -0,0 +1,73 @@ +using System.Collections.Generic; +using System.Linq; +using System.Security.Principal; +using System.Threading.Tasks; +using AutoFixture; +using Moq; +using NUnit.Framework; +using Ombi.Core.Authentication; +using Ombi.Core.Engine; +using Ombi.Core.Engine.Interfaces; +using Ombi.Core.Rule.Interfaces; +using Ombi.Core.Settings; +using Ombi.Settings.Settings.Models; +using Ombi.Store.Entities; +using Ombi.Store.Repository; + +namespace Ombi.Core.Tests.Engine +{ + [TestFixture] + public class VoteEngineTests + { + [SetUp] + public void Setup() + { + F = new Fixture(); + VoteRepository = new Mock>(); + VoteSettings = new Mock>(); + MusicRequestEngine = new Mock(); + TvRequestEngine = new Mock(); + MovieRequestEngine = new Mock(); + MovieRequestEngine = new Mock(); + User = new Mock(); + UserManager = new Mock(); + UserManager.Setup(x => x.Users) + .Returns(new EnumerableQuery(new List {new OmbiUser {Id = "abc"}})); + Rule = new Mock(); + Engine = new VoteEngine(VoteRepository.Object, User.Object, UserManager.Object, Rule.Object, VoteSettings.Object, MusicRequestEngine.Object, + TvRequestEngine.Object, MovieRequestEngine.Object); + } + + public Fixture F { get; set; } + public VoteEngine Engine { get; set; } + public Mock User { get; set; } + public Mock UserManager { get; set; } + public Mock Rule { get; set; } + public Mock> VoteRepository { get; set; } + public Mock> VoteSettings { get; set; } + public Mock MusicRequestEngine { get; set; } + public Mock TvRequestEngine { get; set; } + public Mock MovieRequestEngine { get; set; } + + [Test] + [Ignore("Need to mock the user manager")] + public async Task New_Upvote() + { + VoteSettings.Setup(x => x.GetSettingsAsync()).ReturnsAsync(new VoteSettings()); + var votes = F.CreateMany().ToList(); + votes.Add(new Votes + { + RequestId = 1, + RequestType = RequestType.Movie, + UserId = "abc" + }); + VoteRepository.Setup(x => x.GetAll()).Returns(new EnumerableQuery(votes)); + var result = await Engine.UpVote(1, RequestType.Movie); + + Assert.That(result.Result, Is.True); + VoteRepository.Verify(x => x.Add(It.Is(c => c.UserId == "abc" && c.VoteType == VoteType.Upvote)), Times.Once); + VoteRepository.Verify(x => x.Delete(It.IsAny()), Times.Once); + MovieRequestEngine.Verify(x => x.ApproveMovieById(1), Times.Never); + } + } +} \ No newline at end of file diff --git a/src/Ombi.Core.Tests/Ombi.Core.Tests.csproj b/src/Ombi.Core.Tests/Ombi.Core.Tests.csproj index 8f0abee8f..23fc6db78 100644 --- a/src/Ombi.Core.Tests/Ombi.Core.Tests.csproj +++ b/src/Ombi.Core.Tests/Ombi.Core.Tests.csproj @@ -1,15 +1,16 @@  - netcoreapp2.1 + netcoreapp2.2 - - - - - + + + + + + diff --git a/src/Ombi.Core.Tests/Rule/Request/AutoApproveRuleTests.cs b/src/Ombi.Core.Tests/Rule/Request/AutoApproveRuleTests.cs index 7ff8283da..34c21e008 100644 --- a/src/Ombi.Core.Tests/Rule/Request/AutoApproveRuleTests.cs +++ b/src/Ombi.Core.Tests/Rule/Request/AutoApproveRuleTests.cs @@ -4,6 +4,7 @@ using Moq; using Ombi.Core.Rule.Rules.Request; using Ombi.Store.Entities.Requests; using NUnit.Framework; +using Ombi.Core.Authentication; using Ombi.Helpers; namespace Ombi.Core.Tests.Rule.Request @@ -16,7 +17,7 @@ namespace Ombi.Core.Tests.Rule.Request { PrincipalMock = new Mock(); - Rule = new AutoApproveRule(PrincipalMock.Object); + Rule = new AutoApproveRule(PrincipalMock.Object, null); } diff --git a/src/Ombi.Core.Tests/Rule/Request/CanRequestRuleTests.cs b/src/Ombi.Core.Tests/Rule/Request/CanRequestRuleTests.cs index c9db5875a..f2781c8d2 100644 --- a/src/Ombi.Core.Tests/Rule/Request/CanRequestRuleTests.cs +++ b/src/Ombi.Core.Tests/Rule/Request/CanRequestRuleTests.cs @@ -3,6 +3,7 @@ using System.Threading.Tasks; using Moq; using NUnit.Framework; using Ombi.Core.Rule.Rules; +using Ombi.Core.Rule.Rules.Request; using Ombi.Helpers; using Ombi.Store.Entities.Requests; @@ -15,7 +16,7 @@ namespace Ombi.Core.Tests.Rule.Request { PrincipalMock = new Mock(); - Rule = new CanRequestRule(PrincipalMock.Object); + Rule = new CanRequestRule(PrincipalMock.Object, null); } diff --git a/src/Ombi.Core.Tests/Rule/Search/CouchPotatoCacheRuleTests.cs b/src/Ombi.Core.Tests/Rule/Search/CouchPotatoCacheRuleTests.cs index 56524522b..2a8f7a520 100644 --- a/src/Ombi.Core.Tests/Rule/Search/CouchPotatoCacheRuleTests.cs +++ b/src/Ombi.Core.Tests/Rule/Search/CouchPotatoCacheRuleTests.cs @@ -18,13 +18,13 @@ namespace Ombi.Core.Tests.Rule.Search [SetUp] public void Setup() { - ContextMock = new Mock>(); + ContextMock = new Mock>(); Rule = new CouchPotatoCacheRule(ContextMock.Object); } private CouchPotatoCacheRule Rule { get; set; } - private Mock> ContextMock { get; set; } + private Mock> ContextMock { get; set; } [Test] public async Task Should_ReturnApproved_WhenMovieIsInCouchPotato() diff --git a/src/Ombi.Core.Tests/Rule/Search/EmbyAvailabilityRuleTests.cs b/src/Ombi.Core.Tests/Rule/Search/EmbyAvailabilityRuleTests.cs index 0633d641e..0171e37a1 100644 --- a/src/Ombi.Core.Tests/Rule/Search/EmbyAvailabilityRuleTests.cs +++ b/src/Ombi.Core.Tests/Rule/Search/EmbyAvailabilityRuleTests.cs @@ -16,7 +16,7 @@ namespace Ombi.Core.Tests.Rule.Search public void Setup() { ContextMock = new Mock(); - Rule = new EmbyAvailabilityRule(ContextMock.Object); + Rule = new EmbyAvailabilityRule(ContextMock.Object, null); } private EmbyAvailabilityRule Rule { get; set; } @@ -29,7 +29,10 @@ namespace Ombi.Core.Tests.Rule.Search { ProviderId = "123" }); - var search = new SearchMovieViewModel(); + var search = new SearchMovieViewModel() + { + TheMovieDbId = "123", + }; var result = await Rule.Execute(search); Assert.True(result.Success); diff --git a/src/Ombi.Core.Tests/Rule/Search/ExistingRequestRuleTests.cs b/src/Ombi.Core.Tests/Rule/Search/ExistingRequestRuleTests.cs index a706472dd..e32c8e996 100644 --- a/src/Ombi.Core.Tests/Rule/Search/ExistingRequestRuleTests.cs +++ b/src/Ombi.Core.Tests/Rule/Search/ExistingRequestRuleTests.cs @@ -19,12 +19,14 @@ namespace Ombi.Core.Tests.Rule.Search MovieMock = new Mock(); TvMock = new Mock(); - Rule = new ExistingRule(MovieMock.Object, TvMock.Object); + MusicMock = new Mock(); + Rule = new ExistingRule(MovieMock.Object, TvMock.Object, MusicMock.Object); } private ExistingRule Rule { get; set; } private Mock MovieMock { get; set; } private Mock TvMock { get; set; } + private Mock MusicMock { get; set; } [Test] diff --git a/src/Ombi.Core.Tests/Rule/Search/PlexAvailabilityRuleTests.cs b/src/Ombi.Core.Tests/Rule/Search/PlexAvailabilityRuleTests.cs index 55177a6ac..5bd35473c 100644 --- a/src/Ombi.Core.Tests/Rule/Search/PlexAvailabilityRuleTests.cs +++ b/src/Ombi.Core.Tests/Rule/Search/PlexAvailabilityRuleTests.cs @@ -1,4 +1,5 @@ using System.Threading.Tasks; +using Microsoft.Extensions.Logging; using Moq; using NUnit.Framework; using Ombi.Core.Models.Search; @@ -14,7 +15,7 @@ namespace Ombi.Core.Tests.Rule.Search public void Setup() { ContextMock = new Mock(); - Rule = new PlexAvailabilityRule(ContextMock.Object); + Rule = new PlexAvailabilityRule(ContextMock.Object, new Mock>().Object); } private PlexAvailabilityRule Rule { get; set; } diff --git a/src/Ombi.Core.Tests/Rule/Search/RadarrCacheRuleTests.cs b/src/Ombi.Core.Tests/Rule/Search/RadarrCacheRuleTests.cs index 914112d5b..94efe89a2 100644 --- a/src/Ombi.Core.Tests/Rule/Search/RadarrCacheRuleTests.cs +++ b/src/Ombi.Core.Tests/Rule/Search/RadarrCacheRuleTests.cs @@ -15,13 +15,13 @@ namespace Ombi.Core.Tests.Rule.Search [SetUp] public void Setup() { - ContextMock = new Mock>(); + ContextMock = new Mock>(); Rule = new RadarrCacheRule(ContextMock.Object); } private RadarrCacheRule Rule { get; set; } - private Mock> ContextMock { get; set; } + private Mock> ContextMock { get; set; } [Test] public async Task Should_ReturnApproved_WhenMovieIsInRadarr() diff --git a/src/Ombi.Core.Tests/StringHelperTests.cs b/src/Ombi.Core.Tests/StringHelperTests.cs new file mode 100644 index 000000000..c1b95fcd7 --- /dev/null +++ b/src/Ombi.Core.Tests/StringHelperTests.cs @@ -0,0 +1,26 @@ +using System.Collections.Generic; + +using NUnit.Framework; +using Ombi.Helpers; + +namespace Ombi.Core.Tests +{ + [TestFixture] + public class StringHelperTests + { + [TestCaseSource(nameof(StripCharsData))] + public string StripCharacters(string str, char[] chars) + { + return str.StripCharacters(chars); + } + + private static IEnumerable StripCharsData + { + get + { + yield return new TestCaseData("this!is^a*string",new []{'!','^','*'}).Returns("thisisastring").SetName("Basic Strip Multipe Chars"); + yield return new TestCaseData("What is this madness'",new []{'\'','^','*'}).Returns("What is this madness").SetName("Basic Strip Multipe Chars"); + } + } + } +} \ No newline at end of file diff --git a/src/Ombi.Core/Authentication/OmbiUserManager.cs b/src/Ombi.Core/Authentication/OmbiUserManager.cs index 185677215..2c78f39bf 100644 --- a/src/Ombi.Core/Authentication/OmbiUserManager.cs +++ b/src/Ombi.Core/Authentication/OmbiUserManager.cs @@ -29,6 +29,7 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.AspNetCore.Identity; +using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Ombi.Api.Emby; @@ -101,6 +102,22 @@ namespace Ombi.Core.Authentication return true; } + public async Task GetOmbiUserFromPlexToken(string plexToken) + { + var plexAccount = await _plexApi.GetAccount(plexToken); + + // Check for a ombi user + if (plexAccount?.user != null) + { + var potentialOmbiUser = await Users.FirstOrDefaultAsync(x => + x.ProviderUserId == plexAccount.user.id); + return potentialOmbiUser; + } + + return null; + } + + /// /// Sign the user into plex and make sure we can get the authentication token. /// We do not check if the user is in the owners "friends" since they must have a local user account to get this far diff --git a/src/Ombi.Core/Authentication/PlexOAuthManager.cs b/src/Ombi.Core/Authentication/PlexOAuthManager.cs index 803176d74..76b1b5d97 100644 --- a/src/Ombi.Core/Authentication/PlexOAuthManager.cs +++ b/src/Ombi.Core/Authentication/PlexOAuthManager.cs @@ -28,19 +28,6 @@ namespace Ombi.Core.Authentication return string.Empty; } - if (pin.authToken.IsNullOrEmpty()) - { - // Looks like we do not have a pin yet, we should retry a few times. - var retryCount = 0; - var retryMax = 5; - var retryWaitMs = 1000; - while (pin.authToken.IsNullOrEmpty() && retryCount < retryMax) - { - retryCount++; - await Task.Delay(retryWaitMs); - pin = await _api.GetPin(pinId); - } - } return pin.authToken; } @@ -49,17 +36,17 @@ namespace Ombi.Core.Authentication return await _api.GetAccount(accessToken); } - public async Task GetOAuthUrl(int pinId, string code, string websiteAddress = null) + public async Task GetOAuthUrl(string code, string websiteAddress = null) { var settings = await _customizationSettingsService.GetSettingsAsync(); - var url = await _api.GetOAuthUrl(pinId, code, settings.ApplicationUrl.IsNullOrEmpty() ? websiteAddress : settings.ApplicationUrl, false); + var url = await _api.GetOAuthUrl(code, settings.ApplicationUrl.IsNullOrEmpty() ? websiteAddress : settings.ApplicationUrl); return url; } - public async Task GetWizardOAuthUrl(int pinId, string code, string websiteAddress) + public async Task GetWizardOAuthUrl(string code, string websiteAddress) { - var url = await _api.GetOAuthUrl(pinId, code, websiteAddress, true); + var url = await _api.GetOAuthUrl(code, websiteAddress); return url; } } diff --git a/src/Ombi.Core/Engine/BaseMediaEngine.cs b/src/Ombi.Core/Engine/BaseMediaEngine.cs index 2eab74b75..d0bb74c91 100644 --- a/src/Ombi.Core/Engine/BaseMediaEngine.cs +++ b/src/Ombi.Core/Engine/BaseMediaEngine.cs @@ -36,6 +36,7 @@ namespace Ombi.Core.Engine protected IRequestServiceMain RequestService { get; } protected IMovieRequestRepository MovieRepository => RequestService.MovieRequestService; protected ITvRequestRepository TvRepository => RequestService.TvRequestService; + protected IMusicRequestRepository MusicRepository => RequestService.MusicRequestRepository; protected readonly ICacheService Cache; protected readonly ISettingsService OmbiSettings; protected readonly IRepository _subscriptionRepository; @@ -156,6 +157,24 @@ namespace Ombi.Core.Engine } } + private string defaultLangCode; + protected async Task DefaultLanguageCode(string currentCode) + { + if (currentCode.HasValue()) + { + return currentCode; + } + + var s = await GetOmbiSettings(); + return s.DefaultLanguageCode; + } + + private OmbiSettings ombiSettings; + protected async Task GetOmbiSettings() + { + return ombiSettings ?? (ombiSettings = await OmbiSettings.GetSettingsAsync()); + } + public class HideResult { public bool Hide { get; set; } diff --git a/src/Ombi.Core/Engine/Demo/DemoMovieSearchEngine.cs b/src/Ombi.Core/Engine/Demo/DemoMovieSearchEngine.cs new file mode 100644 index 000000000..86582fb4d --- /dev/null +++ b/src/Ombi.Core/Engine/Demo/DemoMovieSearchEngine.cs @@ -0,0 +1,106 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Principal; +using System.Text; +using System.Threading.Tasks; +using AutoMapper; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Ombi.Api.TheMovieDb; +using Ombi.Api.TheMovieDb.Models; +using Ombi.Config; +using Ombi.Core.Authentication; +using Ombi.Core.Models.Requests; +using Ombi.Core.Models.Search; +using Ombi.Core.Rule.Interfaces; +using Ombi.Core.Settings; +using Ombi.Helpers; +using Ombi.Settings.Settings.Models; +using Ombi.Store.Entities; +using Ombi.Store.Repository; + +namespace Ombi.Core.Engine.Demo +{ + public class DemoMovieSearchEngine : MovieSearchEngine, IDemoMovieSearchEngine + { + public DemoMovieSearchEngine(IPrincipal identity, IRequestServiceMain service, IMovieDbApi movApi, IMapper mapper, + ILogger logger, IRuleEvaluator r, OmbiUserManager um, ICacheService mem, ISettingsService s, + IRepository sub, IOptions lists) + : base(identity, service, movApi, mapper, logger, r, um, mem, s, sub) + { + _demoLists = lists.Value; + } + + private readonly DemoLists _demoLists; + + public async Task> Search(string search) + { + var result = await MovieApi.SearchMovie(search, null, "en"); + + for (var i = 0; i < result.Count; i++) + { + if (!_demoLists.Movies.Contains(result[i].Id)) + { + result.RemoveAt(i); + } + } + if(result.Count > 0) + return await TransformMovieResultsToResponse(result.Take(MovieLimit)); // Take x to stop us overloading the API + return null; + } + + public async Task> NowPlayingMovies() + { + var rand = new Random(); + var responses = new List(); + for (int i = 0; i < 10; i++) + { + var item = rand.Next(_demoLists.Movies.Length); + var movie = _demoLists.Movies[item]; + if (responses.Any(x => x.Id == movie)) + { + i--; + continue; + } + var movieResult = await MovieApi.GetMovieInformationWithExtraInfo(movie); + var viewMovie = Mapper.Map(movieResult); + + responses.Add(await ProcessSingleMovie(viewMovie)); + } + + return responses; + } + + public async Task> PopularMovies() + { + return await NowPlayingMovies(); + } + + + public async Task> TopRatedMovies() + { + return await NowPlayingMovies(); + } + + public async Task> UpcomingMovies() + { + + return await NowPlayingMovies(); + } + } + + public interface IDemoMovieSearchEngine + { + Task> NowPlayingMovies(); + + Task> PopularMovies(); + + Task> Search(string search); + + Task> TopRatedMovies(); + + Task> UpcomingMovies(); + + } +} diff --git a/src/Ombi.Core/Engine/Demo/DemoTvSearchEngine.cs b/src/Ombi.Core/Engine/Demo/DemoTvSearchEngine.cs new file mode 100644 index 000000000..edf9c430d --- /dev/null +++ b/src/Ombi.Core/Engine/Demo/DemoTvSearchEngine.cs @@ -0,0 +1,96 @@ +using AutoMapper; +using Microsoft.Extensions.Options; +using Ombi.Api.Trakt; +using Ombi.Api.TvMaze; +using Ombi.Config; +using Ombi.Core.Authentication; +using Ombi.Core.Models.Requests; +using Ombi.Core.Models.Search; +using Ombi.Core.Rule.Interfaces; +using Ombi.Core.Settings; +using Ombi.Core.Settings.Models.External; +using Ombi.Helpers; +using Ombi.Settings.Settings.Models; +using Ombi.Store.Entities; +using Ombi.Store.Repository; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Principal; +using System.Threading.Tasks; + +namespace Ombi.Core.Engine.Demo +{ + public class DemoTvSearchEngine : TvSearchEngine, IDemoTvSearchEngine + { + + public DemoTvSearchEngine(IPrincipal identity, IRequestServiceMain service, ITvMazeApi tvMaze, IMapper mapper, + ISettingsService plexSettings, ISettingsService embySettings, IPlexContentRepository repo, + IEmbyContentRepository embyRepo, ITraktApi trakt, IRuleEvaluator r, OmbiUserManager um, ICacheService memCache, + ISettingsService s, IRepository sub, IOptions lists) + : base(identity, service, tvMaze, mapper, plexSettings, embySettings, repo, embyRepo, trakt, r, um, memCache, s, sub) + { + _demoLists = lists.Value; + } + + private readonly DemoLists _demoLists; + + public async Task> Search(string search) + { + var searchResult = await TvMazeApi.Search(search); + + for (var i = 0; i < searchResult.Count; i++) + { + if (!_demoLists.TvShows.Contains(searchResult[i].show?.externals?.thetvdb ?? 0)) + { + searchResult.RemoveAt(i); + } + } + + if (searchResult != null) + { + var retVal = new List(); + foreach (var tvMazeSearch in searchResult) + { + if (tvMazeSearch.show.externals == null || !(tvMazeSearch.show.externals?.thetvdb.HasValue ?? false)) + { + continue; + } + retVal.Add(ProcessResult(tvMazeSearch)); + } + return retVal; + } + return null; + } + + public async Task> NowPlayingMovies() + { + var rand = new Random(); + var responses = new List(); + for (int i = 0; i < 10; i++) + { + var item = rand.Next(_demoLists.TvShows.Length); + var tv = _demoLists.TvShows[item]; + if (responses.Any(x => x.Id == tv)) + { + i--; + continue; + } + + var movieResult = await TvMazeApi.ShowLookup(tv); + responses.Add(ProcessResult(movieResult)); + } + + return responses; + } + + + + } + + public interface IDemoTvSearchEngine + { + Task> Search(string search); + Task> NowPlayingMovies(); + } +} diff --git a/src/Ombi.Core/Engine/IMusicRequestEngine.cs b/src/Ombi.Core/Engine/IMusicRequestEngine.cs new file mode 100644 index 000000000..02a051343 --- /dev/null +++ b/src/Ombi.Core/Engine/IMusicRequestEngine.cs @@ -0,0 +1,27 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using Ombi.Core.Models; +using Ombi.Core.Models.Requests; +using Ombi.Core.Models.UI; +using Ombi.Store.Entities; +using Ombi.Store.Entities.Requests; + +namespace Ombi.Core.Engine +{ + public interface IMusicRequestEngine + { + TaskApproveAlbum(AlbumRequest request); + Task ApproveAlbumById(int requestId); + Task DenyAlbumById(int modelId, string reason); + Task> GetRequests(); + Task> GetRequests(int count, int position, OrderFilterModel orderFilter); + Task GetTotal(); + Task MarkAvailable(int modelId); + Task MarkUnavailable(int modelId); + Task RemoveAlbumRequest(int requestId); + Task RequestAlbum(MusicAlbumRequestViewModel model); + Task> SearchAlbumRequest(string search); + Task UserHasRequest(string userId); + Task GetRemainingRequests(OmbiUser user = null); + } +} \ No newline at end of file diff --git a/src/Ombi.Core/Engine/IVoteEngine.cs b/src/Ombi.Core/Engine/IVoteEngine.cs new file mode 100644 index 000000000..681f333c5 --- /dev/null +++ b/src/Ombi.Core/Engine/IVoteEngine.cs @@ -0,0 +1,19 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Ombi.Core.Models; +using Ombi.Core.Models.UI; +using Ombi.Store.Entities; + +namespace Ombi.Core.Engine +{ + public interface IVoteEngine + { + Task DownVote(int requestId, RequestType requestType); + Task GetVoteForUser(int requestId, string userId); + IQueryable GetVotes(int requestId, RequestType requestType); + Task RemoveCurrentVote(Votes currentVote); + Task UpVote(int requestId, RequestType requestType); + Task> GetMovieViewModel(); + } +} \ No newline at end of file diff --git a/src/Ombi.Core/Engine/Interfaces/BaseEngine.cs b/src/Ombi.Core/Engine/Interfaces/BaseEngine.cs index 26bc5969c..c5cb8c45a 100644 --- a/src/Ombi.Core/Engine/Interfaces/BaseEngine.cs +++ b/src/Ombi.Core/Engine/Interfaces/BaseEngine.cs @@ -7,11 +7,8 @@ using Ombi.Core.Models.Search; using Ombi.Core.Rule.Interfaces; using Ombi.Store.Entities.Requests; using Ombi.Store.Entities; -using Microsoft.AspNetCore.Identity; -using System.Linq; using Microsoft.EntityFrameworkCore; using Ombi.Core.Authentication; -using Ombi.Helpers; namespace Ombi.Core.Engine.Interfaces { diff --git a/src/Ombi.Core/Engine/Interfaces/IMovieEngine.cs b/src/Ombi.Core/Engine/Interfaces/IMovieEngine.cs index 91b6404db..9b4cd9831 100644 --- a/src/Ombi.Core/Engine/Interfaces/IMovieEngine.cs +++ b/src/Ombi.Core/Engine/Interfaces/IMovieEngine.cs @@ -10,14 +10,15 @@ namespace Ombi.Core Task> PopularMovies(); - Task> Search(string search); + Task> Search(string search, int? year, string languageCode); Task> TopRatedMovies(); Task> UpcomingMovies(); - Task LookupImdbInformation(int theMovieDbId); + Task LookupImdbInformation(int theMovieDbId, string langCode = null); - Task> SimilarMovies(int theMovieDbId); + Task> SimilarMovies(int theMovieDbId, string langCode); + Task> SearchActor(string search, string langaugeCode); } } \ No newline at end of file diff --git a/src/Ombi.Core/Engine/Interfaces/IMovieRequestEngine.cs b/src/Ombi.Core/Engine/Interfaces/IMovieRequestEngine.cs index 91cbb9e72..d741dc8bc 100644 --- a/src/Ombi.Core/Engine/Interfaces/IMovieRequestEngine.cs +++ b/src/Ombi.Core/Engine/Interfaces/IMovieRequestEngine.cs @@ -12,12 +12,11 @@ namespace Ombi.Core.Engine.Interfaces Task> SearchMovieRequest(string search); Task RemoveMovieRequest(int requestId); + Task RemoveAllMovieRequests(); Task UpdateMovieRequest(MovieRequests request); Task ApproveMovie(MovieRequests request); Task ApproveMovieById(int requestId); - Task DenyMovieById(int modelId); - - + Task DenyMovieById(int modelId, string denyReason); } } \ No newline at end of file diff --git a/src/Ombi.Core/Engine/Interfaces/IMusicSearchEngine.cs b/src/Ombi.Core/Engine/Interfaces/IMusicSearchEngine.cs new file mode 100644 index 000000000..71c02af9d --- /dev/null +++ b/src/Ombi.Core/Engine/Interfaces/IMusicSearchEngine.cs @@ -0,0 +1,17 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using Ombi.Api.Lidarr.Models; +using Ombi.Core.Models.Search; + +namespace Ombi.Core.Engine +{ + public interface IMusicSearchEngine + { + Task GetAlbumArtist(string foreignArtistId); + Task GetArtist(int artistId); + Task> GetArtistAlbums(string foreignArtistId); + Task> SearchAlbum(string search); + Task> SearchArtist(string search); + Task GetAlbumInformation(string foreignAlbumId); + } +} \ No newline at end of file diff --git a/src/Ombi.Core/Engine/IRecentlyAddedEngine.cs b/src/Ombi.Core/Engine/Interfaces/IRecentlyAddedEngine.cs similarity index 100% rename from src/Ombi.Core/Engine/IRecentlyAddedEngine.cs rename to src/Ombi.Core/Engine/Interfaces/IRecentlyAddedEngine.cs diff --git a/src/Ombi.Core/Engine/Interfaces/IRequestEngine.cs b/src/Ombi.Core/Engine/Interfaces/IRequestEngine.cs index 740428ec7..c8b7746f0 100644 --- a/src/Ombi.Core/Engine/Interfaces/IRequestEngine.cs +++ b/src/Ombi.Core/Engine/Interfaces/IRequestEngine.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Threading.Tasks; +using Ombi.Core.Models; using Ombi.Core.Models.Requests; using Ombi.Core.Models.UI; using Ombi.Store.Entities; @@ -22,5 +23,6 @@ namespace Ombi.Core.Engine.Interfaces Task GetTotal(); Task UnSubscribeRequest(int requestId, RequestType type); Task SubscribeToRequest(int requestId, RequestType type); + Task GetRemainingRequests(OmbiUser user = null); } } \ No newline at end of file diff --git a/src/Ombi.Core/Engine/Interfaces/ITvRequestEngine.cs b/src/Ombi.Core/Engine/Interfaces/ITvRequestEngine.cs index 348dc91e7..63de72bd1 100644 --- a/src/Ombi.Core/Engine/Interfaces/ITvRequestEngine.cs +++ b/src/Ombi.Core/Engine/Interfaces/ITvRequestEngine.cs @@ -12,16 +12,16 @@ namespace Ombi.Core.Engine.Interfaces Task RemoveTvRequest(int requestId); Task GetTvRequest(int requestId); Task RequestTvShow(TvRequestViewModel tv); - Task DenyChildRequest(int requestId); + Task DenyChildRequest(int requestId, string reason); Task> GetRequestsLite(int count, int position, OrderFilterModel type); Task> SearchTvRequest(string search); - Task>>> SearchTvRequestTree(string search); Task UpdateTvRequest(TvRequests request); - Task>>> GetRequestsTreeNode(int count, int position); Task> GetAllChldren(int tvId); Task UpdateChildRequest(ChildRequests request); Task RemoveTvChild(int requestId); Task ApproveChildRequest(int id); Task> GetRequestsLite(); + Task UpdateQualityProfile(int requestId, int profileId); + Task UpdateRootPath(int requestId, int rootPath); } } \ No newline at end of file diff --git a/src/Ombi.Core/Engine/Interfaces/ITvSearchEngine.cs b/src/Ombi.Core/Engine/Interfaces/ITvSearchEngine.cs index 53721f792..0926a7f9a 100644 --- a/src/Ombi.Core/Engine/Interfaces/ITvSearchEngine.cs +++ b/src/Ombi.Core/Engine/Interfaces/ITvSearchEngine.cs @@ -7,16 +7,10 @@ namespace Ombi.Core.Engine.Interfaces public interface ITvSearchEngine { Task> Search(string searchTerm); - Task>> SearchTreeNode(string searchTerm); - Task> GetShowInformationTreeNode(int tvdbid); Task GetShowInformation(int tvdbid); - Task>> PopularTree(); Task> Popular(); - Task>> AnticipatedTree(); Task> Anticipated(); - Task>> MostWatchesTree(); Task> MostWatches(); - Task>> TrendingTree(); Task> Trending(); } } \ No newline at end of file diff --git a/src/Ombi.Core/Engine/Interfaces/IUserStatsEngine.cs b/src/Ombi.Core/Engine/Interfaces/IUserStatsEngine.cs new file mode 100644 index 000000000..3b8474749 --- /dev/null +++ b/src/Ombi.Core/Engine/Interfaces/IUserStatsEngine.cs @@ -0,0 +1,9 @@ +using System.Threading.Tasks; + +namespace Ombi.Core.Engine +{ + public interface IUserStatsEngine + { + Task GetSummary(SummaryRequest request); + } +} \ No newline at end of file diff --git a/src/Ombi.Core/Engine/MovieRequestEngine.cs b/src/Ombi.Core/Engine/MovieRequestEngine.cs index 15383ed12..456ba267a 100644 --- a/src/Ombi.Core/Engine/MovieRequestEngine.cs +++ b/src/Ombi.Core/Engine/MovieRequestEngine.cs @@ -19,6 +19,7 @@ using Ombi.Core.Settings; using Ombi.Settings.Settings.Models; using Ombi.Store.Entities.Requests; using Ombi.Store.Repository; +using Ombi.Core.Models; namespace Ombi.Core.Engine { @@ -50,7 +51,7 @@ namespace Ombi.Core.Engine /// public async Task RequestMovie(MovieRequestViewModel model) { - var movieInfo = await MovieApi.GetMovieInformationWithExtraInfo(model.TheMovieDbId); + var movieInfo = await MovieApi.GetMovieInformationWithExtraInfo(model.TheMovieDbId, model.LanguageCode); if (movieInfo == null || movieInfo.Id == 0) { return new RequestEngineResult @@ -81,7 +82,9 @@ namespace Ombi.Core.Engine RequestedDate = DateTime.UtcNow, Approved = false, RequestedUserId = userDetails.Id, - Background = movieInfo.BackdropPath + Background = movieInfo.BackdropPath, + LangCode = model.LanguageCode, + RequestedByAlias = model.RequestedByAlias }; var usDates = movieInfo.ReleaseDates?.Results?.FirstOrDefault(x => x.IsoCode == "US"); @@ -304,7 +307,7 @@ namespace Ombi.Core.Engine return await ApproveMovie(request); } - public async Task DenyMovieById(int modelId) + public async Task DenyMovieById(int modelId, string denyReason) { var request = await MovieRepository.Find(modelId); if (request == null) @@ -316,12 +319,14 @@ namespace Ombi.Core.Engine } request.Denied = true; + request.DeniedReason = denyReason; // We are denying a request NotificationHelper.Notify(request, NotificationType.RequestDeclined); await MovieRepository.Update(request); return new RequestEngineResult { + Result = true, Message = "Request successfully deleted", }; } @@ -336,6 +341,7 @@ namespace Ombi.Core.Engine }; } + request.MarkedAsApproved = DateTime.Now; request.Approved = true; request.Denied = false; await MovieRepository.Update(request); @@ -414,6 +420,12 @@ namespace Ombi.Core.Engine await MovieRepository.Delete(request); } + public async Task RemoveAllMovieRequests() + { + var request = MovieRepository.GetAll(); + await MovieRepository.DeleteRange(request); + } + public async Task UserHasRequest(string userId) { return await MovieRepository.GetAll().AnyAsync(x => x.RequestedUserId == userId); @@ -452,6 +464,7 @@ namespace Ombi.Core.Engine } request.Available = true; + request.MarkedAsAvailable = DateTime.Now; NotificationHelper.Notify(request, NotificationType.RequestAvailable); await MovieRepository.Update(request); @@ -480,7 +493,51 @@ namespace Ombi.Core.Engine RequestType = RequestType.Movie, }); - return new RequestEngineResult {Result = true, Message = $"{movieName} has been successfully added!"}; + return new RequestEngineResult {Result = true, Message = $"{movieName} has been successfully added!", RequestId = model.Id}; + } + + public async Task GetRemainingRequests(OmbiUser user) + { + if (user == null) + { + user = await GetUser(); + + // If user is still null after attempting to get the logged in user, return null. + if (user == null) + { + return null; + } + } + + int limit = user.MovieRequestLimit ?? 0; + + if (limit <= 0) + { + return new RequestQuotaCountModel() + { + HasLimit = false, + Limit = 0, + Remaining = 0, + NextRequest = DateTime.Now, + }; + } + + IQueryable log = _requestLog.GetAll().Where(x => x.UserId == user.Id && x.RequestType == RequestType.Movie); + + int count = limit - await log.CountAsync(x => x.RequestDate >= DateTime.UtcNow.AddDays(-7)); + + DateTime oldestRequestedAt = await log.Where(x => x.RequestDate >= DateTime.UtcNow.AddDays(-7)) + .OrderBy(x => x.RequestDate) + .Select(x => x.RequestDate) + .FirstOrDefaultAsync(); + + return new RequestQuotaCountModel() + { + HasLimit = true, + Limit = limit, + Remaining = count, + NextRequest = DateTime.SpecifyKind(oldestRequestedAt.AddDays(7), DateTimeKind.Utc), + }; } } } \ No newline at end of file diff --git a/src/Ombi.Core/Engine/MovieSearchEngine.cs b/src/Ombi.Core/Engine/MovieSearchEngine.cs index 09b4cea6b..520f052a9 100644 --- a/src/Ombi.Core/Engine/MovieSearchEngine.cs +++ b/src/Ombi.Core/Engine/MovieSearchEngine.cs @@ -1,23 +1,22 @@ -using System; -using AutoMapper; +using AutoMapper; +using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; using Ombi.Api.TheMovieDb; using Ombi.Api.TheMovieDb.Models; +using Ombi.Core.Authentication; using Ombi.Core.Models.Requests; using Ombi.Core.Models.Search; -using System.Collections.Generic; -using System.Linq; -using System.Security.Principal; -using System.Threading.Tasks; -using Microsoft.EntityFrameworkCore; using Ombi.Core.Rule.Interfaces; -using Microsoft.Extensions.Caching.Memory; -using Ombi.Core.Authentication; using Ombi.Core.Settings; using Ombi.Helpers; using Ombi.Settings.Settings.Models; using Ombi.Store.Entities; using Ombi.Store.Repository; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Principal; +using System.Threading.Tasks; namespace Ombi.Core.Engine { @@ -32,18 +31,21 @@ namespace Ombi.Core.Engine Logger = logger; } - private IMovieDbApi MovieApi { get; } - private IMapper Mapper { get; } + protected IMovieDbApi MovieApi { get; } + protected IMapper Mapper { get; } private ILogger Logger { get; } + protected const int MovieLimit = 10; + /// /// Lookups the imdb information. /// /// The movie database identifier. /// - public async Task LookupImdbInformation(int theMovieDbId) + public async Task LookupImdbInformation(int theMovieDbId, string langCode = null) { - var movieInfo = await MovieApi.GetMovieInformationWithExtraInfo(theMovieDbId); + langCode = await DefaultLanguageCode(langCode); + var movieInfo = await MovieApi.GetMovieInformationWithExtraInfo(theMovieDbId, langCode); var viewMovie = Mapper.Map(movieInfo); return await ProcessSingleMovie(viewMovie, true); @@ -52,31 +54,58 @@ namespace Ombi.Core.Engine /// /// Searches the specified movie. /// - /// The search. - /// - public async Task> Search(string search) + public async Task> Search(string search, int? year, string langaugeCode) { - var result = await MovieApi.SearchMovie(search); + langaugeCode = await DefaultLanguageCode(langaugeCode); + var result = await MovieApi.SearchMovie(search, year, langaugeCode); if (result != null) { - return await TransformMovieResultsToResponse(result.Take(10)); // Take 10 to stop us overloading the API + return await TransformMovieResultsToResponse(result.Take(MovieLimit)); // Take x to stop us overloading the API } return null; } + public async Task> SearchActor(string search, string langaugeCode) + { + langaugeCode = await DefaultLanguageCode(langaugeCode); + var people = await MovieApi.SearchByActor(search, langaugeCode); + var person = people?.results?.Count > 0 ? people.results.FirstOrDefault() : null; + + var resultSet = new List(); + if (person == null) + { + return resultSet; + } + + // Get this person movie credits + var credits = await MovieApi.GetActorMovieCredits(person.id, langaugeCode); + // Grab results from both cast and crew, prefer items in cast. we can handle directors like this. + var movieResults = (from role in credits.cast select new { Id = role.id, Title = role.title, ReleaseDate = role.release_date }).ToList(); + movieResults.AddRange((from job in credits.crew select new { Id = job.id, Title = job.title, ReleaseDate = job.release_date }).ToList()); + + movieResults = movieResults.Take(10).ToList(); + foreach (var movieResult in movieResults) + { + resultSet.Add(await LookupImdbInformation(movieResult.Id, langaugeCode)); + } + + return resultSet; + } + /// /// Get similar movies to the id passed in /// /// /// - public async Task> SimilarMovies(int theMovieDbId) + public async Task> SimilarMovies(int theMovieDbId, string langCode) { - var result = await MovieApi.SimilarMovies(theMovieDbId); + langCode = await DefaultLanguageCode(langCode); + var result = await MovieApi.SimilarMovies(theMovieDbId, langCode); if (result != null) { Logger.LogDebug("Search Result: {result}", result); - return await TransformMovieResultsToResponse(result.Take(10)); // Take 10 to stop us overloading the API + return await TransformMovieResultsToResponse(result.Take(MovieLimit)); // Take x to stop us overloading the API } return null; } @@ -87,10 +116,15 @@ namespace Ombi.Core.Engine /// public async Task> PopularMovies() { - var result = await Cache.GetOrAdd(CacheKeys.PopularMovies, async () => await MovieApi.PopularMovies(), DateTime.Now.AddHours(12)); + + var result = await Cache.GetOrAdd(CacheKeys.PopularMovies, async () => + { + var langCode = await DefaultLanguageCode(null); + return await MovieApi.PopularMovies(langCode); + }, DateTime.Now.AddHours(12)); if (result != null) { - return await TransformMovieResultsToResponse(result.Take(10)); // Take 10 to stop us overloading the API + return await TransformMovieResultsToResponse(result.Take(MovieLimit)); // Take x to stop us overloading the API } return null; } @@ -101,10 +135,14 @@ namespace Ombi.Core.Engine /// public async Task> TopRatedMovies() { - var result = await Cache.GetOrAdd(CacheKeys.TopRatedMovies, async () => await MovieApi.TopRated(), DateTime.Now.AddHours(12)); + var result = await Cache.GetOrAdd(CacheKeys.TopRatedMovies, async () => + { + var langCode = await DefaultLanguageCode(null); + return await MovieApi.TopRated(langCode); + }, DateTime.Now.AddHours(12)); if (result != null) { - return await TransformMovieResultsToResponse(result.Take(10)); // Take 10 to stop us overloading the API + return await TransformMovieResultsToResponse(result.Take(MovieLimit)); // Take x to stop us overloading the API } return null; } @@ -115,11 +153,15 @@ namespace Ombi.Core.Engine /// public async Task> UpcomingMovies() { - var result = await Cache.GetOrAdd(CacheKeys.UpcomingMovies, async () => await MovieApi.Upcoming(), DateTime.Now.AddHours(12)); + var result = await Cache.GetOrAdd(CacheKeys.UpcomingMovies, async () => + { + var langCode = await DefaultLanguageCode(null); + return await MovieApi.Upcoming(langCode); + }, DateTime.Now.AddHours(12)); if (result != null) { Logger.LogDebug("Search Result: {result}", result); - return await TransformMovieResultsToResponse(result.Take(10)); // Take 10 to stop us overloading the API + return await TransformMovieResultsToResponse(result.Take(MovieLimit)); // Take x to stop us overloading the API } return null; } @@ -130,15 +172,19 @@ namespace Ombi.Core.Engine /// public async Task> NowPlayingMovies() { - var result = await Cache.GetOrAdd(CacheKeys.NowPlayingMovies, async () => await MovieApi.NowPlaying(), DateTime.Now.AddHours(12)); + var result = await Cache.GetOrAdd(CacheKeys.NowPlayingMovies, async () => + { + var langCode = await DefaultLanguageCode(null); + return await MovieApi.NowPlaying(langCode); + }, DateTime.Now.AddHours(12)); if (result != null) { - return await TransformMovieResultsToResponse(result.Take(10)); // Take 10 to stop us overloading the API + return await TransformMovieResultsToResponse(result.Take(MovieLimit)); // Take x to stop us overloading the API } return null; } - private async Task> TransformMovieResultsToResponse( + protected async Task> TransformMovieResultsToResponse( IEnumerable movies) { var viewMovies = new List(); @@ -149,24 +195,25 @@ namespace Ombi.Core.Engine return viewMovies; } - private async Task ProcessSingleMovie(SearchMovieViewModel viewMovie, bool lookupExtraInfo = false) + protected async Task ProcessSingleMovie(SearchMovieViewModel viewMovie, bool lookupExtraInfo = false) { - if (lookupExtraInfo) + if (lookupExtraInfo && viewMovie.ImdbId.IsNullOrEmpty()) { var showInfo = await MovieApi.GetMovieInformation(viewMovie.Id); viewMovie.Id = showInfo.Id; // TheMovieDbId viewMovie.ImdbId = showInfo.ImdbId; - var usDates = viewMovie.ReleaseDates?.Results?.FirstOrDefault(x => x.IsoCode == "US"); - viewMovie.DigitalReleaseDate = usDates?.ReleaseDate?.FirstOrDefault(x => x.Type == ReleaseDateType.Digital)?.ReleaseDate; } + var usDates = viewMovie.ReleaseDates?.Results?.FirstOrDefault(x => x.IsoCode == "US"); + viewMovie.DigitalReleaseDate = usDates?.ReleaseDate?.FirstOrDefault(x => x.Type == ReleaseDateType.Digital)?.ReleaseDate; + viewMovie.TheMovieDbId = viewMovie.Id.ToString(); await RunSearchRules(viewMovie); // This requires the rules to be run first to populate the RequestId property await CheckForSubscription(viewMovie); - + return viewMovie; } @@ -174,9 +221,13 @@ namespace Ombi.Core.Engine { // Check if this user requested it var user = await GetUser(); + if (user == null) + { + return; + } var request = await RequestService.MovieRequestService.GetAll() .AnyAsync(x => x.RequestedUserId.Equals(user.Id) && x.TheMovieDbId == viewModel.Id); - if (request) + if (request || viewModel.Available) { viewModel.ShowSubscribe = false; } diff --git a/src/Ombi.Core/Engine/MusicRequestEngine.cs b/src/Ombi.Core/Engine/MusicRequestEngine.cs new file mode 100644 index 000000000..8457de515 --- /dev/null +++ b/src/Ombi.Core/Engine/MusicRequestEngine.cs @@ -0,0 +1,505 @@ +using Ombi.Api.TheMovieDb; +using Ombi.Core.Models.Requests; +using Ombi.Helpers; +using Ombi.Store.Entities; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Security.Principal; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using Ombi.Api.Lidarr; +using Ombi.Core.Authentication; +using Ombi.Core.Engine.Interfaces; +using Ombi.Core.Models; +using Ombi.Core.Models.UI; +using Ombi.Core.Rule.Interfaces; +using Ombi.Core.Senders; +using Ombi.Core.Settings; +using Ombi.Settings.Settings.Models; +using Ombi.Settings.Settings.Models.External; +using Ombi.Store.Entities.Requests; +using Ombi.Store.Repository; + +namespace Ombi.Core.Engine +{ + public class MusicRequestEngine : BaseMediaEngine, IMusicRequestEngine + { + public MusicRequestEngine(IRequestServiceMain requestService, IPrincipal user, + INotificationHelper helper, IRuleEvaluator r, ILogger log, + OmbiUserManager manager, IRepository rl, ICacheService cache, + ISettingsService ombiSettings, IRepository sub, ILidarrApi lidarr, + ISettingsService lidarrSettings, IMusicSender sender) + : base(user, requestService, r, manager, cache, ombiSettings, sub) + { + NotificationHelper = helper; + _musicSender = sender; + Logger = log; + _requestLog = rl; + _lidarrApi = lidarr; + _lidarrSettings = lidarrSettings; + } + + private INotificationHelper NotificationHelper { get; } + //private IMovieSender Sender { get; } + private ILogger Logger { get; } + private readonly IRepository _requestLog; + private readonly ISettingsService _lidarrSettings; + private readonly ILidarrApi _lidarrApi; + private readonly IMusicSender _musicSender; + + /// + /// Requests the Album. + /// + /// The model. + /// + public async Task RequestAlbum(MusicAlbumRequestViewModel model) + { + var s = await _lidarrSettings.GetSettingsAsync(); + var album = await _lidarrApi.GetAlbumByForeignId(model.ForeignAlbumId, s.ApiKey, s.FullUri); + if (album == null) + { + return new RequestEngineResult + { + Result = false, + Message = "There was an issue adding this album!", + ErrorMessage = "Please try again later" + }; + } + + var userDetails = await GetUser(); + + var requestModel = new AlbumRequest + { + ForeignAlbumId = model.ForeignAlbumId, + ArtistName = album.artist?.artistName, + ReleaseDate = album.releaseDate, + RequestedDate = DateTime.Now, + RequestType = RequestType.Album, + Rating = album.ratings?.value ?? 0m, + RequestedUserId = userDetails.Id, + Title = album.title, + Disk = album.images?.FirstOrDefault(x => x.coverType.Equals("disc"))?.url, + Cover = album.images?.FirstOrDefault(x => x.coverType.Equals("cover"))?.url, + ForeignArtistId = album?.artist?.foreignArtistId ?? string.Empty, + RequestedByAlias = model.RequestedByAlias + }; + if (requestModel.Cover.IsNullOrEmpty()) + { + requestModel.Cover = album.remoteCover; + } + + var ruleResults = (await RunRequestRules(requestModel)).ToList(); + if (ruleResults.Any(x => !x.Success)) + { + return new RequestEngineResult + { + ErrorMessage = ruleResults.FirstOrDefault(x => x.Message.HasValue()).Message + }; + } + + if (requestModel.Approved) // The rules have auto approved this + { + var requestEngineResult = await AddAlbumRequest(requestModel); + if (requestEngineResult.Result) + { + var result = await ApproveAlbum(requestModel); + if (result.IsError) + { + Logger.LogWarning("Tried auto sending Album but failed. Message: {0}", result.Message); + return new RequestEngineResult + { + Message = result.Message, + ErrorMessage = result.Message, + Result = false + }; + } + + return requestEngineResult; + } + + // If there are no providers then it's successful but album has not been sent + } + + return await AddAlbumRequest(requestModel); + } + + + /// + /// Gets the requests. + /// + /// The count. + /// The position. + /// The order/filter type. + /// + public async Task> GetRequests(int count, int position, + OrderFilterModel orderFilter) + { + var shouldHide = await HideFromOtherUsers(); + IQueryable allRequests; + if (shouldHide.Hide) + { + allRequests = + MusicRepository.GetWithUser(shouldHide + .UserId); //.Skip(position).Take(count).OrderByDescending(x => x.ReleaseDate).ToListAsync(); + } + else + { + allRequests = + MusicRepository + .GetWithUser(); //.Skip(position).Take(count).OrderByDescending(x => x.ReleaseDate).ToListAsync(); + } + + switch (orderFilter.AvailabilityFilter) + { + case FilterType.None: + break; + case FilterType.Available: + allRequests = allRequests.Where(x => x.Available); + break; + case FilterType.NotAvailable: + allRequests = allRequests.Where(x => !x.Available); + break; + default: + throw new ArgumentOutOfRangeException(); + } + + switch (orderFilter.StatusFilter) + { + case FilterType.None: + break; + case FilterType.Approved: + allRequests = allRequests.Where(x => x.Approved); + break; + case FilterType.Processing: + allRequests = allRequests.Where(x => x.Approved && !x.Available); + break; + case FilterType.PendingApproval: + allRequests = allRequests.Where(x => !x.Approved && !x.Available && !(x.Denied ?? false)); + break; + default: + throw new ArgumentOutOfRangeException(); + } + + var total = allRequests.Count(); + + var requests = await (OrderAlbums(allRequests, orderFilter.OrderType)).Skip(position).Take(count) + .ToListAsync(); + + requests.ForEach(async x => + { + await CheckForSubscription(shouldHide, x); + }); + return new RequestsViewModel + { + Collection = requests, + Total = total + }; + } + + private IQueryable OrderAlbums(IQueryable allRequests, OrderType type) + { + switch (type) + { + case OrderType.RequestedDateAsc: + return allRequests.OrderBy(x => x.RequestedDate); + case OrderType.RequestedDateDesc: + return allRequests.OrderByDescending(x => x.RequestedDate); + case OrderType.TitleAsc: + return allRequests.OrderBy(x => x.Title); + case OrderType.TitleDesc: + return allRequests.OrderByDescending(x => x.Title); + default: + throw new ArgumentOutOfRangeException(nameof(type), type, null); + } + } + + public async Task GetTotal() + { + var shouldHide = await HideFromOtherUsers(); + if (shouldHide.Hide) + { + return await MusicRepository.GetWithUser(shouldHide.UserId).CountAsync(); + } + else + { + return await MusicRepository.GetWithUser().CountAsync(); + } + } + + /// + /// Gets the requests. + /// + /// + public async Task> GetRequests() + { + var shouldHide = await HideFromOtherUsers(); + List allRequests; + if (shouldHide.Hide) + { + allRequests = await MusicRepository.GetWithUser(shouldHide.UserId).ToListAsync(); + } + else + { + allRequests = await MusicRepository.GetWithUser().ToListAsync(); + } + + allRequests.ForEach(async x => + { + await CheckForSubscription(shouldHide, x); + }); + return allRequests; + } + + private async Task CheckForSubscription(HideResult shouldHide, AlbumRequest x) + { + if (shouldHide.UserId == x.RequestedUserId) + { + x.ShowSubscribe = false; + } + else + { + x.ShowSubscribe = true; + var sub = await _subscriptionRepository.GetAll().FirstOrDefaultAsync(s => + s.UserId == shouldHide.UserId && s.RequestId == x.Id && s.RequestType == RequestType.Album); + x.Subscribed = sub != null; + } + } + + /// + /// Searches the album request. + /// + /// The search. + /// + public async Task> SearchAlbumRequest(string search) + { + var shouldHide = await HideFromOtherUsers(); + List allRequests; + if (shouldHide.Hide) + { + allRequests = await MusicRepository.GetWithUser(shouldHide.UserId).ToListAsync(); + } + else + { + allRequests = await MusicRepository.GetWithUser().ToListAsync(); + } + + var results = allRequests.Where(x => x.Title.Contains(search, CompareOptions.IgnoreCase)).ToList(); + results.ForEach(async x => + { + await CheckForSubscription(shouldHide, x); + }); + return results; + } + + public async Task ApproveAlbumById(int requestId) + { + var request = await MusicRepository.Find(requestId); + return await ApproveAlbum(request); + } + + public async Task DenyAlbumById(int modelId, string reason) + { + var request = await MusicRepository.Find(modelId); + if (request == null) + { + return new RequestEngineResult + { + ErrorMessage = "Request does not exist" + }; + } + + request.Denied = true; + request.DeniedReason = reason; + // We are denying a request + NotificationHelper.Notify(request, NotificationType.RequestDeclined); + await MusicRepository.Update(request); + + return new RequestEngineResult + { + Message = "Request successfully deleted", + }; + } + + public async Task ApproveAlbum(AlbumRequest request) + { + if (request == null) + { + return new RequestEngineResult + { + ErrorMessage = "Request does not exist" + }; + } + + request.MarkedAsApproved = DateTime.Now; + request.Approved = true; + request.Denied = false; + await MusicRepository.Update(request); + + + var canNotify = await RunSpecificRule(request, SpecificRules.CanSendNotification); + if (canNotify.Success) + { + NotificationHelper.Notify(request, NotificationType.RequestApproved); + } + + if (request.Approved) + { + var result = await _musicSender.Send(request); + if (result.Success && result.Sent) + { + return new RequestEngineResult + { + Result = true + }; + } + + if (!result.Success) + { + Logger.LogWarning("Tried auto sending album but failed. Message: {0}", result.Message); + return new RequestEngineResult + { + Message = result.Message, + ErrorMessage = result.Message, + Result = false + }; + } + + // If there are no providers then it's successful but movie has not been sent + } + + return new RequestEngineResult + { + Result = true + }; + } + + /// + /// Removes the Album request. + /// + /// The request identifier. + /// + public async Task RemoveAlbumRequest(int requestId) + { + var request = await MusicRepository.GetAll().FirstOrDefaultAsync(x => x.Id == requestId); + await MusicRepository.Delete(request); + } + + public async Task UserHasRequest(string userId) + { + return await MusicRepository.GetAll().AnyAsync(x => x.RequestedUserId == userId); + } + + public async Task MarkUnavailable(int modelId) + { + var request = await MusicRepository.Find(modelId); + if (request == null) + { + return new RequestEngineResult + { + ErrorMessage = "Request does not exist" + }; + } + + request.Available = false; + await MusicRepository.Update(request); + + return new RequestEngineResult + { + Message = "Request is now unavailable", + Result = true + }; + } + public async Task GetRemainingRequests(OmbiUser user) + { + if (user == null) + { + user = await GetUser(); + + // If user is still null after attempting to get the logged in user, return null. + if (user == null) + { + return null; + } + } + + int limit = user.MusicRequestLimit ?? 0; + + if (limit <= 0) + { + return new RequestQuotaCountModel() + { + HasLimit = false, + Limit = 0, + Remaining = 0, + NextRequest = DateTime.Now, + }; + } + + IQueryable log = _requestLog.GetAll().Where(x => x.UserId == user.Id && x.RequestType == RequestType.Album); + + int count = limit - await log.CountAsync(x => x.RequestDate >= DateTime.UtcNow.AddDays(-7)); + + DateTime oldestRequestedAt = await log.Where(x => x.RequestDate >= DateTime.UtcNow.AddDays(-7)) + .OrderBy(x => x.RequestDate) + .Select(x => x.RequestDate) + .FirstOrDefaultAsync(); + + return new RequestQuotaCountModel() + { + HasLimit = true, + Limit = limit, + Remaining = count, + NextRequest = DateTime.SpecifyKind(oldestRequestedAt.AddDays(7), DateTimeKind.Utc), + }; + } + + public async Task MarkAvailable(int modelId) + { + var request = await MusicRepository.Find(modelId); + if (request == null) + { + return new RequestEngineResult + { + ErrorMessage = "Request does not exist" + }; + } + + request.Available = true; + request.MarkedAsAvailable = DateTime.Now; + NotificationHelper.Notify(request, NotificationType.RequestAvailable); + await MusicRepository.Update(request); + + return new RequestEngineResult + { + Message = "Request is now available", + Result = true + }; + } + + private async Task AddAlbumRequest(AlbumRequest model) + { + await MusicRepository.Add(model); + + var result = await RunSpecificRule(model, SpecificRules.CanSendNotification); + if (result.Success) + { + NotificationHelper.NewRequest(model); + } + + await _requestLog.Add(new RequestLog + { + UserId = (await GetUser()).Id, + RequestDate = DateTime.UtcNow, + RequestId = model.Id, + RequestType = RequestType.Album, + }); + + return new RequestEngineResult { Result = true, Message = $"{model.Title} has been successfully added!", RequestId = model.Id }; + } + + + } +} \ No newline at end of file diff --git a/src/Ombi.Core/Engine/MusicSearchEngine.cs b/src/Ombi.Core/Engine/MusicSearchEngine.cs new file mode 100644 index 000000000..a9af03ecf --- /dev/null +++ b/src/Ombi.Core/Engine/MusicSearchEngine.cs @@ -0,0 +1,274 @@ +using System; +using AutoMapper; +using Microsoft.Extensions.Logging; +using Ombi.Api.TheMovieDb; +using Ombi.Api.TheMovieDb.Models; +using Ombi.Core.Models.Requests; +using Ombi.Core.Models.Search; +using System.Collections.Generic; +using System.Linq; +using System.Security.Principal; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Ombi.Core.Rule.Interfaces; +using Microsoft.Extensions.Caching.Memory; +using Ombi.Api.Lidarr; +using Ombi.Api.Lidarr.Models; +using Ombi.Core.Authentication; +using Ombi.Core.Settings; +using Ombi.Helpers; +using Ombi.Core.Helpers; +using Ombi.Settings.Settings.Models; +using Ombi.Settings.Settings.Models.External; +using Ombi.Store.Entities; +using Ombi.Store.Repository; + +namespace Ombi.Core.Engine +{ + public class MusicSearchEngine : BaseMediaEngine, IMusicSearchEngine + { + public MusicSearchEngine(IPrincipal identity, IRequestServiceMain service, ILidarrApi lidarrApi, IMapper mapper, + ILogger logger, IRuleEvaluator r, OmbiUserManager um, ICacheService mem, ISettingsService s, IRepository sub, + ISettingsService lidarrSettings) + : base(identity, service, r, um, mem, s, sub) + { + _lidarrApi = lidarrApi; + _lidarrSettings = lidarrSettings; + Mapper = mapper; + Logger = logger; + } + + private readonly ILidarrApi _lidarrApi; + private IMapper Mapper { get; } + private ILogger Logger { get; } + private readonly ISettingsService _lidarrSettings; + + /// + /// Searches the specified album. + /// + /// The search. + /// + public async Task> SearchAlbum(string search) + { + var settings = await GetSettings(); + var result = await _lidarrApi.AlbumLookup(search, settings.ApiKey, settings.FullUri); + var vm = new List(); + foreach (var r in result) + { + vm.Add(await MapIntoAlbumVm(r, settings)); + } + + return vm; + } + + public async Task GetAlbumInformation(string foreignAlbumId) + { + var settings = await GetSettings(); + var result = await _lidarrApi.AlbumInformation(foreignAlbumId, settings.ApiKey, settings.FullUri); + + + var vm = await MapIntoAlbumVm(result, settings); + + + return vm; + } + + /// + /// Searches the specified artist + /// + /// The search. + /// + public async Task> SearchArtist(string search) + { + var settings = await GetSettings(); + var result = await _lidarrApi.ArtistLookup(search, settings.ApiKey, settings.FullUri); + + var vm = new List(); + foreach (var r in result) + { + vm.Add(await MapIntoArtistVm(r)); + } + + return vm; + } + + /// + /// Returns all albums by the specified artist + /// + /// + /// + public async Task> GetArtistAlbums(string foreignArtistId) + { + var settings = await GetSettings(); + var result = await _lidarrApi.GetAlbumsByArtist(foreignArtistId); + // We do not want any Singles (This will include EP's) + var albumsOnly = + result.Albums.Where(x => !x.Type.Equals("Single", StringComparison.InvariantCultureIgnoreCase)); + var vm = new List(); + foreach (var album in albumsOnly) + { + vm.Add(await MapIntoAlbumVm(album, result.Id, result.ArtistName, settings)); + } + return vm; + } + + /// + /// Returns the artist that produced the album + /// + /// + /// + public async Task GetAlbumArtist(string foreignArtistId) + { + var settings = await GetSettings(); + return await _lidarrApi.GetArtistByForeignId(foreignArtistId, settings.ApiKey, settings.FullUri); + } + + public async Task GetArtist(int artistId) + { + var settings = await GetSettings(); + return await _lidarrApi.GetArtist(artistId, settings.ApiKey, settings.FullUri); + } + + private async Task MapIntoArtistVm(ArtistLookup a) + { + var vm = new SearchArtistViewModel + { + ArtistName = a.artistName, + ArtistType = a.artistType, + Banner = a.images?.FirstOrDefault(x => x.coverType.Equals("banner"))?.url, + Logo = a.images?.FirstOrDefault(x => x.coverType.Equals("logo"))?.url, + CleanName = a.cleanName, + Disambiguation = a.disambiguation, + ForignArtistId = a.foreignArtistId, + Links = a.links, + Overview = a.overview, + }; + + var poster = a.images?.FirstOrDefault(x => x.coverType.Equals("poaster")); + if (poster == null) + { + vm.Poster = a.remotePoster; + } + + + await Rules.StartSpecificRules(vm, SpecificRules.LidarrArtist); + + return vm; + } + + + // TODO + private async Task MapIntoAlbumVm(AlbumByForeignId a, LidarrSettings settings) + { + var vm = new SearchAlbumViewModel + { + ForeignAlbumId = a.foreignAlbumId, + Monitored = a.monitored, + Rating = a.ratings?.value ?? 0m, + ReleaseDate = a.releaseDate, + Title = a.title, + Disk = a.images?.FirstOrDefault(x => x.coverType.Equals("disc"))?.url?.ToHttpsUrl(), + Genres = a.genres, + AlbumType = a.albumType, + ArtistName = a.artist.artistName, + ForeignArtistId = a.artist.foreignArtistId, + }; + if (a.artistId > 0) + { + //TODO THEY HAVE FIXED THIS IN DEV + // The JSON is different for some stupid reason + // Need to lookup the artist now and all the images -.-" + var artist = await _lidarrApi.GetArtist(a.artistId, settings.ApiKey, settings.FullUri); + vm.ArtistName = artist.artistName; + vm.ForeignArtistId = artist.foreignArtistId; + } + else + { + //vm.ForeignArtistId = a.artistId?.foreignArtistId; + //vm.ArtistName = a.artist?.artistName; + } + + vm.Cover = a.images?.FirstOrDefault(x => x.coverType.Equals("cover"))?.url?.ToHttpsUrl(); + + await Rules.StartSpecificRules(vm, SpecificRules.LidarrAlbum); + + await RunSearchRules(vm); + + return vm; + } + + private async Task MapIntoAlbumVm(AlbumLookup a, LidarrSettings settings) + { + var vm = new SearchAlbumViewModel + { + ForeignAlbumId = a.foreignAlbumId, + Monitored = a.monitored, + Rating = a.ratings?.value ?? 0m, + ReleaseDate = a.releaseDate, + Title = a.title, + Disk = a.images?.FirstOrDefault(x => x.coverType.Equals("disc"))?.url?.ToHttpsUrl(), + Genres = a.genres + }; + if (a.artistId > 0) + { + //TODO THEY HAVE FIXED THIS IN DEV + // The JSON is different for some stupid reason + // Need to lookup the artist now and all the images -.-" + var artist = await _lidarrApi.GetArtist(a.artistId, settings.ApiKey, settings.FullUri); + vm.ArtistName = artist.artistName; + vm.ForeignArtistId = artist.foreignArtistId; + } + else + { + vm.ForeignArtistId = a.artist?.foreignArtistId; + vm.ArtistName = a.artist?.artistName; + } + + vm.Cover = a.images?.FirstOrDefault(x => x.coverType.Equals("cover"))?.url?.ToHttpsUrl(); + if (vm.Cover.IsNullOrEmpty()) + { + vm.Cover = a.remoteCover; + } + + await Rules.StartSpecificRules(vm, SpecificRules.LidarrAlbum); + + await RunSearchRules(vm); + + return vm; + } + + private async Task MapIntoAlbumVm(Album a, string artistId, string artistName, LidarrSettings settings) + { + var fullAlbum = await _lidarrApi.GetAlbumByForeignId(a.Id, settings.ApiKey, settings.FullUri); + var vm = new SearchAlbumViewModel + { + ForeignAlbumId = a.Id, + Monitored = fullAlbum.monitored, + Rating = fullAlbum.ratings?.value ?? 0m, + ReleaseDate = fullAlbum.releaseDate, + Title = a.Title, + Disk = fullAlbum.images?.FirstOrDefault(x => x.coverType.Equals("disc"))?.url, + ForeignArtistId = artistId, + ArtistName = artistName, + Cover = fullAlbum.images?.FirstOrDefault(x => x.coverType.Equals("cover"))?.url + }; + + if (vm.Cover.IsNullOrEmpty()) + { + vm.Cover = fullAlbum.remoteCover; + } + + await Rules.StartSpecificRules(vm, SpecificRules.LidarrAlbum); + + await RunSearchRules(vm); + + return vm; + } + + private LidarrSettings _settings; + private async Task GetSettings() + { + return _settings ?? (_settings = await _lidarrSettings.GetSettingsAsync()); + } + } +} \ No newline at end of file diff --git a/src/Ombi.Core/Engine/RequestEngineResult.cs b/src/Ombi.Core/Engine/RequestEngineResult.cs index 1dc78d4b4..08f6d5129 100644 --- a/src/Ombi.Core/Engine/RequestEngineResult.cs +++ b/src/Ombi.Core/Engine/RequestEngineResult.cs @@ -6,5 +6,6 @@ public string Message { get; set; } public bool IsError => !string.IsNullOrEmpty(ErrorMessage); public string ErrorMessage { get; set; } + public int RequestId { get; set; } } } \ No newline at end of file diff --git a/src/Ombi.Core/Engine/TreeNode.cs b/src/Ombi.Core/Engine/TreeNode.cs deleted file mode 100644 index 14f2f4cb0..000000000 --- a/src/Ombi.Core/Engine/TreeNode.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System.Collections.Generic; - -namespace Ombi.Core.Engine -{ - - public class TreeNode - { - public string Label { get; set; } - public T Data { get; set; } - public List> Children { get; set; } - public bool Leaf { get; set; } - public bool Expanded { get; set; } - } - - public class TreeNode - { - public string Label { get; set; } - public T Data { get; set; } - public List> Children { get; set; } - public bool Leaf { get; set; } - public bool Expanded { get; set; } - } -} \ No newline at end of file diff --git a/src/Ombi.Core/Engine/TvRequestEngine.cs b/src/Ombi.Core/Engine/TvRequestEngine.cs index e8fc65a26..796e2fe6a 100644 --- a/src/Ombi.Core/Engine/TvRequestEngine.cs +++ b/src/Ombi.Core/Engine/TvRequestEngine.cs @@ -23,6 +23,7 @@ using Ombi.Core.Settings; using Ombi.Settings.Settings.Models; using Ombi.Store.Entities.Requests; using Ombi.Store.Repository; +using Ombi.Core.Models; namespace Ombi.Core.Engine { @@ -30,14 +31,13 @@ namespace Ombi.Core.Engine { public TvRequestEngine(ITvMazeApi tvApi, IMovieDbApi movApi, IRequestServiceMain requestService, IPrincipal user, INotificationHelper helper, IRuleEvaluator rule, OmbiUserManager manager, - ITvSender sender, IAuditRepository audit, IRepository rl, ISettingsService settings, ICacheService cache, + ITvSender sender, IRepository rl, ISettingsService settings, ICacheService cache, IRepository sub) : base(user, requestService, rule, manager, cache, settings, sub) { TvApi = tvApi; MovieDbApi = movApi; NotificationHelper = helper; TvSender = sender; - Audit = audit; _requestLog = rl; } @@ -45,7 +45,6 @@ namespace Ombi.Core.Engine private ITvMazeApi TvApi { get; } private IMovieDbApi MovieDbApi { get; } private ITvSender TvSender { get; } - private IAuditRepository Audit { get; } private readonly IRepository _requestLog; public async Task RequestTvShow(TvRequestViewModel tv) @@ -83,8 +82,6 @@ namespace Ombi.Core.Engine } } - await Audit.Record(AuditType.Added, AuditArea.TvRequest, $"Added Request {tvBuilder.ChildRequest.Title}", Username); - var existingRequest = await TvRepository.Get().FirstOrDefaultAsync(x => x.TvDbId == tv.TvDbId); if (existingRequest != null) { @@ -115,6 +112,7 @@ namespace Ombi.Core.Engine } // Remove the ID since this is a new child + // This was a TVDBID for the request rules to run tvBuilder.ChildRequest.Id = 0; if (!tvBuilder.ChildRequest.SeasonRequests.Any()) { @@ -143,7 +141,7 @@ namespace Ombi.Core.Engine .Include(x => x.ChildRequests) .ThenInclude(x => x.SeasonRequests) .ThenInclude(x => x.Episodes) - .OrderByDescending(x => x.ChildRequests.Max(y => y.RequestedDate)) + .OrderByDescending(x => x.ChildRequests.Select(y => y.RequestedDate).FirstOrDefault()) .Skip(position).Take(count).ToListAsync(); // Filter out children @@ -156,8 +154,9 @@ namespace Ombi.Core.Engine .Include(x => x.ChildRequests) .ThenInclude(x => x.SeasonRequests) .ThenInclude(x => x.Episodes) - .OrderByDescending(x => x.ChildRequests.Max(y => y.RequestedDate)) + .OrderByDescending(x => x.ChildRequests.Select(y => y.RequestedDate).FirstOrDefault()) .Skip(position).Take(count).ToListAsync(); + } allRequests.ForEach(async r => { await CheckForSubscription(shouldHide, r); }); @@ -171,24 +170,30 @@ namespace Ombi.Core.Engine public async Task> GetRequestsLite(int count, int position, OrderFilterModel type) { var shouldHide = await HideFromOtherUsers(); - List allRequests; + List allRequests = null; if (shouldHide.Hide) { - allRequests = await TvRepository.GetLite(shouldHide.UserId) - .OrderByDescending(x => x.ChildRequests.Max(y => y.RequestedDate)) - .Skip(position).Take(count).ToListAsync(); + var tv = TvRepository.GetLite(shouldHide.UserId); + if (tv.Any() && tv.Select(x => x.ChildRequests).Any()) + { + allRequests = await tv.OrderByDescending(x => x.ChildRequests.Select(y => y.RequestedDate).FirstOrDefault()).Skip(position).Take(count).ToListAsync(); + } // Filter out children - FilterChildren(allRequests, shouldHide); } else { - allRequests = await TvRepository.GetLite() - .OrderByDescending(x => x.ChildRequests.Max(y => y.RequestedDate)) - .Skip(position).Take(count).ToListAsync(); + var tv = TvRepository.GetLite(); + if (tv.Any() && tv.Select(x => x.ChildRequests).Any()) + { + allRequests = await tv.OrderByDescending(x => x.ChildRequests.Select(y => y.RequestedDate).FirstOrDefault()).Skip(position).Take(count).ToListAsync(); + } + } + if (allRequests == null) + { + return new RequestsViewModel(); } - allRequests.ForEach(async r => { await CheckForSubscription(shouldHide, r); }); return new RequestsViewModel @@ -196,38 +201,6 @@ namespace Ombi.Core.Engine Collection = allRequests }; } - - public async Task>>> GetRequestsTreeNode(int count, int position) - { - var shouldHide = await HideFromOtherUsers(); - List allRequests; - if (shouldHide.Hide) - { - allRequests = await TvRepository.Get(shouldHide.UserId) - .Include(x => x.ChildRequests) - .ThenInclude(x => x.SeasonRequests) - .ThenInclude(x => x.Episodes) - .Where(x => x.ChildRequests.Any()) - .OrderByDescending(x => x.ChildRequests.Max(y => y.RequestedDate)) - .Skip(position).Take(count).ToListAsync(); - - FilterChildren(allRequests, shouldHide); - } - else - { - allRequests = await TvRepository.Get() - .Include(x => x.ChildRequests) - .ThenInclude(x => x.SeasonRequests) - .ThenInclude(x => x.Episodes) - .Where(x => x.ChildRequests.Any()) - .OrderByDescending(x => x.ChildRequests.Max(y => y.RequestedDate)) - .Skip(position).Take(count).ToListAsync(); - } - - allRequests.ForEach(async r => { await CheckForSubscription(shouldHide, r); }); - return ParseIntoTreeNode(allRequests); - } - public async Task> GetRequests() { var shouldHide = await HideFromOtherUsers(); @@ -288,6 +261,10 @@ namespace Ombi.Core.Engine private static void FilterChildren(IEnumerable allRequests, HideResult shouldHide) { + if (allRequests == null) + { + return; + } // Filter out children foreach (var t in allRequests) { @@ -350,26 +327,26 @@ namespace Ombi.Core.Engine return results; } - public async Task>>> SearchTvRequestTree(string search) + public async Task UpdateRootPath(int requestId, int rootPath) { - var shouldHide = await HideFromOtherUsers(); - IQueryable allRequests; - if (shouldHide.Hide) - { - allRequests = TvRepository.Get(shouldHide.UserId); - } - else - { - allRequests = TvRepository.Get(); - } - var results = await allRequests.Where(x => x.Title.Contains(search, CompareOptions.IgnoreCase)).ToListAsync(); - results.ForEach(async r => { await CheckForSubscription(shouldHide, r); }); - return ParseIntoTreeNode(results); + var allRequests = TvRepository.Get(); + var results = await allRequests.FirstOrDefaultAsync(x => x.Id == requestId); + results.RootFolder = rootPath; + + await TvRepository.Update(results); + } + + public async Task UpdateQualityProfile(int requestId, int profileId) + { + var allRequests = TvRepository.Get(); + var results = await allRequests.FirstOrDefaultAsync(x => x.Id == requestId); + results.QualityOverride = profileId; + + await TvRepository.Update(results); } public async Task UpdateTvRequest(TvRequests request) { - await Audit.Record(AuditType.Updated, AuditArea.TvRequest, $"Updated Request {request.Title}", Username); var allRequests = TvRepository.Get(); var results = await allRequests.FirstOrDefaultAsync(x => x.Id == request.Id); @@ -403,6 +380,7 @@ namespace Ombi.Core.Engine foreach (var ep in s.Episodes) { ep.Approved = true; + ep.Requested = true; } } @@ -411,7 +389,6 @@ namespace Ombi.Core.Engine if (request.Approved) { NotificationHelper.Notify(request, NotificationType.RequestApproved); - await Audit.Record(AuditType.Approved, AuditArea.TvRequest, $"Approved Request {request.Title}", Username); // Autosend await TvSender.Send(request); } @@ -421,7 +398,7 @@ namespace Ombi.Core.Engine }; } - public async Task DenyChildRequest(int requestId) + public async Task DenyChildRequest(int requestId, string reason) { var request = await TvRepository.GetChild().FirstOrDefaultAsync(x => x.Id == requestId); if (request == null) @@ -432,6 +409,7 @@ namespace Ombi.Core.Engine }; } request.Denied = true; + request.DeniedReason = reason; await TvRepository.UpdateChild(request); NotificationHelper.Notify(request, NotificationType.RequestDeclined); return new RequestEngineResult @@ -442,9 +420,7 @@ namespace Ombi.Core.Engine public async Task UpdateChildRequest(ChildRequests request) { - await Audit.Record(AuditType.Updated, AuditArea.TvRequest, $"Updated Request {request.Title}", Username); - - await TvRepository.UpdateChild(request); + await TvRepository.UpdateChild(request); return request; } @@ -462,16 +438,14 @@ namespace Ombi.Core.Engine // Delete the parent TvRepository.Db.TvRequests.Remove(parent); } - await Audit.Record(AuditType.Deleted, AuditArea.TvRequest, $"Deleting Request {request.Title}", Username); - + await TvRepository.Db.SaveChangesAsync(); } public async Task RemoveTvRequest(int requestId) { var request = await TvRepository.Get().FirstOrDefaultAsync(x => x.Id == requestId); - await Audit.Record(AuditType.Deleted, AuditArea.TvRequest, $"Deleting Request {request.Title}", Username); - await TvRepository.Delete(request); + await TvRepository.Delete(request); } public async Task UserHasRequest(string userId) @@ -516,6 +490,7 @@ namespace Ombi.Core.Engine }; } request.Available = true; + request.MarkedAsAvailable = DateTime.Now; foreach (var season in request.SeasonRequests) { foreach (var e in season.Episodes) @@ -585,29 +560,7 @@ namespace Ombi.Core.Engine return await AfterRequest(model.ChildRequests.FirstOrDefault()); } - private static List>> ParseIntoTreeNode(IEnumerable result) - { - var node = new List>>(); - - foreach (var value in result) - { - node.Add(new TreeNode> - { - Data = value, - Children = new List>> - { - new TreeNode> - { - Data = SortEpisodes(value.ChildRequests), - Leaf = true - } - } - }); - } - return node; - } - - private static List SortEpisodes(List items) + private static List SortEpisodes(List items) { foreach (var value in items) { @@ -628,6 +581,15 @@ namespace Ombi.Core.Engine NotificationHelper.NewRequest(model); } + await _requestLog.Add(new RequestLog + { + UserId = (await GetUser()).Id, + RequestDate = DateTime.UtcNow, + RequestId = model.Id, + RequestType = RequestType.TvShow, + EpisodeCount = model.SeasonRequests.Select(m => m.Episodes.Count).Sum(), + }); + if (model.Approved) { // Autosend @@ -635,23 +597,67 @@ namespace Ombi.Core.Engine var result = await TvSender.Send(model); if (result.Success) { - return new RequestEngineResult { Result = true }; + return new RequestEngineResult { Result = true, RequestId = model.Id}; } return new RequestEngineResult { - ErrorMessage = result.Message + ErrorMessage = result.Message, + RequestId = model.Id }; } - await _requestLog.Add(new RequestLog - { - UserId = (await GetUser()).Id, - RequestDate = DateTime.UtcNow, - RequestId = model.Id, - RequestType = RequestType.TvShow, - }); + return new RequestEngineResult { Result = true, RequestId = model.Id }; + } - return new RequestEngineResult { Result = true }; + public async Task GetRemainingRequests(OmbiUser user) + { + if (user == null) + { + user = await GetUser(); + + // If user is still null after attempting to get the logged in user, return null. + if (user == null) + { + return null; + } + } + + int limit = user.EpisodeRequestLimit ?? 0; + + if (limit <= 0) + { + return new RequestQuotaCountModel() + { + HasLimit = false, + Limit = 0, + Remaining = 0, + NextRequest = DateTime.Now, + }; + } + + IQueryable log = _requestLog.GetAll() + .Where(x => x.UserId == user.Id + && x.RequestType == RequestType.TvShow + && x.RequestDate >= DateTime.UtcNow.AddDays(-7)); + + // Needed, due to a bug which would cause all episode counts to be 0 + int zeroEpisodeCount = await log.Where(x => x.EpisodeCount == 0).Select(x => x.EpisodeCount).CountAsync(); + + int episodeCount = await log.Where(x => x.EpisodeCount != 0).Select(x => x.EpisodeCount).SumAsync(); + + int count = limit - (zeroEpisodeCount + episodeCount); + + DateTime oldestRequestedAt = await log.OrderBy(x => x.RequestDate) + .Select(x => x.RequestDate) + .FirstOrDefaultAsync(); + + return new RequestQuotaCountModel() + { + HasLimit = true, + Limit = limit, + Remaining = count, + NextRequest = DateTime.SpecifyKind(oldestRequestedAt.AddDays(7), DateTimeKind.Utc), + }; } } } \ No newline at end of file diff --git a/src/Ombi.Core/Engine/TvSearchEngine.cs b/src/Ombi.Core/Engine/TvSearchEngine.cs index bc5a2e984..3a1fead18 100644 --- a/src/Ombi.Core/Engine/TvSearchEngine.cs +++ b/src/Ombi.Core/Engine/TvSearchEngine.cs @@ -40,8 +40,8 @@ namespace Ombi.Core.Engine EmbyContentRepo = embyRepo; } - private ITvMazeApi TvMazeApi { get; } - private IMapper Mapper { get; } + protected ITvMazeApi TvMazeApi { get; } + protected IMapper Mapper { get; } private ISettingsService PlexSettings { get; } private ISettingsService EmbySettings { get; } private IPlexContentRepository PlexContentRepo { get; } @@ -54,16 +54,20 @@ namespace Ombi.Core.Engine if (searchResult != null) { - return await ProcessResults(searchResult); + var retVal = new List(); + foreach (var tvMazeSearch in searchResult) + { + if (tvMazeSearch.show.externals == null || !(tvMazeSearch.show.externals?.thetvdb.HasValue ?? false)) + { + continue; + } + retVal.Add(ProcessResult(tvMazeSearch)); + } + return retVal; } return null; } - public async Task>> SearchTreeNode(string searchTerm) - { - var result = await Search(searchTerm); - return result.Select(ParseIntoTreeNode).ToList(); - } public async Task GetShowInformation(int tvdbid) { var show = await TvMazeApi.ShowLookupByTheTvDbId(tvdbid); @@ -95,7 +99,7 @@ namespace Ombi.Core.Engine { Url = e.url, Title = e.name, - AirDate = DateTime.Parse(e.airstamp ?? DateTime.MinValue.ToString()), + AirDate = e.airstamp.HasValue() ? DateTime.Parse(e.airstamp) : DateTime.MinValue, EpisodeNumber = e.number, }); @@ -108,7 +112,7 @@ namespace Ombi.Core.Engine { Url = e.url, Title = e.name, - AirDate = DateTime.Parse(e.airstamp ?? DateTime.MinValue.ToString()), + AirDate = e.airstamp.HasValue() ? DateTime.Parse(e.airstamp) : DateTime.MinValue, EpisodeNumber = e.number, }); } @@ -116,94 +120,50 @@ namespace Ombi.Core.Engine return await ProcessResult(mapped); } - public async Task> GetShowInformationTreeNode(int tvdbid) - { - var result = await GetShowInformation(tvdbid); - return ParseIntoTreeNode(result); - } - - public async Task>> PopularTree() - { - var result = await Cache.GetOrAdd(CacheKeys.PopularTv, async () => await TraktApi.GetPopularShows(), DateTime.Now.AddHours(12)); - var processed = await ProcessResults(result); - return processed.Select(ParseIntoTreeNode).ToList(); - } - public async Task> Popular() { var result = await Cache.GetOrAdd(CacheKeys.PopularTv, async () => await TraktApi.GetPopularShows(), DateTime.Now.AddHours(12)); - var processed = await ProcessResults(result); + var processed = ProcessResults(result); return processed; } - public async Task>> AnticipatedTree() - { - var result = await Cache.GetOrAdd(CacheKeys.AnticipatedTv, async () => await TraktApi.GetAnticipatedShows(), DateTime.Now.AddHours(12)); - var processed = await ProcessResults(result); - return processed.Select(ParseIntoTreeNode).ToList(); - } public async Task> Anticipated() { var result = await Cache.GetOrAdd(CacheKeys.AnticipatedTv, async () => await TraktApi.GetAnticipatedShows(), DateTime.Now.AddHours(12)); - var processed = await ProcessResults(result); + var processed = ProcessResults(result); return processed; } - public async Task>> MostWatchesTree() - { - var result = await Cache.GetOrAdd(CacheKeys.MostWatchesTv, async () => await TraktApi.GetMostWatchesShows(), DateTime.Now.AddHours(12)); - var processed = await ProcessResults(result); - return processed.Select(ParseIntoTreeNode).ToList(); - } public async Task> MostWatches() { var result = await Cache.GetOrAdd(CacheKeys.MostWatchesTv, async () => await TraktApi.GetMostWatchesShows(), DateTime.Now.AddHours(12)); - var processed = await ProcessResults(result); + var processed = ProcessResults(result); return processed; } - public async Task>> TrendingTree() - { - var result = await Cache.GetOrAdd(CacheKeys.TrendingTv, async () => await TraktApi.GetTrendingShows(), DateTime.Now.AddHours(12)); - var processed = await ProcessResults(result); - return processed.Select(ParseIntoTreeNode).ToList(); - } - public async Task> Trending() { var result = await Cache.GetOrAdd(CacheKeys.TrendingTv, async () => await TraktApi.GetTrendingShows(), DateTime.Now.AddHours(12)); - var processed = await ProcessResults(result); + var processed = ProcessResults(result); return processed; } - private static TreeNode ParseIntoTreeNode(SearchTvShowViewModel result) - { - return new TreeNode - { - Data = result, - Children = new List> - { - new TreeNode - { - Data = result, Leaf = true - } - }, - Leaf = false - }; - } - - private async Task> ProcessResults(IEnumerable items) + protected IEnumerable ProcessResults(IEnumerable items) { var retVal = new List(); foreach (var tvMazeSearch in items) { - var viewT = Mapper.Map(tvMazeSearch); - retVal.Add(await ProcessResult(viewT)); + retVal.Add(ProcessResult(tvMazeSearch)); } return retVal; } + protected SearchTvShowViewModel ProcessResult(T tvMazeSearch) + { + return Mapper.Map(tvMazeSearch); + } + private async Task ProcessResult(SearchTvShowViewModel item) { item.TheTvDbId = item.Id.ToString(); diff --git a/src/Ombi.Core/Engine/UserStatsEngine.cs b/src/Ombi.Core/Engine/UserStatsEngine.cs new file mode 100644 index 000000000..06ab65f92 --- /dev/null +++ b/src/Ombi.Core/Engine/UserStatsEngine.cs @@ -0,0 +1,77 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Ombi.Core.Authentication; +using Ombi.Store.Entities; +using Ombi.Store.Repository.Requests; + +namespace Ombi.Core.Engine +{ + public class UserStatsEngine : IUserStatsEngine + { + public UserStatsEngine(OmbiUserManager um, IMovieRequestRepository movieRequest, ITvRequestRepository tvRequest) + { + _userManager = um; + _movieRequest = movieRequest; + _tvRequest = tvRequest; + } + + private readonly OmbiUserManager _userManager; + private readonly IMovieRequestRepository _movieRequest; + private readonly ITvRequestRepository _tvRequest; + + public async Task GetSummary(SummaryRequest request) + { + // get all movie requests + var movies = _movieRequest.GetWithUser(); + var filteredMovies = movies.Where(x => x.RequestedDate >= request.From && x.RequestedDate <= request.To); + var tv = _tvRequest.GetLite(); + var children = tv.SelectMany(x => + x.ChildRequests.Where(c => c.RequestedDate >= request.From && c.RequestedDate <= request.To)); + + var moviesCount = filteredMovies.CountAsync(); + var childrenCount = children.CountAsync(); + var availableMovies = + movies.Select(x => x.MarkedAsAvailable >= request.From && x.MarkedAsAvailable <= request.To).CountAsync(); + var availableChildren = tv.SelectMany(x => + x.ChildRequests.Where(c => c.MarkedAsAvailable >= request.From && c.MarkedAsAvailable <= request.To)).CountAsync(); + + var userMovie = filteredMovies.GroupBy(x => x.RequestedUserId).OrderBy(x => x.Key).FirstOrDefaultAsync(); + var userTv = children.GroupBy(x => x.RequestedUserId).OrderBy(x => x.Key).FirstOrDefaultAsync(); + + + return new UserStatsSummary + { + TotalMovieRequests = await moviesCount, + TotalTvRequests = await childrenCount, + CompletedRequestsTv = await availableChildren, + CompletedRequestsMovies = await availableMovies, + MostRequestedUserMovie = (await userMovie).FirstOrDefault().RequestedUser, + MostRequestedUserTv = (await userTv).FirstOrDefault().RequestedUser, + }; + } + } + + public class SummaryRequest + { + public DateTime From { get; set; } + public DateTime To { get; set; } + } + + public class UserStatsSummary + { + public int TotalRequests => TotalTvRequests + TotalMovieRequests; + public int TotalMovieRequests { get; set; } + public int TotalTvRequests { get; set; } + public int TotalIssues { get; set; } + public int CompletedRequestsMovies { get; set; } + public int CompletedRequestsTv { get; set; } + public int CompletedRequests => CompletedRequestsMovies + CompletedRequestsTv; + public OmbiUser MostRequestedUserMovie { get; set; } + public OmbiUser MostRequestedUserTv { get; set; } + + } +} diff --git a/src/Ombi.Core/Engine/VoteEngine.cs b/src/Ombi.Core/Engine/VoteEngine.cs new file mode 100644 index 000000000..5c73e03d9 --- /dev/null +++ b/src/Ombi.Core/Engine/VoteEngine.cs @@ -0,0 +1,263 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Principal; +using System.Text; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Ombi.Core.Authentication; +using Ombi.Core.Engine.Interfaces; +using Ombi.Core.Models; +using Ombi.Core.Models.UI; +using Ombi.Core.Rule.Interfaces; +using Ombi.Core.Settings; +using Ombi.Helpers; +using Ombi.Settings.Settings.Models; +using Ombi.Store.Entities; +using Ombi.Store.Repository; + +namespace Ombi.Core.Engine +{ + public class VoteEngine : BaseEngine, IVoteEngine + { + public VoteEngine(IRepository votes, IPrincipal user, OmbiUserManager um, IRuleEvaluator r, ISettingsService voteSettings, + IMusicRequestEngine musicRequestEngine, ITvRequestEngine tvRequestEngine, IMovieRequestEngine movieRequestEngine) : base(user, um, r) + { + _voteRepository = votes; + _voteSettings = voteSettings; + _movieRequestEngine = movieRequestEngine; + _musicRequestEngine = musicRequestEngine; + _tvRequestEngine = tvRequestEngine; + } + + private readonly IRepository _voteRepository; + private readonly ISettingsService _voteSettings; + private readonly IMusicRequestEngine _musicRequestEngine; + private readonly ITvRequestEngine _tvRequestEngine; + private readonly IMovieRequestEngine _movieRequestEngine; + + public async Task> GetMovieViewModel() + { + var vm = new List(); + var movieRequests = await _movieRequestEngine.GetRequests(); + var tvRequestsTask = _tvRequestEngine.GetRequests(); + var musicRequestsTask = _musicRequestEngine.GetRequests(); + var user = await GetUser(); + foreach (var r in movieRequests) + { + if (r.Available || r.Approved || (r.Denied ?? false)) + { + continue; + } + // Make model + var votes = GetVotes(r.Id, RequestType.Movie); + var upVotes = await votes.Where(x => x.VoteType == VoteType.Upvote).CountAsync(); + var downVotes = await votes.Where(x => x.VoteType == VoteType.Downvote).CountAsync(); + var myVote = await votes.FirstOrDefaultAsync(x => x.UserId == user.Id && !x.Deleted); + + vm.Add(new VoteViewModel + { + Upvotes = upVotes, + Downvotes = downVotes, + RequestId = r.Id, + RequestType = RequestType.Movie, + Title = r.Title, + Image = $"https://image.tmdb.org/t/p/w500/{r.PosterPath}", + Background = $"https://image.tmdb.org/t/p/w1280{r.Background}", + Description = r.Overview, + AlreadyVoted = myVote != null, + MyVote = myVote?.VoteType ?? VoteType.Downvote + }); + } + + foreach (var r in await musicRequestsTask) + { + if (r.Available || r.Approved || (r.Denied ?? false)) + { + continue; + } + // Make model + var votes = GetVotes(r.Id, RequestType.Album); + var upVotes = await votes.Where(x => x.VoteType == VoteType.Upvote).CountAsync(); + var downVotes = await votes.Where(x => x.VoteType == VoteType.Downvote).CountAsync(); + var myVote = await votes.FirstOrDefaultAsync(x => x.UserId == user.Id && !x.Deleted); + vm.Add(new VoteViewModel + { + Upvotes = upVotes, + Downvotes = downVotes, + RequestId = r.Id, + RequestType = RequestType.Album, + Title = r.Title, + Image = r.Cover, + Background = r.Cover, + Description = r.ArtistName, + AlreadyVoted = myVote != null, + MyVote = myVote?.VoteType ?? VoteType.Downvote + }); + } + + foreach (var r in await tvRequestsTask) + { + + foreach (var childRequests in r.ChildRequests) + { + var finalsb = new StringBuilder(); + if (childRequests.Available || childRequests.Approved || (childRequests.Denied ?? false)) + { + continue; + } + var votes = GetVotes(childRequests.Id, RequestType.TvShow); + // Make model + var upVotes = await votes.Where(x => x.VoteType == VoteType.Upvote).CountAsync(); + var downVotes = await votes.Where(x => x.VoteType == VoteType.Downvote).CountAsync(); + var myVote = await votes.FirstOrDefaultAsync(x => x.UserId == user.Id && !x.Deleted); + foreach (var epInformation in childRequests.SeasonRequests.OrderBy(x => x.SeasonNumber)) + { + var orderedEpisodes = epInformation.Episodes.OrderBy(x => x.EpisodeNumber).ToList(); + var episodeString = StringHelper.BuildEpisodeList(orderedEpisodes.Select(x => x.EpisodeNumber)); + finalsb.Append($"Season: {epInformation.SeasonNumber} - Episodes: {episodeString}"); + finalsb.Append("
"); + } + vm.Add(new VoteViewModel + { + Upvotes = upVotes, + Downvotes = downVotes, + RequestId = childRequests.Id, + RequestType = RequestType.TvShow, + Title = r.Title, + Image = r.PosterPath, + Background = r.Background, + Description = finalsb.ToString(), + AlreadyVoted = myVote != null, + MyVote = myVote?.VoteType ?? VoteType.Downvote + }); + } + } + + return vm; + } + + public IQueryable GetVotes(int requestId, RequestType requestType) + { + return _voteRepository.GetAll().Where(x => x.RequestType == requestType && requestId == x.RequestId); + } + + public Task GetVoteForUser(int requestId, string userId) + { + return _voteRepository.GetAll().FirstOrDefaultAsync(x => x.RequestId == requestId && x.UserId == userId); + } + + public async Task UpVote(int requestId, RequestType requestType) + { + var voteSettings = await _voteSettings.GetSettingsAsync(); + if (!voteSettings.Enabled) + { + return new VoteEngineResult {Result = true}; + } + // How many votes does this have?! + var currentVotes = GetVotes(requestId, requestType); + + var user = await GetUser(); + + // Does this user have a downvote? If so we should revert it and make it an upvote + var currentVote = await GetVoteForUser(requestId, user.Id); + if (currentVote != null && currentVote.VoteType == VoteType.Upvote) + { + return new VoteEngineResult { ErrorMessage = "You have already voted!" }; + } + await RemoveCurrentVote(currentVote); + await _movieRequestEngine.SubscribeToRequest(requestId, requestType); + + await _voteRepository.Add(new Votes + { + Date = DateTime.UtcNow, + RequestId = requestId, + RequestType = requestType, + UserId = user.Id, + VoteType = VoteType.Upvote + }); + + var upVotes = await currentVotes.Where(x => x.VoteType == VoteType.Upvote).CountAsync(); + var downVotes = -(await currentVotes.Where(x => x.VoteType == VoteType.Downvote).CountAsync()); + + var totalVotes = upVotes + downVotes; + RequestEngineResult result = null; + switch (requestType) + { + case RequestType.TvShow: + if (totalVotes >= voteSettings.TvShowVoteMax) + { + result = await _tvRequestEngine.ApproveChildRequest(requestId); + } + break; + case RequestType.Movie: + if (totalVotes >= voteSettings.MovieVoteMax) + { + result = await _movieRequestEngine.ApproveMovieById(requestId); + } + break; + case RequestType.Album: + if (totalVotes >= voteSettings.MusicVoteMax) + { + result = await _musicRequestEngine.ApproveAlbumById(requestId); + } + break; + default: + throw new ArgumentOutOfRangeException(nameof(requestType), requestType, null); + } + + if (result != null && !result.Result) + { + return new VoteEngineResult + { + ErrorMessage = "Voted succesfully but could not approve!" + }; + } + + return new VoteEngineResult + { + Result = true + }; + } + + public async Task DownVote(int requestId, RequestType requestType) + { + var voteSettings = await _voteSettings.GetSettingsAsync(); + if (!voteSettings.Enabled) + { + return new VoteEngineResult { Result = true }; + } + var user = await GetUser(); + var currentVote = await GetVoteForUser(requestId, user.Id); + if (currentVote != null && currentVote.VoteType == VoteType.Downvote) + { + return new VoteEngineResult { ErrorMessage = "You have already voted!" }; + } + await RemoveCurrentVote(currentVote); + + await _movieRequestEngine.UnSubscribeRequest(requestId, requestType); + + await _voteRepository.Add(new Votes + { + Date = DateTime.UtcNow, + RequestId = requestId, + RequestType = requestType, + UserId = user.Id, + VoteType = VoteType.Downvote + }); + + return new VoteEngineResult + { + Result = true + }; + } + + public async Task RemoveCurrentVote(Votes currentVote) + { + if (currentVote != null) + { + await _voteRepository.Delete(currentVote); + } + } + } +} \ No newline at end of file diff --git a/src/Ombi.Core/Helpers/NotificationHelper.cs b/src/Ombi.Core/Helpers/NotificationHelper.cs index 9bd890c21..1615b24f7 100644 --- a/src/Ombi.Core/Helpers/NotificationHelper.cs +++ b/src/Ombi.Core/Helpers/NotificationHelper.cs @@ -40,6 +40,18 @@ namespace Ombi.Core BackgroundJob.Enqueue(() => NotificationService.Publish(notificationModel)); } + public void NewRequest(AlbumRequest model) + { + var notificationModel = new NotificationOptions + { + RequestId = model.Id, + DateTime = DateTime.Now, + NotificationType = NotificationType.NewRequest, + RequestType = model.RequestType + }; + BackgroundJob.Enqueue(() => NotificationService.Publish(notificationModel)); + } + public void Notify(MovieRequests model, NotificationType type) { @@ -66,5 +78,19 @@ namespace Ombi.Core }; BackgroundJob.Enqueue(() => NotificationService.Publish(notificationModel)); } + + public void Notify(AlbumRequest model, NotificationType type) + { + var notificationModel = new NotificationOptions + { + RequestId = model.Id, + DateTime = DateTime.Now, + NotificationType = type, + RequestType = model.RequestType, + Recipient = model.RequestedUser?.Email ?? string.Empty + }; + + BackgroundJob.Enqueue(() => NotificationService.Publish(notificationModel)); + } } } \ No newline at end of file diff --git a/src/Ombi.Core/Helpers/TvShowRequestBuilder.cs b/src/Ombi.Core/Helpers/TvShowRequestBuilder.cs index e5277e236..56d227ad0 100644 --- a/src/Ombi.Core/Helpers/TvShowRequestBuilder.cs +++ b/src/Ombi.Core/Helpers/TvShowRequestBuilder.cs @@ -41,7 +41,7 @@ namespace Ombi.Core.Helpers ShowInfo = await TvApi.ShowLookupByTheTvDbId(id); Results = await MovieDbApi.SearchTv(ShowInfo.name); foreach (TvSearchResult result in Results) { - if (result.Name == ShowInfo.name) + if (result.Name.Equals(ShowInfo.name, StringComparison.InvariantCultureIgnoreCase)) { var showIds = await MovieDbApi.GetTvExternals(result.Id); ShowInfo.externals.imdb = showIds.imdb_id; @@ -64,14 +64,16 @@ namespace Ombi.Core.Helpers { ChildRequest = new ChildRequests { - Id = model.TvDbId, + Id = model.TvDbId, // This is set to 0 after the request rules have run, the request rules needs it to identify the request RequestType = RequestType.TvShow, RequestedDate = DateTime.UtcNow, Approved = false, RequestedUserId = userId, SeasonRequests = new List(), Title = ShowInfo.name, - SeriesType = ShowInfo.genres.Any( s => s.Equals("Anime", StringComparison.OrdinalIgnoreCase)) ? SeriesType.Anime : SeriesType.Standard + ReleaseYear = FirstAir, + RequestedByAlias = model.RequestedByAlias, + SeriesType = ShowInfo.genres.Any( s => s.Equals("Anime", StringComparison.InvariantCultureIgnoreCase)) ? SeriesType.Anime : SeriesType.Standard }; return this; diff --git a/src/Ombi.Core/IPlexOAuthManager.cs b/src/Ombi.Core/IPlexOAuthManager.cs index a5c0c44ff..7668ee882 100644 --- a/src/Ombi.Core/IPlexOAuthManager.cs +++ b/src/Ombi.Core/IPlexOAuthManager.cs @@ -7,8 +7,8 @@ namespace Ombi.Core.Authentication public interface IPlexOAuthManager { Task GetAccessTokenFromPin(int pinId); - Task GetOAuthUrl(int pinId, string code, string websiteAddress = null); - Task GetWizardOAuthUrl(int pinId, string code, string websiteAddress); + Task GetOAuthUrl(string code, string websiteAddress = null); + Task GetWizardOAuthUrl(string code, string websiteAddress); Task GetAccount(string accessToken); } } \ No newline at end of file diff --git a/src/Ombi.Core/Models/RequestQuotaCountModel.cs b/src/Ombi.Core/Models/RequestQuotaCountModel.cs new file mode 100644 index 000000000..1af9ad819 --- /dev/null +++ b/src/Ombi.Core/Models/RequestQuotaCountModel.cs @@ -0,0 +1,15 @@ +using System; + +namespace Ombi.Core.Models +{ + public class RequestQuotaCountModel + { + public bool HasLimit { get; set; } + + public int Limit { get; set; } + + public int Remaining { get; set; } + + public DateTime NextRequest { get; set; } + } +} \ No newline at end of file diff --git a/src/Ombi.Core/Models/Requests/IRequestServiceMain.cs b/src/Ombi.Core/Models/Requests/IRequestServiceMain.cs index 8a269054f..0e68a38e9 100644 --- a/src/Ombi.Core/Models/Requests/IRequestServiceMain.cs +++ b/src/Ombi.Core/Models/Requests/IRequestServiceMain.cs @@ -7,5 +7,6 @@ namespace Ombi.Core.Models.Requests { IMovieRequestRepository MovieRequestService { get; } ITvRequestRepository TvRequestService { get; } + IMusicRequestRepository MusicRequestRepository { get; } } } \ No newline at end of file diff --git a/src/Ombi.Core/Models/Requests/MovieRequestViewModel.cs b/src/Ombi.Core/Models/Requests/MovieRequestViewModel.cs index c67e138e1..5a79d2982 100644 --- a/src/Ombi.Core/Models/Requests/MovieRequestViewModel.cs +++ b/src/Ombi.Core/Models/Requests/MovieRequestViewModel.cs @@ -24,10 +24,20 @@ // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // ************************************************************************/ #endregion + +using Newtonsoft.Json; + namespace Ombi.Core.Models.Requests { public class MovieRequestViewModel { public int TheMovieDbId { get; set; } + public string LanguageCode { get; set; } = "en"; + + /// + /// This is only set from a HTTP Header + /// + [JsonIgnore] + public string RequestedByAlias { get; set; } } } \ No newline at end of file diff --git a/src/Ombi.Core/Models/Requests/MusicArtistRequestViewModel.cs b/src/Ombi.Core/Models/Requests/MusicArtistRequestViewModel.cs new file mode 100644 index 000000000..cd799406b --- /dev/null +++ b/src/Ombi.Core/Models/Requests/MusicArtistRequestViewModel.cs @@ -0,0 +1,8 @@ +namespace Ombi.Core.Models.Requests +{ + public class MusicAlbumRequestViewModel + { + public string ForeignAlbumId { get; set; } + public string RequestedByAlias { get; set; } + } +} \ No newline at end of file diff --git a/src/Ombi.Core/Models/Requests/RequestService.cs b/src/Ombi.Core/Models/Requests/RequestService.cs index 049666440..6f7431baa 100644 --- a/src/Ombi.Core/Models/Requests/RequestService.cs +++ b/src/Ombi.Core/Models/Requests/RequestService.cs @@ -5,13 +5,15 @@ namespace Ombi.Core.Models.Requests { public class RequestService : IRequestServiceMain { - public RequestService(ITvRequestRepository tv, IMovieRequestRepository movie) + public RequestService(ITvRequestRepository tv, IMovieRequestRepository movie, IMusicRequestRepository music) { TvRequestService = tv; MovieRequestService = movie; + MusicRequestRepository = music; } public ITvRequestRepository TvRequestService { get; } + public IMusicRequestRepository MusicRequestRepository { get; } public IMovieRequestRepository MovieRequestService { get; } } } \ No newline at end of file diff --git a/src/Ombi.Core/Models/Requests/TvRequestViewModel.cs b/src/Ombi.Core/Models/Requests/TvRequestViewModel.cs index 78f9edd6d..c17925b1b 100644 --- a/src/Ombi.Core/Models/Requests/TvRequestViewModel.cs +++ b/src/Ombi.Core/Models/Requests/TvRequestViewModel.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using Newtonsoft.Json; namespace Ombi.Core.Models.Requests { @@ -9,6 +10,8 @@ namespace Ombi.Core.Models.Requests public bool FirstSeason { get; set; } public int TvDbId { get; set; } public List Seasons { get; set; } = new List(); + [JsonIgnore] + public string RequestedByAlias { get; set; } } public class SeasonsViewModel diff --git a/src/Ombi.Core/Models/Search/SearchAlbumViewModel.cs b/src/Ombi.Core/Models/Search/SearchAlbumViewModel.cs new file mode 100644 index 000000000..e9f9c5f9e --- /dev/null +++ b/src/Ombi.Core/Models/Search/SearchAlbumViewModel.cs @@ -0,0 +1,28 @@ +using System; +using Ombi.Store.Entities; + +namespace Ombi.Core.Models.Search +{ + public class SearchAlbumViewModel : SearchViewModel + { + public string Title { get; set; } + public string ForeignAlbumId { get; set; } + public bool Monitored { get; set; } + public string AlbumType { get; set; } + public decimal Rating { get; set; } + public DateTime ReleaseDate { get; set; } + public string ArtistName { get; set; } + public string ForeignArtistId { get; set; } + public string Cover { get; set; } + public string Disk { get; set; } + public decimal PercentOfTracks { get; set; } + public object[] Genres { get; set; } + public override RequestType Type => RequestType.Album; + public bool PartiallyAvailable => PercentOfTracks != 100 && PercentOfTracks > 0; + public bool FullyAvailable => PercentOfTracks == 100; + + + // Below is from the INFO call NEED A SEPERATE VM FOR THIS IN V4 TODO + // TODO ADD TRACK COUNT + } +} \ No newline at end of file diff --git a/src/Ombi.Core/Models/Search/SearchArtistViewModel.cs b/src/Ombi.Core/Models/Search/SearchArtistViewModel.cs new file mode 100644 index 000000000..b736df529 --- /dev/null +++ b/src/Ombi.Core/Models/Search/SearchArtistViewModel.cs @@ -0,0 +1,19 @@ +using Ombi.Api.Lidarr.Models; + +namespace Ombi.Core.Models.Search +{ + public class SearchArtistViewModel + { + public string ArtistName { get; set; } + public string ForignArtistId { get; set; } + public string Overview { get; set; } + public string Disambiguation { get; set; } + public string Banner { get; set; } + public string Poster { get; set; } + public string Logo { get; set; } + public bool Monitored { get; set; } + public string ArtistType { get; set; } + public string CleanName { get; set; } + public Link[] Links { get; set; } // Couldn't be bothered to map it + } +} \ No newline at end of file diff --git a/src/Ombi.Core/Models/UI/GotifyNotificationViewModel.cs b/src/Ombi.Core/Models/UI/GotifyNotificationViewModel.cs new file mode 100644 index 000000000..93ce66724 --- /dev/null +++ b/src/Ombi.Core/Models/UI/GotifyNotificationViewModel.cs @@ -0,0 +1,23 @@ + +using System.Collections.Generic; +using Ombi.Settings.Settings.Models.Notifications; +using Ombi.Store.Entities; + +namespace Ombi.Core.Models.UI +{ + /// + /// The view model for the notification settings page + /// + /// + public class GotifyNotificationViewModel : GotifySettings + { + /// + /// Gets or sets the notification templates. + /// + /// + /// The notification templates. + /// + public List NotificationTemplates { get; set; } + + } +} diff --git a/src/Ombi.Core/Models/UI/UserViewModel.cs b/src/Ombi.Core/Models/UI/UserViewModel.cs index 1c1e6162b..ca40c2ec5 100644 --- a/src/Ombi.Core/Models/UI/UserViewModel.cs +++ b/src/Ombi.Core/Models/UI/UserViewModel.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using Ombi.Store.Entities; namespace Ombi.Core.Models.UI { @@ -16,6 +17,11 @@ namespace Ombi.Core.Models.UI public UserType UserType { get; set; } public int MovieRequestLimit { get; set; } public int EpisodeRequestLimit { get; set; } + public RequestQuotaCountModel EpisodeRequestQuota { get; set; } + public RequestQuotaCountModel MovieRequestQuota { get; set; } + public RequestQuotaCountModel MusicRequestQuota { get; set; } + public int MusicRequestLimit { get; set; } + public UserQualityProfiles UserQualityProfiles { get; set; } } public class ClaimCheckboxes diff --git a/src/Ombi.Core/Models/UI/VoteViewModel.cs b/src/Ombi.Core/Models/UI/VoteViewModel.cs new file mode 100644 index 000000000..f58db3dbc --- /dev/null +++ b/src/Ombi.Core/Models/UI/VoteViewModel.cs @@ -0,0 +1,18 @@ +using Ombi.Store.Entities; + +namespace Ombi.Core.Models.UI +{ + public class VoteViewModel + { + public int RequestId { get; set; } + public RequestType RequestType { get; set; } + public string Image { get; set; } + public string Background { get; set; } + public int Upvotes { get; set; } + public int Downvotes { get; set; } + public string Title { get; set; } + public string Description { get; set; } + public bool AlreadyVoted { get; set; } + public VoteType MyVote { get; set; } + } +} \ No newline at end of file diff --git a/src/Ombi.Core/Models/VoteEngineResult.cs b/src/Ombi.Core/Models/VoteEngineResult.cs new file mode 100644 index 000000000..7546b0bb2 --- /dev/null +++ b/src/Ombi.Core/Models/VoteEngineResult.cs @@ -0,0 +1,10 @@ +namespace Ombi.Core.Models +{ + public class VoteEngineResult + { + public bool Result { get; set; } + public string Message { get; set; } + public bool IsError => !string.IsNullOrEmpty(ErrorMessage); + public string ErrorMessage { get; set; } + } +} \ No newline at end of file diff --git a/src/Ombi.Core/Ombi.Core.csproj b/src/Ombi.Core/Ombi.Core.csproj index 104db24fc..790aa8500 100644 --- a/src/Ombi.Core/Ombi.Core.csproj +++ b/src/Ombi.Core/Ombi.Core.csproj @@ -11,25 +11,27 @@ - - - - + + + - + + + + + - diff --git a/src/Ombi.Core/Rule/Interfaces/SpecificRules.cs b/src/Ombi.Core/Rule/Interfaces/SpecificRules.cs index 522ba8a95..d432f87be 100644 --- a/src/Ombi.Core/Rule/Interfaces/SpecificRules.cs +++ b/src/Ombi.Core/Rule/Interfaces/SpecificRules.cs @@ -3,5 +3,7 @@ public enum SpecificRules { CanSendNotification, + LidarrArtist, + LidarrAlbum, } } \ No newline at end of file diff --git a/src/Ombi.Core/Rule/Rules/Request/AutoApproveRule.cs b/src/Ombi.Core/Rule/Rules/Request/AutoApproveRule.cs index 7eecd62f2..6b528e806 100644 --- a/src/Ombi.Core/Rule/Rules/Request/AutoApproveRule.cs +++ b/src/Ombi.Core/Rule/Rules/Request/AutoApproveRule.cs @@ -1,5 +1,8 @@ -using System.Security.Principal; +using System; +using System.Security.Principal; using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Ombi.Core.Authentication; using Ombi.Core.Models.Requests; using Ombi.Core.Rule.Interfaces; using Ombi.Helpers; @@ -10,26 +13,31 @@ namespace Ombi.Core.Rule.Rules.Request { public class AutoApproveRule : BaseRequestRule, IRules { - public AutoApproveRule(IPrincipal principal) + public AutoApproveRule(IPrincipal principal, OmbiUserManager um) { User = principal; + _manager = um; } private IPrincipal User { get; } + private readonly OmbiUserManager _manager; - public Task Execute(BaseRequest obj) + public async Task Execute(BaseRequest obj) { - if (User.IsInRole(OmbiRoles.Admin)) + var user = await _manager.Users.FirstOrDefaultAsync(x => x.UserName.Equals(User.Identity.Name, StringComparison.InvariantCultureIgnoreCase)); + if (await _manager.IsInRoleAsync(user, OmbiRoles.Admin) || user.IsSystemUser) { obj.Approved = true; - return Task.FromResult(Success()); + return Success(); } - if (obj.RequestType == RequestType.Movie && User.IsInRole(OmbiRoles.AutoApproveMovie)) + if (obj.RequestType == RequestType.Movie && await _manager.IsInRoleAsync(user, OmbiRoles.AutoApproveMovie)) obj.Approved = true; - if (obj.RequestType == RequestType.TvShow && User.IsInRole(OmbiRoles.AutoApproveTv)) + if (obj.RequestType == RequestType.TvShow && await _manager.IsInRoleAsync(user, OmbiRoles.AutoApproveTv)) obj.Approved = true; - return Task.FromResult(Success()); // We don't really care, we just don't set the obj to approve + if (obj.RequestType == RequestType.Album && await _manager.IsInRoleAsync(user, OmbiRoles.AutoApproveMusic)) + obj.Approved = true; + return Success(); // We don't really care, we just don't set the obj to approve } } } \ No newline at end of file diff --git a/src/Ombi.Core/Rule/Rules/Request/CanRequestRule.cs b/src/Ombi.Core/Rule/Rules/Request/CanRequestRule.cs index e9729ec35..b54c8b4fb 100644 --- a/src/Ombi.Core/Rule/Rules/Request/CanRequestRule.cs +++ b/src/Ombi.Core/Rule/Rules/Request/CanRequestRule.cs @@ -1,36 +1,63 @@ -using Ombi.Store.Entities; +using System; +using Ombi.Store.Entities; +using System.IO; +using System.Security.Claims; using System.Security.Principal; using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Ombi.Core.Authentication; using Ombi.Core.Rule.Interfaces; using Ombi.Helpers; +using Ombi.Store.Entities; using Ombi.Store.Entities.Requests; -namespace Ombi.Core.Rule.Rules +namespace Ombi.Core.Rule.Rules.Request { public class CanRequestRule : BaseRequestRule, IRules { - public CanRequestRule(IPrincipal principal) + public CanRequestRule(IPrincipal principal, OmbiUserManager manager) { User = principal; + _manager = manager; } private IPrincipal User { get; } + private readonly OmbiUserManager _manager; - public Task Execute(BaseRequest obj) + public async Task Execute(BaseRequest obj) { - if (User.IsInRole(OmbiRoles.Admin)) - return Task.FromResult(Success()); + var user = await _manager.Users.FirstOrDefaultAsync(x => x.UserName.Equals(User.Identity.Name, StringComparison.InvariantCultureIgnoreCase)); + if (await _manager.IsInRoleAsync(user, OmbiRoles.Admin) || user.IsSystemUser) + return Success(); if (obj.RequestType == RequestType.Movie) { - if (User.IsInRole(OmbiRoles.RequestMovie)) - return Task.FromResult(Success()); - return Task.FromResult(Fail("You do not have permissions to Request a Movie")); + if (await _manager.IsInRoleAsync(user, OmbiRoles.RequestMovie) || await _manager.IsInRoleAsync(user, OmbiRoles.AutoApproveMovie)) + return Success(); + return Fail("You do not have permissions to Request a Movie"); } - if (User.IsInRole(OmbiRoles.RequestTv)) - return Task.FromResult(Success()); - return Task.FromResult(Fail("You do not have permissions to Request a TV Show")); + if (obj.RequestType == RequestType.TvShow) + { + if (await _manager.IsInRoleAsync(user, OmbiRoles.RequestTv) || await _manager.IsInRoleAsync(user, OmbiRoles.AutoApproveTv)) + { + return Success(); + } + + return Fail("You do not have permissions to Request a TV Show"); + } + + if (obj.RequestType == RequestType.Album) + { + if (await _manager.IsInRoleAsync(user, OmbiRoles.RequestMusic) || await _manager.IsInRoleAsync(user, OmbiRoles.AutoApproveMusic)) + { + return Success(); + } + + return Fail("You do not have permissions to Request an Album"); + } + + throw new InvalidDataException("Permission check failed: unknown RequestType"); } } -} \ No newline at end of file +} diff --git a/src/Ombi.Core/Rule/Rules/Request/ExistingPlexRequestRule.cs b/src/Ombi.Core/Rule/Rules/Request/ExistingPlexRequestRule.cs new file mode 100644 index 000000000..5d7658c83 --- /dev/null +++ b/src/Ombi.Core/Rule/Rules/Request/ExistingPlexRequestRule.cs @@ -0,0 +1,82 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Ombi.Core.Rule.Interfaces; +using Ombi.Store.Entities; +using Ombi.Store.Entities.Requests; +using Ombi.Store.Repository; + +namespace Ombi.Core.Rule.Rules.Request +{ + public class ExistingPlexRequestRule : BaseRequestRule, IRules + { + public ExistingPlexRequestRule(IPlexContentRepository rv) + { + _plexContent = rv; + } + + private readonly IPlexContentRepository _plexContent; + + /// + /// We check if the request exists, if it does then we don't want to re-request it. + /// + /// The object. + /// + public async Task Execute(BaseRequest obj) + { + if (obj.RequestType == RequestType.TvShow) + { + var tvRequest = (ChildRequests) obj; + + var tvContent = _plexContent.GetAll().Where(x => x.Type == PlexMediaTypeEntity.Show); + // We need to do a check on the TVDBId + var anyTvDbMatches = await tvContent.Include(x => x.Episodes).FirstOrDefaultAsync(x => x.HasTvDb && x.TvDbId.Equals(tvRequest.Id.ToString())); // the Id on the child is the tvdbid at this point + if (anyTvDbMatches == null) + { + // So we do not have a TVDB Id, that really sucks. + // Let's try and match on the title and year of the show + var titleAndYearMatch = await tvContent.Include(x=> x.Episodes).FirstOrDefaultAsync(x => + x.Title.Equals(tvRequest.Title, StringComparison.InvariantCultureIgnoreCase) + && x.ReleaseYear == tvRequest.ReleaseYear.Year.ToString()); + if (titleAndYearMatch != null) + { + // We have a match! Suprise Motherfucker + return CheckExistingContent(tvRequest, titleAndYearMatch); + } + + // We do not have this + return Success(); + } + // looks like we have a match on the TVDbID + return CheckExistingContent(tvRequest, anyTvDbMatches); + } + return Success(); + } + + + private RuleResult CheckExistingContent(ChildRequests child, PlexServerContent content) + { + foreach (var season in child.SeasonRequests) + { + var currentSeasonRequest = + content.Episodes.Where(x => x.SeasonNumber == season.SeasonNumber).ToList(); + if (!currentSeasonRequest.Any()) + { + continue; + } + foreach (var e in season.Episodes) + { + var hasEpisode = currentSeasonRequest.Any(x => x.EpisodeNumber == e.EpisodeNumber); + if (hasEpisode) + { + return Fail($"We already have episodes requested from series {child.Title}"); + } + } + } + + return Success(); + } + } +} \ No newline at end of file diff --git a/src/Ombi.Core/Rule/Rules/Request/ExistingTVRequestRule.cs b/src/Ombi.Core/Rule/Rules/Request/ExistingTVRequestRule.cs new file mode 100644 index 000000000..9942ece63 --- /dev/null +++ b/src/Ombi.Core/Rule/Rules/Request/ExistingTVRequestRule.cs @@ -0,0 +1,57 @@ +using System.Linq; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Ombi.Core.Rule.Interfaces; +using Ombi.Store.Entities; +using Ombi.Store.Entities.Requests; +using Ombi.Store.Repository.Requests; + +namespace Ombi.Core.Rule.Rules.Request +{ + public class ExistingTvRequestRule : BaseRequestRule, IRules + { + public ExistingTvRequestRule(ITvRequestRepository rv) + { + Tv = rv; + } + + private ITvRequestRepository Tv { get; } + + /// + /// We check if the request exists, if it does then we don't want to re-request it. + /// + /// The object. + /// + public async Task Execute(BaseRequest obj) + { + if (obj.RequestType == RequestType.TvShow) + { + var tv = (ChildRequests) obj; + var tvRequests = Tv.GetChild(); + var currentRequest = await tvRequests.FirstOrDefaultAsync(x => x.ParentRequest.TvDbId == tv.Id); // the Id on the child is the tvdbid at this point + if (currentRequest == null) + { + return Success(); + } + foreach (var season in tv.SeasonRequests) + { + var currentSeasonRequest = + currentRequest.SeasonRequests.FirstOrDefault(x => x.SeasonNumber == season.SeasonNumber); + if (currentSeasonRequest == null) + { + continue; + } + foreach (var e in season.Episodes) + { + var hasEpisode = currentSeasonRequest.Episodes.Any(x => x.EpisodeNumber == e.EpisodeNumber); + if (hasEpisode) + { + return Fail($"We already have episodes requested from series {tv.Title}"); + } + } + } + } + return Success(); + } + } +} \ No newline at end of file diff --git a/src/Ombi.Core/Rule/Rules/Request/RequestLimitRule.cs b/src/Ombi.Core/Rule/Rules/Request/RequestLimitRule.cs index a19ac1df8..2afd0700b 100644 --- a/src/Ombi.Core/Rule/Rules/Request/RequestLimitRule.cs +++ b/src/Ombi.Core/Rule/Rules/Request/RequestLimitRule.cs @@ -54,6 +54,7 @@ namespace Ombi.Core.Rule.Rules.Request var movieLimit = user.MovieRequestLimit; var episodeLimit = user.EpisodeRequestLimit; + var musicLimit = user.MusicRequestLimit; var requestLog = _requestLog.GetAll().Where(x => x.UserId == obj.RequestedUserId); if (obj.RequestType == RequestType.Movie) @@ -71,7 +72,7 @@ namespace Ombi.Core.Rule.Rules.Request return Fail("You have exceeded your Movie request quota!"); } } - else + else if (obj.RequestType == RequestType.TvShow) { if (episodeLimit <= 0) return Success(); @@ -81,21 +82,40 @@ namespace Ombi.Core.Rule.Rules.Request // Get the count of requests to be made foreach (var s in child.SeasonRequests) { - requestCount = s.Episodes.Count; + requestCount += s.Episodes.Count; } var tvLogs = requestLog.Where(x => x.RequestType == RequestType.TvShow); // Count how many requests in the past 7 days var tv = tvLogs.Where(x => x.RequestDate >= DateTime.UtcNow.AddDays(-7)); - var count = await tv.Select(x => x.EpisodeCount).CountAsync(); - count += requestCount; // Add the amount of requests in + + // Needed, due to a bug which would cause all episode counts to be 0 + var zeroEpisodeCount = await tv.Where(x => x.EpisodeCount == 0).Select(x => x.EpisodeCount).CountAsync(); + + var episodeCount = await tv.Where(x => x.EpisodeCount != 0).Select(x => x.EpisodeCount).SumAsync(); + + var count = requestCount + episodeCount + zeroEpisodeCount; // Add the amount of requests in if (count > episodeLimit) { return Fail("You have exceeded your Episode request quota!"); } + } else if (obj.RequestType == RequestType.Album) + { + if (musicLimit <= 0) + return Success(); + + var albumLogs = requestLog.Where(x => x.RequestType == RequestType.Album); + + // Count how many requests in the past 7 days + var count = await albumLogs.CountAsync(x => x.RequestDate >= DateTime.UtcNow.AddDays(-7)); + count += 1; // Since we are including this request + if (count > musicLimit) + { + return Fail("You have exceeded your Album request quota!"); + } } - return Success(); + return Success(); } } } diff --git a/src/Ombi.Core/Rule/Rules/Request/SonarrCacheRequestRule.cs b/src/Ombi.Core/Rule/Rules/Request/SonarrCacheRequestRule.cs index 65750a64b..625407f3c 100644 --- a/src/Ombi.Core/Rule/Rules/Request/SonarrCacheRequestRule.cs +++ b/src/Ombi.Core/Rule/Rules/Request/SonarrCacheRequestRule.cs @@ -7,12 +7,12 @@ namespace Ombi.Core.Rule.Rules.Request { public class SonarrCacheRequestRule : BaseRequestRule, IRules { - public SonarrCacheRequestRule(IOmbiContext ctx) + public SonarrCacheRequestRule(IExternalContext ctx) { _ctx = ctx; } - private readonly IOmbiContext _ctx; + private readonly IExternalContext _ctx; public Task Execute(BaseRequest obj) { diff --git a/src/Ombi.Core/Rule/Rules/Search/AvailabilityRuleHelper.cs b/src/Ombi.Core/Rule/Rules/Search/AvailabilityRuleHelper.cs new file mode 100644 index 000000000..ab1a0af98 --- /dev/null +++ b/src/Ombi.Core/Rule/Rules/Search/AvailabilityRuleHelper.cs @@ -0,0 +1,105 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using Ombi.Core.Models.Search; +using Ombi.Store.Entities; +using Ombi.Store.Repository.Requests; + +namespace Ombi.Core.Rule.Rules.Search +{ + public static class AvailabilityRuleHelper + { + public static void CheckForUnairedEpisodes(SearchTvShowViewModel search) + { + if (search.SeasonRequests.All(x => x.Episodes.All(e => e.Available))) + { + search.FullyAvailable = true; + } + else + { + var airedButNotAvailable = search.SeasonRequests.Any(x => + x.Episodes.Any(c => !c.Available && c.AirDate <= DateTime.Now.Date && c.AirDate != DateTime.MinValue)); + if (!airedButNotAvailable) + { + var unairedEpisodes = search.SeasonRequests.Any(x => + x.Episodes.Any(c => !c.Available && c.AirDate > DateTime.Now.Date || c.AirDate != DateTime.MinValue)); + if (unairedEpisodes) + { + search.FullyAvailable = true; + } + } + } + } + + public static async Task SingleEpisodeCheck(bool useImdb, IQueryable allEpisodes, EpisodeRequests episode, + SeasonRequests season, PlexServerContent item, bool useTheMovieDb, bool useTvDb, ILogger log) + { + PlexEpisode epExists = null; + try + { + + if (useImdb) + { + epExists = await allEpisodes.FirstOrDefaultAsync(x => + x.EpisodeNumber == episode.EpisodeNumber && x.SeasonNumber == season.SeasonNumber && + x.Series.ImdbId == item.ImdbId); + } + + if (useTheMovieDb) + { + epExists = await allEpisodes.FirstOrDefaultAsync(x => + x.EpisodeNumber == episode.EpisodeNumber && x.SeasonNumber == season.SeasonNumber && + x.Series.TheMovieDbId == item.TheMovieDbId); + } + + if (useTvDb) + { + epExists = await allEpisodes.FirstOrDefaultAsync(x => + x.EpisodeNumber == episode.EpisodeNumber && x.SeasonNumber == season.SeasonNumber && + x.Series.TvDbId == item.TvDbId); + } + } + catch (Exception e) + { + log.LogError(e, "Exception thrown when attempting to check if something is available"); + } + + if (epExists != null) + { + episode.Available = true; + } + } + public static async Task SingleEpisodeCheck(bool useImdb, IQueryable allEpisodes, EpisodeRequests episode, + SeasonRequests season, EmbyContent item, bool useTheMovieDb, bool useTvDb) + { + EmbyEpisode epExists = null; + if (useImdb) + { + epExists = await allEpisodes.FirstOrDefaultAsync(x => + x.EpisodeNumber == episode.EpisodeNumber && x.SeasonNumber == season.SeasonNumber && + x.Series.ImdbId == item.ImdbId); + } + + if (useTheMovieDb) + { + epExists = await allEpisodes.FirstOrDefaultAsync(x => + x.EpisodeNumber == episode.EpisodeNumber && x.SeasonNumber == season.SeasonNumber && + x.Series.TheMovieDbId == item.TheMovieDbId); + } + + if (useTvDb) + { + epExists = await allEpisodes.FirstOrDefaultAsync(x => + x.EpisodeNumber == episode.EpisodeNumber && x.SeasonNumber == season.SeasonNumber && + x.Series.TvDbId == item.TvDbId); + } + + if (epExists != null) + { + episode.Available = true; + } + } + } +} \ No newline at end of file diff --git a/src/Ombi.Core/Rule/Rules/Search/CouchPotatoCacheRule.cs b/src/Ombi.Core/Rule/Rules/Search/CouchPotatoCacheRule.cs index a6deb95d2..277266b91 100644 --- a/src/Ombi.Core/Rule/Rules/Search/CouchPotatoCacheRule.cs +++ b/src/Ombi.Core/Rule/Rules/Search/CouchPotatoCacheRule.cs @@ -11,12 +11,12 @@ namespace Ombi.Core.Rule.Rules.Search { public class CouchPotatoCacheRule : BaseSearchRule, IRules { - public CouchPotatoCacheRule(IRepository ctx) + public CouchPotatoCacheRule(IExternalRepository ctx) { _ctx = ctx; } - private readonly IRepository _ctx; + private readonly IExternalRepository _ctx; public async Task Execute(SearchViewModel obj) { diff --git a/src/Ombi.Core/Rule/Rules/Search/EmbyAvailabilityRule.cs b/src/Ombi.Core/Rule/Rules/Search/EmbyAvailabilityRule.cs index 486de9ea8..f235c635e 100644 --- a/src/Ombi.Core/Rule/Rules/Search/EmbyAvailabilityRule.cs +++ b/src/Ombi.Core/Rule/Rules/Search/EmbyAvailabilityRule.cs @@ -1,10 +1,10 @@ -using System; -using System.Linq; -using System.Linq.Expressions; +using System.Linq; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; using Ombi.Core.Models.Search; using Ombi.Core.Rule.Interfaces; +using Ombi.Core.Settings; +using Ombi.Core.Settings.Models.External; using Ombi.Helpers; using Ombi.Store.Entities; using Ombi.Store.Repository; @@ -13,25 +13,39 @@ namespace Ombi.Core.Rule.Rules.Search { public class EmbyAvailabilityRule : BaseSearchRule, IRules { - public EmbyAvailabilityRule(IEmbyContentRepository repo) + public EmbyAvailabilityRule(IEmbyContentRepository repo, ISettingsService s) { EmbyContentRepository = repo; + EmbySettings = s; } private IEmbyContentRepository EmbyContentRepository { get; } + private ISettingsService EmbySettings { get; } public async Task Execute(SearchViewModel obj) { EmbyContent item = null; + var useImdb = false; + var useTheMovieDb = false; + var useTvDb = false; + if (obj.ImdbId.HasValue()) { item = await EmbyContentRepository.GetByImdbId(obj.ImdbId); + if (item != null) + { + useImdb = true; + } } if (item == null) { if (obj.TheMovieDbId.HasValue()) { item = await EmbyContentRepository.GetByTheMovieDbId(obj.TheMovieDbId); + if (item != null) + { + useTheMovieDb = true; + } } if (item == null) @@ -39,14 +53,27 @@ namespace Ombi.Core.Rule.Rules.Search if (obj.TheTvDbId.HasValue()) { item = await EmbyContentRepository.GetByTvDbId(obj.TheTvDbId); + if (item != null) + { + useTvDb = true; + } } } } - + if (item != null) { obj.Available = true; - obj.EmbyUrl = item.Url; + var s = await EmbySettings.GetSettingsAsync(); + var server = s.Servers.FirstOrDefault(x => x.ServerHostname != null); + if ((server?.ServerHostname ?? string.Empty).HasValue()) + { + obj.EmbyUrl = EmbyHelper.GetEmbyMediaUrl(item.EmbyId, server?.ServerHostname, s.IsJellyfin); + } + else + { + obj.EmbyUrl = EmbyHelper.GetEmbyMediaUrl(item.EmbyId, null, s.IsJellyfin); + } if (obj.Type == RequestType.TvShow) { @@ -59,29 +86,12 @@ namespace Ombi.Core.Rule.Rules.Search { foreach (var episode in season.Episodes) { - EmbyEpisode epExists = null; - - if (item.HasImdb) - { - epExists = await allEpisodes.FirstOrDefaultAsync(e => e.EpisodeNumber == episode.EpisodeNumber && e.SeasonNumber == season.SeasonNumber - && e.ImdbId == item.ImdbId); - } if (item.HasTvDb && epExists == null) - { - epExists = await allEpisodes.FirstOrDefaultAsync(e => e.EpisodeNumber == episode.EpisodeNumber && e.SeasonNumber == season.SeasonNumber - && e.Series.TvDbId == item.TvDbId); - } if (item.HasTheMovieDb && epExists == null) - { - epExists = await allEpisodes.FirstOrDefaultAsync(e => e.EpisodeNumber == episode.EpisodeNumber && e.SeasonNumber == season.SeasonNumber - && e.TheMovieDbId == item.TheMovieDbId); - } - - if (epExists != null) - { - episode.Available = true; - } + await AvailabilityRuleHelper.SingleEpisodeCheck(useImdb, allEpisodes, episode, season, item, useTheMovieDb, useTvDb); } } } + + AvailabilityRuleHelper.CheckForUnairedEpisodes(search); } } return Success(); diff --git a/src/Ombi.Core/Rule/Rules/Search/ExistingRule.cs b/src/Ombi.Core/Rule/Rules/Search/ExistingRule.cs index 6ca0b966b..2d4482ba9 100644 --- a/src/Ombi.Core/Rule/Rules/Search/ExistingRule.cs +++ b/src/Ombi.Core/Rule/Rules/Search/ExistingRule.cs @@ -1,4 +1,5 @@ -using System.Linq; +using System; +using System.Linq; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; using Ombi.Core.Models.Search; @@ -11,20 +12,22 @@ namespace Ombi.Core.Rule.Rules.Search { public class ExistingRule : BaseSearchRule, IRules { - public ExistingRule(IMovieRequestRepository movie, ITvRequestRepository tv) + public ExistingRule(IMovieRequestRepository movie, ITvRequestRepository tv, IMusicRequestRepository music) { Movie = movie; Tv = tv; + Music = music; } private IMovieRequestRepository Movie { get; } + private IMusicRequestRepository Music { get; } private ITvRequestRepository Tv { get; } - public Task Execute(SearchViewModel obj) + public async Task Execute(SearchViewModel obj) { if (obj.Type == RequestType.Movie) { - var movieRequests = Movie.GetRequest(obj.Id); + var movieRequests = await Movie.GetRequestAsync(obj.Id); if (movieRequests != null) // Do we already have a request for this? { @@ -33,11 +36,11 @@ namespace Ombi.Core.Rule.Rules.Search obj.Approved = movieRequests.Approved; obj.Available = movieRequests.Available; - return Task.FromResult(Success()); + return Success(); } - return Task.FromResult(Success()); + return Success(); } - else + if (obj.Type == RequestType.TvShow) { //var tvRequests = Tv.GetRequest(obj.Id); //if (tvRequests != null) // Do we already have a request for this? @@ -50,7 +53,7 @@ namespace Ombi.Core.Rule.Rules.Search // return Task.FromResult(Success()); //} - var request = (SearchTvShowViewModel) obj; + var request = (SearchTvShowViewModel)obj; var tvRequests = Tv.GetRequest(obj.Id); if (tvRequests != null) // Do we already have a request for this? { @@ -85,17 +88,33 @@ namespace Ombi.Core.Rule.Rules.Search } } - if (request.SeasonRequests.Any() && request.SeasonRequests.All(x => x.Episodes.All(e => e.Available))) + if (request.SeasonRequests.Any() && request.SeasonRequests.All(x => x.Episodes.All(e => e.Available && e.AirDate > DateTime.MinValue))) { request.FullyAvailable = true; } - if (request.SeasonRequests.Any() && request.SeasonRequests.All(x => x.Episodes.Any(e => e.Available))) + if (request.SeasonRequests.Any() && request.SeasonRequests.All(x => x.Episodes.Any(e => e.Available && e.AirDate > DateTime.MinValue))) { request.PartlyAvailable = true; } - return Task.FromResult(Success()); + return Success(); } + if (obj.Type == RequestType.Album) + { + var album = (SearchAlbumViewModel) obj; + var albumRequest = await Music.GetRequestAsync(album.ForeignAlbumId); + if (albumRequest != null) // Do we already have a request for this? + { + obj.Requested = true; + obj.RequestId = albumRequest.Id; + obj.Approved = albumRequest.Approved; + obj.Available = albumRequest.Available; + + return Success(); + } + return Success(); + } + return Success(); } } } \ No newline at end of file diff --git a/src/Ombi.Core/Rule/Rules/Search/LidarrAlbumCacheRule.cs b/src/Ombi.Core/Rule/Rules/Search/LidarrAlbumCacheRule.cs new file mode 100644 index 000000000..fe28c3acf --- /dev/null +++ b/src/Ombi.Core/Rule/Rules/Search/LidarrAlbumCacheRule.cs @@ -0,0 +1,36 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using Ombi.Core.Models.Search; +using Ombi.Core.Rule.Interfaces; +using Ombi.Store.Entities; +using Ombi.Store.Repository; + +namespace Ombi.Core.Rule.Rules.Search +{ + public class LidarrAlbumCacheRule : SpecificRule, ISpecificRule + { + public LidarrAlbumCacheRule(IExternalRepository db) + { + _db = db; + } + + private readonly IExternalRepository _db; + + public Task Execute(object objec) + { + var obj = (SearchAlbumViewModel) objec; + // Check if it's in Lidarr + var result = _db.GetAll().FirstOrDefault(x => x.ForeignAlbumId.Equals(obj.ForeignAlbumId, StringComparison.InvariantCultureIgnoreCase)); + if (result != null) + { + obj.PercentOfTracks = result.PercentOfTracks; + obj.Monitored = true; // It's in Lidarr so it's monitored + } + + return Task.FromResult(Success()); + } + + public override SpecificRules Rule => SpecificRules.LidarrAlbum; + } +} \ No newline at end of file diff --git a/src/Ombi.Core/Rule/Rules/Search/LidarrArtistCacheRule.cs b/src/Ombi.Core/Rule/Rules/Search/LidarrArtistCacheRule.cs new file mode 100644 index 000000000..d9667d66b --- /dev/null +++ b/src/Ombi.Core/Rule/Rules/Search/LidarrArtistCacheRule.cs @@ -0,0 +1,35 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using Ombi.Core.Models.Search; +using Ombi.Core.Rule.Interfaces; +using Ombi.Store.Entities; +using Ombi.Store.Repository; + +namespace Ombi.Core.Rule.Rules.Search +{ + public class LidarrArtistCacheRule : SpecificRule, ISpecificRule + { + public LidarrArtistCacheRule(IExternalRepository db) + { + _db = db; + } + + private readonly IExternalRepository _db; + + public Task Execute(object objec) + { + var obj = (SearchArtistViewModel) objec; + // Check if it's in Lidarr + var result = _db.GetAll().FirstOrDefault(x => x.ForeignArtistId.Equals(obj.ForignArtistId, StringComparison.InvariantCultureIgnoreCase)); + if (result != null) + { + obj.Monitored = true; // It's in Lidarr so it's monitored + } + + return Task.FromResult(Success()); + } + + public override SpecificRules Rule => SpecificRules.LidarrArtist; + } +} \ No newline at end of file diff --git a/src/Ombi.Core/Rule/Rules/Search/PlexAvailabilityRule.cs b/src/Ombi.Core/Rule/Rules/Search/PlexAvailabilityRule.cs index 016a2817c..2a239d1d3 100644 --- a/src/Ombi.Core/Rule/Rules/Search/PlexAvailabilityRule.cs +++ b/src/Ombi.Core/Rule/Rules/Search/PlexAvailabilityRule.cs @@ -1,6 +1,6 @@ using System.Linq; using System.Threading.Tasks; -using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; using Ombi.Core.Models.Search; using Ombi.Core.Rule.Interfaces; using Ombi.Helpers; @@ -11,12 +11,14 @@ namespace Ombi.Core.Rule.Rules.Search { public class PlexAvailabilityRule : BaseSearchRule, IRules { - public PlexAvailabilityRule(IPlexContentRepository repo) + public PlexAvailabilityRule(IPlexContentRepository repo, ILogger log) { PlexContentRepository = repo; + Log = log; } private IPlexContentRepository PlexContentRepository { get; } + private ILogger Log { get; } public async Task Execute(SearchViewModel obj) { @@ -73,41 +75,17 @@ namespace Ombi.Core.Rule.Rules.Search { foreach (var episode in season.Episodes) { - PlexEpisode epExists = null; - if (useImdb) - { - epExists = await allEpisodes.FirstOrDefaultAsync(x => - x.EpisodeNumber == episode.EpisodeNumber && x.SeasonNumber == season.SeasonNumber && - x.Series.ImdbId == item.ImdbId.ToString()); - } - if (useTheMovieDb) - { - epExists = await allEpisodes.FirstOrDefaultAsync(x => - x.EpisodeNumber == episode.EpisodeNumber && x.SeasonNumber == season.SeasonNumber && - x.Series.TheMovieDbId == item.TheMovieDbId.ToString()); - } - if (useTvDb) - { - epExists = await allEpisodes.FirstOrDefaultAsync(x => - x.EpisodeNumber == episode.EpisodeNumber && x.SeasonNumber == season.SeasonNumber && - x.Series.TvDbId == item.TvDbId.ToString()); - } - - if (epExists != null) - { - episode.Available = true; - } + await AvailabilityRuleHelper.SingleEpisodeCheck(useImdb, allEpisodes, episode, season, item, useTheMovieDb, useTvDb, Log); } } - if (search.SeasonRequests.All(x => x.Episodes.All(e => e.Available))) - { - search.FullyAvailable = true; - } + AvailabilityRuleHelper.CheckForUnairedEpisodes(search); } } } return Success(); } + + } } \ No newline at end of file diff --git a/src/Ombi.Core/Rule/Rules/Search/RadarrCacheRule.cs b/src/Ombi.Core/Rule/Rules/Search/RadarrCacheRule.cs index ae3cb4782..105681c82 100644 --- a/src/Ombi.Core/Rule/Rules/Search/RadarrCacheRule.cs +++ b/src/Ombi.Core/Rule/Rules/Search/RadarrCacheRule.cs @@ -9,12 +9,12 @@ namespace Ombi.Core.Rule.Rules.Search { public class RadarrCacheRule : BaseSearchRule, IRules { - public RadarrCacheRule(IRepository db) + public RadarrCacheRule(IExternalRepository db) { _db = db; } - private readonly IRepository _db; + private readonly IExternalRepository _db; public Task Execute(SearchViewModel obj) { diff --git a/src/Ombi.Core/Rule/Rules/Search/SonarrCacheSearchRule.cs b/src/Ombi.Core/Rule/Rules/Search/SonarrCacheSearchRule.cs index f9c5cd09d..03bdbe091 100644 --- a/src/Ombi.Core/Rule/Rules/Search/SonarrCacheSearchRule.cs +++ b/src/Ombi.Core/Rule/Rules/Search/SonarrCacheSearchRule.cs @@ -34,12 +34,12 @@ namespace Ombi.Core.Rule.Rules.Search { public class SonarrCacheSearchRule : BaseSearchRule, IRules { - public SonarrCacheSearchRule(IOmbiContext ctx) + public SonarrCacheSearchRule(IExternalContext ctx) { _ctx = ctx; } - private readonly IOmbiContext _ctx; + private readonly IExternalContext _ctx; public Task Execute(SearchViewModel obj) { diff --git a/src/Ombi.Core/Rule/Rules/SonarrCacheRule.cs b/src/Ombi.Core/Rule/Rules/SonarrCacheRule.cs index c851afb4b..7eac05d56 100644 --- a/src/Ombi.Core/Rule/Rules/SonarrCacheRule.cs +++ b/src/Ombi.Core/Rule/Rules/SonarrCacheRule.cs @@ -10,12 +10,12 @@ namespace Ombi.Core.Rule.Rules { public class SonarrCacheRule { - public SonarrCacheRule(IOmbiContext ctx) + public SonarrCacheRule(IExternalContext ctx) { _ctx = ctx; } - private readonly IOmbiContext _ctx; + private readonly IExternalContext _ctx; public async Task Execute(BaseRequest obj) { diff --git a/src/Ombi.Core/Rule/Rules/Specific/SendNotificationRule.cs b/src/Ombi.Core/Rule/Rules/Specific/SendNotificationRule.cs index 50ac607cb..30ec9b14a 100644 --- a/src/Ombi.Core/Rule/Rules/Specific/SendNotificationRule.cs +++ b/src/Ombi.Core/Rule/Rules/Specific/SendNotificationRule.cs @@ -42,8 +42,15 @@ namespace Ombi.Core.Rule.Rules.Specific sendNotification = !await UserManager.IsInRoleAsync(requestedUser, OmbiRoles.AutoApproveTv); } } + else if (req.RequestType == RequestType.Album) + { + if (settings.DoNotSendNotificationsForAutoApprove) + { + sendNotification = !await UserManager.IsInRoleAsync(requestedUser, OmbiRoles.AutoApproveMusic); + } + } - if (await UserManager.IsInRoleAsync(requestedUser, OmbiRoles.Admin)) + if (await UserManager.IsInRoleAsync(requestedUser, OmbiRoles.Admin) || requestedUser.IsSystemUser) { sendNotification = false; // Don't bother sending a notification if the user is an admin } diff --git a/src/Ombi.Core/Senders/IMusicSender.cs b/src/Ombi.Core/Senders/IMusicSender.cs new file mode 100644 index 000000000..abeec5c29 --- /dev/null +++ b/src/Ombi.Core/Senders/IMusicSender.cs @@ -0,0 +1,10 @@ +using System.Threading.Tasks; +using Ombi.Store.Entities.Requests; + +namespace Ombi.Core.Senders +{ + public interface IMusicSender + { + Task Send(AlbumRequest model); + } +} \ No newline at end of file diff --git a/src/Ombi.Core/Senders/INotificationHelper.cs b/src/Ombi.Core/Senders/INotificationHelper.cs index efc45020c..4ba47d761 100644 --- a/src/Ombi.Core/Senders/INotificationHelper.cs +++ b/src/Ombi.Core/Senders/INotificationHelper.cs @@ -8,7 +8,9 @@ namespace Ombi.Core { void NewRequest(FullBaseRequest model); void NewRequest(ChildRequests model); + void NewRequest(AlbumRequest model); void Notify(MovieRequests model, NotificationType type); void Notify(ChildRequests model, NotificationType type); + void Notify(AlbumRequest model, NotificationType type); } } \ No newline at end of file diff --git a/src/Ombi.Core/Senders/MovieSender.cs b/src/Ombi.Core/Senders/MovieSender.cs index e57a5bf2a..567df43b5 100644 --- a/src/Ombi.Core/Senders/MovieSender.cs +++ b/src/Ombi.Core/Senders/MovieSender.cs @@ -1,5 +1,7 @@ -using System.Linq; +using System; +using System.Linq; using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; using Ombi.Api.CouchPotato; using Ombi.Api.DogNzb.Models; @@ -9,6 +11,8 @@ using Ombi.Helpers; using Ombi.Settings.Settings.Models.External; using Ombi.Store.Entities.Requests; using Ombi.Api.DogNzb; +using Ombi.Store.Entities; +using Ombi.Store.Repository; namespace Ombi.Core.Senders { @@ -16,7 +20,7 @@ namespace Ombi.Core.Senders { public MovieSender(ISettingsService radarrSettings, IRadarrApi api, ILogger log, ISettingsService dogSettings, IDogNzbApi dogApi, ISettingsService cpSettings, - ICouchPotatoApi cpApi) + ICouchPotatoApi cpApi, IRepository userProfiles, IRepository requestQueue, INotificationHelper notify) { RadarrSettings = radarrSettings; RadarrApi = api; @@ -25,6 +29,9 @@ namespace Ombi.Core.Senders DogNzbApi = dogApi; CouchPotatoSettings = cpSettings; CouchPotatoApi = cpApi; + _userProfiles = userProfiles; + _requestQueuRepository = requestQueue; + _notificationHelper = notify; } private ISettingsService RadarrSettings { get; } @@ -34,39 +41,64 @@ namespace Ombi.Core.Senders private ISettingsService DogNzbSettings { get; } private ISettingsService CouchPotatoSettings { get; } private ICouchPotatoApi CouchPotatoApi { get; } + private readonly IRepository _userProfiles; + private readonly IRepository _requestQueuRepository; + private readonly INotificationHelper _notificationHelper; public async Task Send(MovieRequests model) { - var cpSettings = await CouchPotatoSettings.GetSettingsAsync(); - //var watcherSettings = await WatcherSettings.GetSettingsAsync(); - var radarrSettings = await RadarrSettings.GetSettingsAsync(); - if (radarrSettings.Enabled) + try { - return await SendToRadarr(model, radarrSettings); - } - - var dogSettings = await DogNzbSettings.GetSettingsAsync(); - if (dogSettings.Enabled) - { - await SendToDogNzb(model, dogSettings); - return new SenderResult + var cpSettings = await CouchPotatoSettings.GetSettingsAsync(); + //var watcherSettings = await WatcherSettings.GetSettingsAsync(); + var radarrSettings = await RadarrSettings.GetSettingsAsync(); + if (radarrSettings.Enabled) { - Success = true, - Sent = true, - }; - } + return await SendToRadarr(model, radarrSettings); + } - if (cpSettings.Enabled) + var dogSettings = await DogNzbSettings.GetSettingsAsync(); + if (dogSettings.Enabled) + { + await SendToDogNzb(model, dogSettings); + return new SenderResult + { + Success = true, + Sent = true, + }; + } + + if (cpSettings.Enabled) + { + return await SendToCp(model, cpSettings, cpSettings.DefaultProfileId); + } + } + catch (Exception e) { - return await SendToCp(model, cpSettings, cpSettings.DefaultProfileId); + Log.LogError(e, "Error when sending movie to DVR app, added to the request queue"); + + // Check if already in request quee + var existingQueue = await _requestQueuRepository.FirstOrDefaultAsync(x => x.RequestId == model.Id); + if (existingQueue != null) + { + existingQueue.RetryCount++; + existingQueue.Error = e.Message; + await _requestQueuRepository.SaveChangesAsync(); + } + else + { + await _requestQueuRepository.Add(new RequestQueue + { + Dts = DateTime.UtcNow, + Error = e.Message, + RequestId = model.Id, + Type = RequestType.Movie, + RetryCount = 0 + }); + _notificationHelper.Notify(model, NotificationType.ItemAddedToFaultQueue); + } } - //if (watcherSettings.Enabled) - //{ - // return SendToWatcher(model, watcherSettings); - //} - - return new SenderResult { Success = true, @@ -88,13 +120,37 @@ namespace Ombi.Core.Senders private async Task SendToRadarr(MovieRequests model, RadarrSettings settings) { + var qualityToUse = int.Parse(settings.DefaultQualityProfile); + + var rootFolderPath = settings.DefaultRootPath; + + var profiles = await _userProfiles.GetAll().FirstOrDefaultAsync(x => x.UserId == model.RequestedUserId); + if (profiles != null) + { + if (profiles.RadarrRootPath > 0) + { + var tempPath = await RadarrRootPath(profiles.RadarrRootPath, settings); + if (tempPath.HasValue()) + { + rootFolderPath = tempPath; + } + } + if (profiles.RadarrQualityProfile > 0) + { + qualityToUse = profiles.RadarrQualityProfile; + } + } + + // Overrides on the request take priority if (model.QualityOverride > 0) { qualityToUse = model.QualityOverride; } - - var rootFolderPath = model.RootPathOverride <= 0 ? settings.DefaultRootPath : await RadarrRootPath(model.RootPathOverride, settings); + if (model.RootPathOverride > 0) + { + rootFolderPath = await RadarrRootPath(model.RootPathOverride, settings); + } // Check if the movie already exists? Since it could be unmonitored var movies = await RadarrApi.GetMovies(settings.ApiKey, settings.FullUri); @@ -123,7 +179,10 @@ namespace Ombi.Core.Senders existingMovie.monitored = true; await RadarrApi.UpdateMovie(existingMovie, settings.ApiKey, settings.FullUri); // Search for it - await RadarrApi.MovieSearch(new[] { existingMovie.id }, settings.ApiKey, settings.FullUri); + if (!settings.AddOnly) + { + await RadarrApi.MovieSearch(new[] { existingMovie.id }, settings.ApiKey, settings.FullUri); + } return new SenderResult { Success = true, Sent = true }; } @@ -135,7 +194,7 @@ namespace Ombi.Core.Senders { var paths = await RadarrApi.GetRootFolders(settings.ApiKey, settings.FullUri); var selectedPath = paths.FirstOrDefault(x => x.id == overrideId); - return selectedPath.path; + return selectedPath?.path ?? String.Empty; } } } \ No newline at end of file diff --git a/src/Ombi.Core/Senders/MusicSender.cs b/src/Ombi.Core/Senders/MusicSender.cs new file mode 100644 index 000000000..04544c6be --- /dev/null +++ b/src/Ombi.Core/Senders/MusicSender.cs @@ -0,0 +1,202 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using EnsureThat; +using Microsoft.Extensions.Logging; +using Ombi.Api.Lidarr; +using Ombi.Api.Lidarr.Models; +using Ombi.Core.Settings; +using Ombi.Helpers; +using Ombi.Settings.Settings.Models.External; +using Ombi.Store.Entities; +using Ombi.Store.Entities.Requests; +using Ombi.Store.Repository; +using ILogger = Microsoft.Extensions.Logging.ILogger; + +namespace Ombi.Core.Senders +{ + public class MusicSender : IMusicSender + { + public MusicSender(ISettingsService lidarr, ILidarrApi lidarrApi, ILogger log, + IRepository requestQueue, INotificationHelper notify) + { + _lidarrSettings = lidarr; + _lidarrApi = lidarrApi; + _log = log; + _requestQueueRepository = requestQueue; + _notificationHelper = notify; + } + + private readonly ISettingsService _lidarrSettings; + private readonly ILidarrApi _lidarrApi; + private readonly ILogger _log; + private readonly IRepository _requestQueueRepository; + private readonly INotificationHelper _notificationHelper; + + public async Task Send(AlbumRequest model) + { + try + { + var settings = await _lidarrSettings.GetSettingsAsync(); + if (settings.Enabled) + { + return await SendToLidarr(model, settings); + } + + return new SenderResult { Success = false, Sent = false, Message = "Lidarr is not enabled" }; + } + catch (Exception e) + { + _log.LogError(e, "Exception thrown when sending a music to DVR app, added to the request queue"); + var existingQueue = await _requestQueueRepository.FirstOrDefaultAsync(x => x.RequestId == model.Id); + if (existingQueue != null) + { + existingQueue.RetryCount++; + existingQueue.Error = e.Message; + await _requestQueueRepository.SaveChangesAsync(); + } + else + { + await _requestQueueRepository.Add(new RequestQueue + { + Dts = DateTime.UtcNow, + Error = e.Message, + RequestId = model.Id, + Type = RequestType.Album, + RetryCount = 0 + }); + _notificationHelper.Notify(model, NotificationType.ItemAddedToFaultQueue); + } + } + + + return new SenderResult { Success = false, Sent = false, Message = "Something went wrong!" }; + } + + private async Task SendToLidarr(AlbumRequest model, LidarrSettings settings) + { + var qualityToUse = int.Parse(settings.DefaultQualityProfile); + //if (model.QualityOverride > 0) + //{ + // qualityToUse = model.QualityOverride; + //} + + var rootFolderPath = /*model.RootPathOverride <= 0 ?*/ settings.DefaultRootPath /*: await RadarrRootPath(model.RootPathOverride, settings)*/; + + // Need to get the artist + var artist = await _lidarrApi.GetArtistByForeignId(model.ForeignArtistId, settings.ApiKey, settings.FullUri); + + if (artist == null || artist.id <= 0) + { + EnsureArg.IsNotNullOrEmpty(model.ForeignArtistId, nameof(model.ForeignArtistId)); + EnsureArg.IsNotNullOrEmpty(model.ForeignAlbumId, nameof(model.ForeignAlbumId)); + EnsureArg.IsNotNullOrEmpty(model.ArtistName, nameof(model.ArtistName)); + EnsureArg.IsNotNullOrEmpty(rootFolderPath, nameof(rootFolderPath)); + + // Create artist + var newArtist = new ArtistAdd + { + foreignArtistId = model.ForeignArtistId, + addOptions = new Addoptions + { + monitored = true, + searchForMissingAlbums = false, + selectedOption = 6, // None + AlbumsToMonitor = new[] {model.ForeignAlbumId} + }, + added = DateTime.Now, + monitored = true, + albumFolder = settings.AlbumFolder, + artistName = model.ArtistName, + cleanName = model.ArtistName.ToLowerInvariant().RemoveSpaces(), + images = new Image[] { }, + links = new Link[] {}, + metadataProfileId = settings.MetadataProfileId, + qualityProfileId = qualityToUse, + rootFolderPath = rootFolderPath, + }; + + var result = await _lidarrApi.AddArtist(newArtist, settings.ApiKey, settings.FullUri); + if (result != null && result.id > 0) + { + // Search for it + if (!settings.AddOnly) + { + // get the album + var album = await _lidarrApi.GetAllAlbumsByArtistId(result.id, settings.ApiKey, settings.FullUri); + + var albumToSearch = album.FirstOrDefault(x => + x.foreignAlbumId.Equals(model.ForeignAlbumId, StringComparison.InvariantCultureIgnoreCase)); + var maxRetryCount = 10; // 5 seconds + var currentRetry = 0; + while (albumToSearch != null) + { + if (currentRetry >= maxRetryCount) + { + break; + } + currentRetry++; + await Task.Delay(500); + album = await _lidarrApi.GetAllAlbumsByArtistId(result.id, settings.ApiKey, settings.FullUri); + albumToSearch = album.FirstOrDefault(x => + x.foreignAlbumId.Equals(model.ForeignAlbumId, StringComparison.InvariantCultureIgnoreCase)); + } + + + if (albumToSearch != null) + { + await _lidarrApi.AlbumSearch(new[] {albumToSearch.id}, settings.ApiKey, settings.FullUri); + } + } + return new SenderResult { Message = "Album has been requested!", Sent = true, Success = true }; + } + } + else + { + SenderResult result = await SetupAlbum(model, artist, settings); + return result; + } + + return new SenderResult { Success = false, Sent = false, Message = "Album is already monitored" }; + } + + private async Task SetupAlbum(AlbumRequest model, ArtistResult artist, LidarrSettings settings) + { + // Get the album id + var albums = await _lidarrApi.GetAllAlbumsByArtistId(artist.id, settings.ApiKey, settings.FullUri); + var album = albums.FirstOrDefault(x => + x.foreignAlbumId.Equals(model.ForeignAlbumId, StringComparison.InvariantCultureIgnoreCase)); + var maxRetryCount = 10; // 5 seconds + var currentRetry = 0; + while (!albums.Any() || album == null) + { + if (currentRetry >= maxRetryCount) + { + break; + } + currentRetry++; + await Task.Delay(500); + albums = await _lidarrApi.GetAllAlbumsByArtistId(artist.id, settings.ApiKey, settings.FullUri); + album = albums.FirstOrDefault(x => + x.foreignAlbumId.Equals(model.ForeignAlbumId, StringComparison.InvariantCultureIgnoreCase)); + } + // Get the album we want. + + if (album == null) + { + return new SenderResult { Message = "Could not find album in Lidarr", Sent = false, Success = false }; + } + + var result = await _lidarrApi.MontiorAlbum(album.id, settings.ApiKey, settings.FullUri); + if (!settings.AddOnly) + { + await _lidarrApi.AlbumSearch(new[] {result.id}, settings.ApiKey, settings.FullUri); + } + if (result.monitored) + { + return new SenderResult { Message = "Album has been requested!", Sent = true, Success = true}; + } + return new SenderResult { Message = "Could not set album to monitored", Sent = false, Success = false }; + } + } +} \ No newline at end of file diff --git a/src/Ombi.Core/Senders/TvSender.cs b/src/Ombi.Core/Senders/TvSender.cs index 2cccd6778..3a3e34745 100644 --- a/src/Ombi.Core/Senders/TvSender.cs +++ b/src/Ombi.Core/Senders/TvSender.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; using Ombi.Api.DogNzb; using Ombi.Api.DogNzb.Models; @@ -12,85 +13,129 @@ using Ombi.Api.Sonarr.Models; using Ombi.Core.Settings; using Ombi.Helpers; using Ombi.Settings.Settings.Models.External; +using Ombi.Store.Entities; using Ombi.Store.Entities.Requests; +using Ombi.Store.Repository; +using Remotion.Linq.Parsing.Structure.IntermediateModel; namespace Ombi.Core.Senders { public class TvSender : ITvSender { - public TvSender(ISonarrApi sonarrApi, ILogger log, ISettingsService sonarrSettings, + public TvSender(ISonarrApi sonarrApi, ISonarrV3Api sonarrV3Api, ILogger log, ISettingsService sonarrSettings, ISettingsService dog, IDogNzbApi dogApi, ISettingsService srSettings, - ISickRageApi srApi) + ISickRageApi srApi, IRepository userProfiles, IRepository requestQueue, INotificationHelper notify) { SonarrApi = sonarrApi; + SonarrV3Api = sonarrV3Api; Logger = log; SonarrSettings = sonarrSettings; DogNzbSettings = dog; DogNzbApi = dogApi; SickRageSettings = srSettings; SickRageApi = srApi; + UserQualityProfiles = userProfiles; + _requestQueueRepository = requestQueue; + _notificationHelper = notify; } private ISonarrApi SonarrApi { get; } + private ISonarrV3Api SonarrV3Api { get; } private IDogNzbApi DogNzbApi { get; } private ISickRageApi SickRageApi { get; } private ILogger Logger { get; } private ISettingsService SonarrSettings { get; } private ISettingsService DogNzbSettings { get; } private ISettingsService SickRageSettings { get; } + private IRepository UserQualityProfiles { get; } + private readonly IRepository _requestQueueRepository; + private readonly INotificationHelper _notificationHelper; public async Task Send(ChildRequests model) { - var sonarr = await SonarrSettings.GetSettingsAsync(); - if (sonarr.Enabled) + try { - var result = await SendToSonarr(model); - if (result != null) + var sonarr = await SonarrSettings.GetSettingsAsync(); + if (sonarr.Enabled) { + var result = await SendToSonarr(model, sonarr); + if (result != null) + { + return new SenderResult + { + Sent = true, + Success = true + }; + } + } + var dog = await DogNzbSettings.GetSettingsAsync(); + if (dog.Enabled) + { + var result = await SendToDogNzb(model, dog); + if (!result.Failure) + { + return new SenderResult + { + Sent = true, + Success = true + }; + } return new SenderResult { - Sent = true, - Success = true + Message = result.ErrorMessage }; } - } - var dog = await DogNzbSettings.GetSettingsAsync(); - if (dog.Enabled) - { - var result = await SendToDogNzb(model, dog); - if (!result.Failure) + var sr = await SickRageSettings.GetSettingsAsync(); + if (sr.Enabled) { + var result = await SendToSickRage(model, sr); + if (result) + { + return new SenderResult + { + Sent = true, + Success = true + }; + } return new SenderResult { - Sent = true, - Success = true + Message = "Could not send to SickRage!" }; } return new SenderResult { - Message = result.ErrorMessage + Success = true }; } - var sr = await SickRageSettings.GetSettingsAsync(); - if (sr.Enabled) + catch (Exception e) { - var result = await SendToSickRage(model, sr); - if (result) + Logger.LogError(e, "Exception thrown when sending a movie to DVR app, added to the request queue"); + // Check if already in request queue + var existingQueue = await _requestQueueRepository.FirstOrDefaultAsync(x => x.RequestId == model.Id); + if (existingQueue != null) { - return new SenderResult - { - Sent = true, - Success = true - }; + existingQueue.RetryCount++; + existingQueue.Error = e.Message; + await _requestQueueRepository.SaveChangesAsync(); } - return new SenderResult + else { - Message = "Could not send to SickRage!" - }; + await _requestQueueRepository.Add(new RequestQueue + { + Dts = DateTime.UtcNow, + Error = e.Message, + RequestId = model.Id, + Type = RequestType.TvShow, + RetryCount = 0 + }); + _notificationHelper.Notify(model, NotificationType.ItemAddedToFaultQueue); + } } + return new SenderResult { - Success = true + Success = false, + Message = "Something went wrong!" }; } @@ -106,13 +151,8 @@ namespace Ombi.Core.Senders /// /// /// - public async Task SendToSonarr(ChildRequests model) + public async Task SendToSonarr(ChildRequests model, SonarrSettings s) { - var s = await SonarrSettings.GetSettingsAsync(); - if (!s.Enabled) - { - return null; - } if (string.IsNullOrEmpty(s.ApiKey)) { return null; @@ -120,29 +160,60 @@ namespace Ombi.Core.Senders int qualityToUse; string rootFolderPath; + string seriesType; + + var profiles = await UserQualityProfiles.GetAll().FirstOrDefaultAsync(x => x.UserId == model.RequestedUserId); if (model.SeriesType == SeriesType.Anime) { // Get the root path from the rootfolder selected. // For some reason, if we haven't got one use the first root folder in Sonarr - // TODO make this overrideable via the UI rootFolderPath = await GetSonarrRootPath(model.ParentRequest.RootFolder ?? int.Parse(s.RootPathAnime), s); int.TryParse(s.QualityProfileAnime, out qualityToUse); + if (profiles != null) + { + if (profiles.SonarrRootPathAnime > 0) + { + rootFolderPath = await GetSonarrRootPath(profiles.SonarrRootPathAnime, s); + } + if (profiles.SonarrQualityProfileAnime > 0) + { + qualityToUse = profiles.SonarrQualityProfileAnime; + } + } + seriesType = "anime"; + } else { int.TryParse(s.QualityProfile, out qualityToUse); // Get the root path from the rootfolder selected. // For some reason, if we haven't got one use the first root folder in Sonarr - // TODO make this overrideable via the UI rootFolderPath = await GetSonarrRootPath(model.ParentRequest.RootFolder ?? int.Parse(s.RootPath), s); + if (profiles != null) + { + if (profiles.SonarrRootPath > 0) + { + rootFolderPath = await GetSonarrRootPath(profiles.SonarrRootPath, s); + } + if (profiles.SonarrQualityProfile > 0) + { + qualityToUse = profiles.SonarrQualityProfile; + } + } + seriesType = "standard"; } + // Overrides on the request take priority if (model.ParentRequest.QualityOverride.HasValue) { qualityToUse = model.ParentRequest.QualityOverride.Value; } - + + // Are we using v3 sonarr? + var sonarrV3 = s.V3; + var languageProfileId = s.LanguageProfile; + try { // Does the series actually exist? @@ -163,27 +234,23 @@ namespace Ombi.Core.Senders rootFolderPath = rootFolderPath, qualityProfileId = qualityToUse, titleSlug = model.ParentRequest.Title, + seriesType = seriesType, addOptions = new AddOptions { - ignoreEpisodesWithFiles = true, // There shouldn't be any episodes with files, this is a new season - ignoreEpisodesWithoutFiles = true, // We want all missing + ignoreEpisodesWithFiles = false, // There shouldn't be any episodes with files, this is a new season + ignoreEpisodesWithoutFiles = false, // We want all missing searchForMissingEpisodes = false // we want dont want to search yet. We want to make sure everything is unmonitored/monitored correctly. } }; + if (sonarrV3) + { + newSeries.languageProfileId = languageProfileId; + } + // Montitor the correct seasons, // If we have that season in the model then it's monitored! - var seasonsToAdd = new List(); - for (var i = 0; i < model.ParentRequest.TotalSeasons + 1; i++) - { - var index = i; - var season = new Season - { - seasonNumber = i, - monitored = model.SeasonRequests.Any(x => x.SeasonNumber == index && x.SeasonNumber != 0) - }; - seasonsToAdd.Add(season); - } + var seasonsToAdd = GetSeasonsToCreate(model); newSeries.seasons = seasonsToAdd; var result = await SonarrApi.AddSeries(newSeries, s.ApiKey, s.FullUri); existingSeries = await SonarrApi.GetSeriesById(result.id, s.ApiKey, s.FullUri); @@ -237,7 +304,7 @@ namespace Ombi.Core.Senders { var sonarrEp = sonarrEpList.FirstOrDefault(x => x.episodeNumber == ep.EpisodeNumber && x.seasonNumber == req.SeasonNumber); - if (sonarrEp != null) + if (sonarrEp != null && !sonarrEp.monitored) { sonarrEp.monitored = true; episodesToUpdate.Add(sonarrEp); @@ -245,27 +312,78 @@ namespace Ombi.Core.Senders } } var seriesChanges = false; + foreach (var season in model.SeasonRequests) { - var sonarrSeason = sonarrEpList.Where(x => x.seasonNumber == season.SeasonNumber); - var sonarrEpCount = sonarrSeason.Count(); + var sonarrEpisodeList = sonarrEpList.Where(x => x.seasonNumber == season.SeasonNumber).ToList(); + var sonarrEpCount = sonarrEpisodeList.Count; var ourRequestCount = season.Episodes.Count; - if (sonarrEpCount == ourRequestCount) + var ourEpisodes = season.Episodes.Select(x => x.EpisodeNumber).ToList(); + var unairedEpisodes = sonarrEpisodeList.Where(x => x.airDateUtc > DateTime.UtcNow).Select(x => x.episodeNumber).ToList(); + + //// Check if we have requested all the latest episodes, if we have then monitor + //// NOTE, not sure if needed since ombi ui displays future episodes anyway... + //ourEpisodes.AddRange(unairedEpisodes); + //var distinctEpisodes = ourEpisodes.Distinct().ToList(); + //var missingEpisodes = Enumerable.Range(distinctEpisodes.Min(), distinctEpisodes.Count).Except(distinctEpisodes); + + var existingSeason = + result.seasons.FirstOrDefault(x => x.seasonNumber == season.SeasonNumber); + if (existingSeason == null) + { + Logger.LogError("There was no season numer {0} in Sonarr for title {1}", season.SeasonNumber, model.ParentRequest.Title); + continue; + } + + + if (sonarrEpCount == ourRequestCount /*|| !missingEpisodes.Any()*/) { // We have the same amount of requests as all of the episodes in the season. - var existingSeason = - result.seasons.FirstOrDefault(x => x.seasonNumber == season.SeasonNumber); - if (existingSeason == null) + + if (!existingSeason.monitored) { - Logger.LogError("The sonarr ep count was the same as out request count, but could not match the season number {0}", season.SeasonNumber); - continue; + existingSeason.monitored = true; + seriesChanges = true; + } + // Now update the episodes that need updating + foreach (var epToUpdate in episodesToUpdate.Where(x => x.seasonNumber == season.SeasonNumber)) + { + await SonarrApi.UpdateEpisode(epToUpdate, s.ApiKey, s.FullUri); } - existingSeason.monitored = true; - seriesChanges = true; } else { + // Make sure this season is set to monitored + if (!existingSeason.monitored) + { + // We need to monitor it, problem being is all episodes will now be monitored + // So we need to monitor the series but unmonitor every episode + // Except the episodes that are already monitored before we update the series (we do not want to unmonitored episodes that are monitored beforehand) + existingSeason.monitored = true; + var sea = result.seasons.FirstOrDefault(x => x.seasonNumber == existingSeason.seasonNumber); + sea.monitored = true; + //var previouslyMonitoredEpisodes = sonarrEpList.Where(x => + // x.seasonNumber == existingSeason.seasonNumber && x.monitored).Select(x => x.episodeNumber).ToList(); // We probably don't actually care about this + result = await SonarrApi.UpdateSeries(result, s.ApiKey, s.FullUri); + var epToUnmonitored = new List(); + var newEpList = sonarrEpList.ConvertAll(ep => new Episode(ep)); // Clone it so we don't modify the original member + foreach (var ep in newEpList.Where(x => x.seasonNumber == existingSeason.seasonNumber).ToList()) + { + //if (previouslyMonitoredEpisodes.Contains(ep.episodeNumber)) + //{ + // // This was previously monitored. + // continue; + //} + ep.monitored = false; + epToUnmonitored.Add(ep); + } + + foreach (var epToUpdate in epToUnmonitored) + { + await SonarrApi.UpdateEpisode(epToUpdate, s.ApiKey, s.FullUri); + } + } // Now update the episodes that need updating foreach (var epToUpdate in episodesToUpdate.Where(x => x.seasonNumber == season.SeasonNumber)) { @@ -285,6 +403,24 @@ namespace Ombi.Core.Senders } } + private static List GetSeasonsToCreate(ChildRequests model) + { + // Let's get a list of seasons just incase we need to change it + var seasonsToUpdate = new List(); + for (var i = 0; i < model.ParentRequest.TotalSeasons + 1; i++) + { + var index = i; + var sea = new Season + { + seasonNumber = i, + monitored = false + }; + seasonsToUpdate.Add(sea); + } + + return seasonsToUpdate; + } + private async Task SendToSickRage(ChildRequests model, SickRageSettings settings, string qualityId = null) { var tvdbid = model.ParentRequest.TvDbId; diff --git a/src/Ombi.DependencyInjection/IocExtensions.cs b/src/Ombi.DependencyInjection/IocExtensions.cs index dcae39cb0..3d54e5a67 100644 --- a/src/Ombi.DependencyInjection/IocExtensions.cs +++ b/src/Ombi.DependencyInjection/IocExtensions.cs @@ -32,6 +32,8 @@ using Ombi.Api.CouchPotato; using Ombi.Api.DogNzb; using Ombi.Api.FanartTv; using Ombi.Api.Github; +using Ombi.Api.Gotify; +using Ombi.Api.Lidarr; using Ombi.Api.Mattermost; using Ombi.Api.Notifications; using Ombi.Api.Pushbullet; @@ -52,10 +54,14 @@ using Ombi.Updater; using PlexContentCacher = Ombi.Schedule.Jobs.Plex; using Ombi.Api.Telegram; using Ombi.Core.Authentication; +using Ombi.Core.Engine.Demo; using Ombi.Core.Processor; +using Ombi.Schedule.Jobs.Lidarr; using Ombi.Schedule.Jobs.Plex.Interfaces; using Ombi.Schedule.Jobs.SickRage; using Ombi.Schedule.Processor; +using Ombi.Store.Entities; +using Quartz.Spi; namespace Ombi.DependencyInjection { @@ -79,11 +85,18 @@ namespace Ombi.DependencyInjection services.AddTransient(); services.AddTransient(); services.AddTransient(); + services.AddTransient(); services.AddTransient(); services.AddTransient(); + services.AddTransient(); + services.AddTransient(); services.AddTransient(); + services.AddTransient(); services.AddTransient(); services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); } public static void RegisterHttp(this IServiceCollection services) { @@ -99,6 +112,7 @@ namespace Ombi.DependencyInjection services.AddTransient(); services.AddTransient(); services.AddTransient(); + services.AddTransient(); services.AddTransient(); services.AddTransient(); services.AddTransient(); @@ -108,6 +122,7 @@ namespace Ombi.DependencyInjection services.AddTransient(); services.AddTransient(); services.AddTransient(); + services.AddTransient(); services.AddTransient(); services.AddTransient(); services.AddTransient(); @@ -116,25 +131,32 @@ namespace Ombi.DependencyInjection services.AddTransient(); services.AddTransient(); services.AddTransient(); + services.AddTransient(); } public static void RegisterStore(this IServiceCollection services) { - services.AddEntityFrameworkSqlite().AddDbContext(); + services.AddDbContext(); + services.AddDbContext(); + services.AddDbContext(); services.AddScoped(); // https://docs.microsoft.com/en-us/aspnet/core/data/entity-framework-6 - services.AddTransient(); - services.AddTransient(); - services.AddTransient(); - services.AddTransient(); - services.AddTransient(); + services.AddScoped(); // https://docs.microsoft.com/en-us/aspnet/core/data/entity-framework-6 + services.AddScoped(); // https://docs.microsoft.com/en-us/aspnet/core/data/entity-framework-6 + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); - services.AddTransient(); - services.AddTransient(); - services.AddTransient(); - services.AddTransient(); - services.AddTransient(); - services.AddTransient(typeof(ISettingsService<>), typeof(SettingsService<>)); - services.AddTransient(typeof(IRepository<>), typeof(Repository<>)); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(typeof(ISettingsService<>), typeof(SettingsService<>)); + services.AddScoped(typeof(IRepository<>), typeof(Repository<>)); + services.AddScoped(typeof(IExternalRepository<>), typeof(ExternalRepository<>)); } public static void RegisterServices(this IServiceCollection services) { @@ -142,7 +164,7 @@ namespace Ombi.DependencyInjection services.AddTransient(); services.AddTransient(); services.AddTransient(); - services.AddTransient(); + services.AddSingleton(); services.AddTransient(); services.AddTransient(); @@ -151,6 +173,7 @@ namespace Ombi.DependencyInjection services.AddTransient(); services.AddTransient(); services.AddTransient(); + services.AddTransient(); services.AddTransient(); services.AddTransient(); services.AddTransient(); @@ -158,6 +181,7 @@ namespace Ombi.DependencyInjection public static void RegisterJobs(this IServiceCollection services) { + services.AddSingleton(provider => new IoCJobFactory(provider)); services.AddTransient(); services.AddTransient(); @@ -166,7 +190,6 @@ namespace Ombi.DependencyInjection services.AddTransient(); services.AddTransient(); services.AddTransient(); - services.AddTransient(); services.AddTransient(); services.AddTransient(); services.AddTransient(); @@ -178,7 +201,13 @@ namespace Ombi.DependencyInjection services.AddTransient(); services.AddTransient(); services.AddTransient(); - services.AddTransient(); + //services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); } } } diff --git a/src/Ombi.DependencyInjection/Ombi.DependencyInjection.csproj b/src/Ombi.DependencyInjection/Ombi.DependencyInjection.csproj index 2e7f984a7..ec905e718 100644 --- a/src/Ombi.DependencyInjection/Ombi.DependencyInjection.csproj +++ b/src/Ombi.DependencyInjection/Ombi.DependencyInjection.csproj @@ -9,9 +9,9 @@ - - - + + + @@ -21,6 +21,7 @@ + diff --git a/src/Ombi.Helpers.Tests/Ombi.Helpers.Tests.csproj b/src/Ombi.Helpers.Tests/Ombi.Helpers.Tests.csproj new file mode 100644 index 000000000..0517af22d --- /dev/null +++ b/src/Ombi.Helpers.Tests/Ombi.Helpers.Tests.csproj @@ -0,0 +1,19 @@ + + + + netcoreapp2.2 + + false + + + + + + + + + + + + + diff --git a/src/Ombi.Helpers.Tests/PlexHelperTests.cs b/src/Ombi.Helpers.Tests/PlexHelperTests.cs new file mode 100644 index 000000000..8ecb3fa0a --- /dev/null +++ b/src/Ombi.Helpers.Tests/PlexHelperTests.cs @@ -0,0 +1,52 @@ +using System; +using NUnit.Framework; +using System.Collections.Generic; + +namespace Ombi.Helpers.Tests +{ + [TestFixture] + public class PlexHelperTests + { + + [TestCaseSource(nameof(ProviderIdGuidData))] + public string GetProviderIdFromPlexGuidTests(string guidInput, ProviderIdType type) + { + var result = PlexHelper.GetProviderIdFromPlexGuid(guidInput); + + switch (type) + { + case ProviderIdType.Imdb: + Assert.That(result.ImdbId, Is.Not.Null); + return result.ImdbId; + case ProviderIdType.TvDb: + Assert.That(result.TheTvDb, Is.Not.Null); + return result.TheTvDb; + case ProviderIdType.MovieDb: + Assert.That(result.TheMovieDb, Is.Not.Null); + return result.TheMovieDb; + default: + throw new ArgumentOutOfRangeException(nameof(type), type, null); + } + } + + public static IEnumerable ProviderIdGuidData + { + get + { + yield return new TestCaseData("com.plexapp.agents.thetvdb://269586/2/8?lang=en", ProviderIdType.TvDb).Returns("269586").SetName("Regular TvDb Id"); + yield return new TestCaseData("com.plexapp.agents.themoviedb://390043?lang=en", ProviderIdType.MovieDb).Returns("390043").SetName("Regular MovieDb Id"); + yield return new TestCaseData("com.plexapp.agents.imdb://tt2543164?lang=en", ProviderIdType.Imdb).Returns("tt2543164").SetName("Regular Imdb Id"); + yield return new TestCaseData("com.plexapp.agents.agent47://tt2543456?lang=en", ProviderIdType.Imdb).Returns("tt2543456").SetName("Unknown IMDB agent"); + yield return new TestCaseData("com.plexapp.agents.agent47://456822/1/1?lang=en", ProviderIdType.TvDb).Returns("456822").SetName("Unknown TvDb agent"); + yield return new TestCaseData("com.plexapp.agents.agent47://456822/999/999?lang=en", ProviderIdType.TvDb).Returns("456822").SetName("Unknown TvDb agent, large episode and season"); + } + } + + public enum ProviderIdType + { + Imdb, + TvDb, + MovieDb + } + } +} \ No newline at end of file diff --git a/src/Ombi.Helpers.Tests/StringHelperTests.cs b/src/Ombi.Helpers.Tests/StringHelperTests.cs new file mode 100644 index 000000000..d03f926ff --- /dev/null +++ b/src/Ombi.Helpers.Tests/StringHelperTests.cs @@ -0,0 +1,44 @@ +using NUnit.Framework; + +namespace Ombi.Helpers.Tests +{ + [TestFixture] + public class StringHelperTests + { + [Test] + public void ToHttpsUrl_ShouldReturnsHttpsUrl_HttpUrl() + { + var sourceUrl = "http://www.test.url"; + var expectedUrl = "https://www.test.url"; + + Assert.AreEqual(expectedUrl, sourceUrl.ToHttpsUrl(), "Should return the source URL as https"); + } + + [Test] + public void ToHttpsUrl_ShouldReturnsUnchangedUrl_HttpsUrl() + { + var sourceUrl = "https://www.test.url"; + var expectedUrl = "https://www.test.url"; + + Assert.AreEqual(expectedUrl, sourceUrl.ToHttpsUrl(), "Should return the unchanged https URL"); + } + + [Test] + public void ToHttpsUrl_ShouldReturnsUnchangedUrl_NonHttpUrl() + { + var sourceUrl = "ftp://www.test.url"; + var expectedUrl = "ftp://www.test.url"; + + Assert.AreEqual(expectedUrl, sourceUrl.ToHttpsUrl(), "Should return the unchanged non-http URL"); + } + + [Test] + public void ToHttpsUrl_ShouldReturnsUnchangedUrl_InvalidUrl() + { + var sourceUrl = "http:/www.test.url"; + var expectedUrl = "http:/www.test.url"; + + Assert.AreEqual(expectedUrl, sourceUrl.ToHttpsUrl(), "Should return the unchanged invalid URL"); + } + } +} \ No newline at end of file diff --git a/src/Ombi.Helpers/CacheKeys.cs b/src/Ombi.Helpers/CacheKeys.cs index e6c482f7b..f7a40d321 100644 --- a/src/Ombi.Helpers/CacheKeys.cs +++ b/src/Ombi.Helpers/CacheKeys.cs @@ -18,6 +18,8 @@ namespace Ombi.Helpers public const string NowPlayingMovies = nameof(NowPlayingMovies); public const string RadarrRootProfiles = nameof(RadarrRootProfiles); public const string RadarrQualityProfiles = nameof(RadarrQualityProfiles); + public const string LidarrRootFolders = nameof(LidarrRootFolders); + public const string LidarrQualityProfiles = nameof(LidarrQualityProfiles); public const string FanartTv = nameof(FanartTv); } } diff --git a/src/Ombi.Helpers/CacheService.cs b/src/Ombi.Helpers/CacheService.cs index 4eef62bda..ae57a9f61 100644 --- a/src/Ombi.Helpers/CacheService.cs +++ b/src/Ombi.Helpers/CacheService.cs @@ -28,18 +28,15 @@ namespace Ombi.Helpers return result; } - using (await _mutex.LockAsync()) + if (_memoryCache.TryGetValue(cacheKey, out result)) { - if (_memoryCache.TryGetValue(cacheKey, out result)) - { - return result; - } - - result = await factory(); - _memoryCache.Set(cacheKey, result, absoluteExpiration); - return result; } + + result = await factory(); + _memoryCache.Set(cacheKey, result, absoluteExpiration); + + return result; } public void Remove(string key) @@ -47,34 +44,34 @@ namespace Ombi.Helpers _memoryCache.Remove(key); } - - - public T GetOrAdd(string cacheKey, Func factory, DateTime absoluteExpiration) + + + public T GetOrAdd(string cacheKey, Func factory, DateTime absoluteExpiration) + { + // locks get and set internally + if (_memoryCache.TryGetValue(cacheKey, out var result)) { - // locks get and set internally - if (_memoryCache.TryGetValue(cacheKey, out var result)) + return result; + } + + lock (TypeLock.Lock) + { + if (_memoryCache.TryGetValue(cacheKey, out result)) { return result; } - lock (TypeLock.Lock) - { - if (_memoryCache.TryGetValue(cacheKey, out result)) - { - return result; - } + result = factory(); + _memoryCache.Set(cacheKey, result, absoluteExpiration); - result = factory(); - _memoryCache.Set(cacheKey, result, absoluteExpiration); - - return result; - } + return result; } + } + + private static class TypeLock + { + public static object Lock { get; } = new object(); + } - private static class TypeLock - { - public static object Lock { get; } = new object(); - } - } } diff --git a/src/Ombi.Helpers/Cron.cs b/src/Ombi.Helpers/Cron.cs index 35b141eda..9b6339391 100644 --- a/src/Ombi.Helpers/Cron.cs +++ b/src/Ombi.Helpers/Cron.cs @@ -16,7 +16,6 @@ // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . - using System; /// /// Helper class that provides common values for the cron expressions. /// @@ -44,7 +43,7 @@ /// The minute in which the schedule will be activated (0-59). public static string Hourly(int minute) { - return $"{minute} * * * *"; + return $"0 {minute} 0/1 1/1 * ? *"; } /// @@ -73,7 +72,7 @@ /// The minute in which the schedule will be activated (0-59). public static string Daily(int hour, int minute) { - return $"{minute} {hour} * * *"; + return $"0 {minute} {hour} 1/1 * ? *"; } /// @@ -114,7 +113,7 @@ /// The minute in which the schedule will be activated (0-59). public static string Weekly(DayOfWeek dayOfWeek, int hour, int minute) { - return $"{minute} {hour} * * {(int)dayOfWeek}"; + return $"0 {minute} {hour} ? * {(int)dayOfWeek} *"; } /// @@ -219,7 +218,7 @@ /// The number of minutes to wait between every activation. public static string MinuteInterval(int interval) { - return $"*/{interval} * * * *"; + return $"0 0/{interval} * 1/1 * ? *"; } /// @@ -228,7 +227,7 @@ /// The number of hours to wait between every activation. public static string HourInterval(int interval) { - return $"0 */{interval} * * *"; + return $"0 0 0/{interval} 1/1 * ? *"; } /// @@ -237,7 +236,7 @@ /// The number of days to wait between every activation. public static string DayInterval(int interval) { - return $"0 0 */{interval} * *"; + return $"0 0 12 1/{interval} * ? *"; } /// @@ -249,4 +248,39 @@ return $"0 0 1 */{interval} *"; } } + + // + // Summary: + // Specifies the day of the week. + public enum DayOfWeek + { + // + // Summary: + // Indicates Sunday. + Sunday = 1, + // + // Summary: + // Indicates Monday. + Monday = 2, + // + // Summary: + // Indicates Tuesday. + Tuesday = 3, + // + // Summary: + // Indicates Wednesday. + Wednesday = 4, + // + // Summary: + // Indicates Thursday. + Thursday = 5, + // + // Summary: + // Indicates Friday. + Friday = 6, + // + // Summary: + // Indicates Saturday. + Saturday = 7 + } } \ No newline at end of file diff --git a/src/Ombi.Helpers/DemoLists.cs b/src/Ombi.Helpers/DemoLists.cs new file mode 100644 index 000000000..c0d0dd77f --- /dev/null +++ b/src/Ombi.Helpers/DemoLists.cs @@ -0,0 +1,11 @@ +namespace Ombi.Config +{ + public class DemoLists + { + public bool Enabled { get; set; } + public int[] Movies { get; set; } + public int[] TvShows { get; set; } + } + + +} \ No newline at end of file diff --git a/src/Ombi.Helpers/DemoSingleton.cs b/src/Ombi.Helpers/DemoSingleton.cs new file mode 100644 index 000000000..22b6b2f31 --- /dev/null +++ b/src/Ombi.Helpers/DemoSingleton.cs @@ -0,0 +1,13 @@ +namespace Ombi.Helpers +{ + public class DemoSingleton + { + private static DemoSingleton instance; + + private DemoSingleton() { } + + public static DemoSingleton Instance => instance ?? (instance = new DemoSingleton()); + + public bool Demo { get; set; } + } +} \ No newline at end of file diff --git a/src/Ombi.Helpers/EmbyHelper.cs b/src/Ombi.Helpers/EmbyHelper.cs index 567bcfe7e..e22bb1d99 100644 --- a/src/Ombi.Helpers/EmbyHelper.cs +++ b/src/Ombi.Helpers/EmbyHelper.cs @@ -1,17 +1,22 @@ -using System; -using System.Globalization; -using System.Collections.Generic; -using System.Text; - -namespace Ombi.Helpers +namespace Ombi.Helpers { public class EmbyHelper { - public static string GetEmbyMediaUrl(string mediaId) + public static string GetEmbyMediaUrl(string mediaId, string customerServerUrl = null, bool isJellyfin = false) { - var url = - $"http://app.emby.media/#!/itemdetails.html?id={mediaId}"; - return url; + string path = "item/item"; + if (isJellyfin) + { + path = "itemdetails"; + } + if (customerServerUrl.HasValue()) + { + return $"{customerServerUrl}#!/{path}.html?id={mediaId}"; + } + else + { + return $"https://app.emby.media/#!/{path}.html?id={mediaId}"; + } } } } diff --git a/src/Ombi.Helpers/LoggingEvents.cs b/src/Ombi.Helpers/LoggingEvents.cs index 39c309102..0723800ab 100644 --- a/src/Ombi.Helpers/LoggingEvents.cs +++ b/src/Ombi.Helpers/LoggingEvents.cs @@ -20,6 +20,8 @@ namespace Ombi.Helpers public static EventId CouchPotatoCacher => new EventId(2007); public static EventId PlexContentCacher => new EventId(2008); public static EventId SickRageCacher => new EventId(2009); + public static EventId LidarrArtistCache => new EventId(2010); + public static EventId MediaReferesh => new EventId(2011); public static EventId MovieSender => new EventId(3000); @@ -30,6 +32,7 @@ namespace Ombi.Helpers public static EventId MattermostNotification => new EventId(4004); public static EventId PushoverNotification => new EventId(4005); public static EventId TelegramNotifcation => new EventId(4006); + public static EventId GotifyNotification => new EventId(4007); public static EventId TvSender => new EventId(5000); public static EventId SonarrSender => new EventId(5001); diff --git a/src/Ombi.Helpers/NotificationAgent.cs b/src/Ombi.Helpers/NotificationAgent.cs index 8990eeba9..18f28105a 100644 --- a/src/Ombi.Helpers/NotificationAgent.cs +++ b/src/Ombi.Helpers/NotificationAgent.cs @@ -10,5 +10,6 @@ Slack = 5, Mattermost = 6, Mobile = 7, + Gotify = 8, } } \ No newline at end of file diff --git a/src/Ombi.Helpers/Ombi.Helpers.csproj b/src/Ombi.Helpers/Ombi.Helpers.csproj index e94afc816..5dedaff61 100644 --- a/src/Ombi.Helpers/Ombi.Helpers.csproj +++ b/src/Ombi.Helpers/Ombi.Helpers.csproj @@ -10,9 +10,9 @@ - - - + + + diff --git a/src/Ombi.Helpers/OmbiRoles.cs b/src/Ombi.Helpers/OmbiRoles.cs index 5b62a2bae..02a480fdf 100644 --- a/src/Ombi.Helpers/OmbiRoles.cs +++ b/src/Ombi.Helpers/OmbiRoles.cs @@ -7,10 +7,14 @@ public const string Admin = nameof(Admin); public const string AutoApproveMovie = nameof(AutoApproveMovie); public const string AutoApproveTv = nameof(AutoApproveTv); + public const string AutoApproveMusic = nameof(AutoApproveMusic); public const string PowerUser = nameof(PowerUser); public const string RequestTv = nameof(RequestTv); public const string RequestMovie = nameof(RequestMovie); + public const string RequestMusic = nameof(RequestMusic); public const string Disabled = nameof(Disabled); public const string ReceivesNewsletter = nameof(ReceivesNewsletter); + public const string ManageOwnRequests = nameof(ManageOwnRequests); + public const string EditCustomPage = nameof(EditCustomPage); } } \ No newline at end of file diff --git a/src/Ombi.Helpers/PlexHelper.cs b/src/Ombi.Helpers/PlexHelper.cs index 93710022f..de61b8740 100644 --- a/src/Ombi.Helpers/PlexHelper.cs +++ b/src/Ombi.Helpers/PlexHelper.cs @@ -27,12 +27,15 @@ using System; using System.Globalization; +using System.Text.RegularExpressions; namespace Ombi.Helpers { public class PlexHelper { - + private const string ImdbMatchExpression = "tt([0-9]{1,10})"; + private const string TvDbIdMatchExpression = "//[0-9]+/([0-9]{1,3})/([0-9]{1,3})"; + public static ProviderId GetProviderIdFromPlexGuid(string guid) { //com.plexapp.agents.thetvdb://269586/2/8?lang=en @@ -52,7 +55,7 @@ namespace Ombi.Helpers { TheTvDb = guidSplit[1] }; - } + } else if (guid.Contains("themoviedb", CompareOptions.IgnoreCase)) { return new ProviderId @@ -60,6 +63,7 @@ namespace Ombi.Helpers TheMovieDb = guidSplit[1] }; } + else if (guid.Contains("imdb", CompareOptions.IgnoreCase)) { return new ProviderId @@ -67,6 +71,31 @@ namespace Ombi.Helpers ImdbId = guidSplit[1] }; } + else + { + var imdbRegex = new Regex(ImdbMatchExpression, RegexOptions.Compiled); + var tvdbRegex = new Regex(TvDbIdMatchExpression, RegexOptions.Compiled); + var imdbMatch = imdbRegex.IsMatch(guid); + if (imdbMatch) + { + return new ProviderId + { + ImdbId = guidSplit[1] + }; + } + else + { + // Check if it matches the TvDb pattern + var tvdbMatch = tvdbRegex.IsMatch(guid); + if (tvdbMatch) + { + return new ProviderId + { + TheTvDb = guidSplit[1] + }; + } + } + } } return new ProviderId(); } diff --git a/src/Ombi.Helpers/StringHelper.cs b/src/Ombi.Helpers/StringHelper.cs index aba120c65..bd9ecb5bb 100644 --- a/src/Ombi.Helpers/StringHelper.cs +++ b/src/Ombi.Helpers/StringHelper.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Security; @@ -75,5 +76,62 @@ namespace Ombi.Helpers return -1; } + + public static string BuildEpisodeList(IEnumerable orderedEpisodes) + { + var epSb = new StringBuilder(); + var previousEpisodes = new List(); + var previousEpisode = -1; + foreach (var ep in orderedEpisodes) + { + if (ep - 1 == previousEpisode) + { + // This is the next one + previousEpisodes.Add(ep); + } + else + { + if (previousEpisodes.Count > 1) + { + // End it + epSb.Append($"{previousEpisodes.First()}-{previousEpisodes.Last()}, "); + } + else if (previousEpisodes.Count == 1) + { + epSb.Append($"{previousEpisodes.FirstOrDefault()}, "); + } + // New one + previousEpisodes.Clear(); + previousEpisodes.Add(ep); + } + previousEpisode = ep; + } + + if (previousEpisodes.Count > 1) + { + // Got some left over + epSb.Append($"{previousEpisodes.First()}-{previousEpisodes.Last()}"); + } + else if (previousEpisodes.Count == 1) + { + epSb.Append(previousEpisodes.FirstOrDefault()); + } + + return epSb.ToString(); + } + + public static string RemoveSpaces(this string str) + { + return str.Replace(" ", ""); + } + public static string StripCharacters(this string str, params char[] chars) + { + return string.Concat(str.Where(c => !chars.Contains(c))); + } + + public static string ToHttpsUrl(this string currentUrl) + { + return currentUrl.Replace("http://", "https://"); + } } } \ No newline at end of file diff --git a/src/Ombi.Mapping/Profiles/SettingsProfile.cs b/src/Ombi.Mapping/Profiles/SettingsProfile.cs index 139290f2b..f460ce78b 100644 --- a/src/Ombi.Mapping/Profiles/SettingsProfile.cs +++ b/src/Ombi.Mapping/Profiles/SettingsProfile.cs @@ -19,6 +19,7 @@ namespace Ombi.Mapping.Profiles CreateMap().ReverseMap(); CreateMap().ReverseMap(); CreateMap().ReverseMap(); + CreateMap().ReverseMap(); } } } \ No newline at end of file diff --git a/src/Ombi.Mapping/Profiles/TvProfile.cs b/src/Ombi.Mapping/Profiles/TvProfile.cs index 4808a905b..db5213b71 100644 --- a/src/Ombi.Mapping/Profiles/TvProfile.cs +++ b/src/Ombi.Mapping/Profiles/TvProfile.cs @@ -29,7 +29,7 @@ namespace Ombi.Mapping.Profiles .ForMember(dest => dest.Runtime, opts => opts.MapFrom(src => src.show.runtime.ToString())) .ForMember(dest => dest.SeriesId, opts => opts.MapFrom(src => src.show.id)) .ForMember(dest => dest.Title, opts => opts.MapFrom(src => src.show.name)) - .ForMember(dest => dest.Banner, opts => opts.MapFrom(src => !string.IsNullOrEmpty(src.show.image.medium) ? src.show.image.medium.Replace("http", "https") : string.Empty)) + .ForMember(dest => dest.Banner, opts => opts.MapFrom(src => !string.IsNullOrEmpty(src.show.image.medium) ? src.show.image.medium.ToHttpsUrl() : string.Empty)) .ForMember(dest => dest.Status, opts => opts.MapFrom(src => src.show.status)); CreateMap() @@ -46,7 +46,7 @@ namespace Ombi.Mapping.Profiles .ForMember(dest => dest.Title, opts => opts.MapFrom(src => src.name)) .ForMember(dest => dest.Banner, opts => opts.MapFrom(src => !string.IsNullOrEmpty(src.image.medium) - ? src.image.medium.Replace("http", "https") + ? src.image.medium.ToHttpsUrl() : string.Empty)) .ForMember(dest => dest.Status, opts => opts.MapFrom(src => src.status)); diff --git a/src/Ombi.Notifications.Templates/EmailBasicTemplate.cs b/src/Ombi.Notifications.Templates/EmailBasicTemplate.cs index b29122be0..fc80de193 100644 --- a/src/Ombi.Notifications.Templates/EmailBasicTemplate.cs +++ b/src/Ombi.Notifications.Templates/EmailBasicTemplate.cs @@ -13,7 +13,7 @@ namespace Ombi.Notifications.Templates if (string.IsNullOrEmpty(_templateLocation)) { #if DEBUG - _templateLocation = Path.Combine(Directory.GetCurrentDirectory(), "bin", "Debug", "netcoreapp2.0", "Templates", + _templateLocation = Path.Combine(Directory.GetCurrentDirectory(), "bin", "Debug", "netcoreapp2.2", "Templates", "BasicTemplate.html"); #else _templateLocation = Path.Combine(Directory.GetCurrentDirectory(), "Templates","BasicTemplate.html"); diff --git a/src/Ombi.Notifications.Templates/Templates/NewsletterTemplate.html b/src/Ombi.Notifications.Templates/Templates/NewsletterTemplate.html index 450e7df2a..5456743c9 100644 --- a/src/Ombi.Notifications.Templates/Templates/NewsletterTemplate.html +++ b/src/Ombi.Notifications.Templates/Templates/NewsletterTemplate.html @@ -1,10 +1,17 @@ - + + - - + + Ombi - - - - -
-
- - - - - - - - - - - - - -
- - - - - - - -
- -
-
-
-

{@INTRO}

- -
- -
- {@RECENTLYADDED} -
- - -
"); - sb.AppendFormat("", url); + sb.Append("
"); + sb.AppendFormat("", url); sb.Append(""); - sb.Append("
"); - sb.Append(""); + sb.Append("
"); + sb.Append(""); } protected virtual void AddPosterInsideTable(StringBuilder sb, string url) { sb.Append(""); - sb.Append("
"); - sb.AppendFormat("", url); + sb.Append(""); } @@ -36,47 +44,47 @@ namespace Ombi.Schedule.Jobs.Ombi protected virtual void AddInfoTable(StringBuilder sb) { sb.Append( - "
"); + sb.AppendFormat("", url); } protected virtual void AddMediaServerUrl(StringBuilder sb, string mediaurl, string url) { - sb.Append(""); - sb.Append(""); - sb.Append(""); + if (url.HasValue()) + { + sb.Append(""); + sb.Append( + ""); + sb.Append(""); + } + sb.Append("
"); - sb.AppendFormat("", mediaurl); - sb.AppendFormat("", url); - sb.Append(""); - sb.Append("
"); + sb.AppendFormat("", mediaurl); + sb.AppendFormat( + "", + url); + sb.Append(""); + sb.Append("
"); sb.Append("
"); + ""); sb.Append(""); } protected virtual void AddTitle(StringBuilder sb, string url, string title) { - sb.Append(""); - sb.Append(""); + sb.Append(""); sb.Append(""); } protected virtual void AddParagraph(StringBuilder sb, string text) { - sb.Append(""); - sb.Append(""); + sb.Append(""); sb.Append(""); } protected virtual void AddTvParagraph(StringBuilder sb, string episodes, string summary) { - sb.Append(""); - sb.Append(""); + sb.Append(""); sb.Append(""); } protected virtual void AddGenres(StringBuilder sb, string text) { - sb.Append(""); - sb.Append(""); + sb.Append(""); sb.Append(""); } } -} \ No newline at end of file +} diff --git a/src/Ombi.Schedule/Jobs/Ombi/Interfaces/IIssuesPurge.cs b/src/Ombi.Schedule/Jobs/Ombi/Interfaces/IIssuesPurge.cs new file mode 100644 index 000000000..d72b062ce --- /dev/null +++ b/src/Ombi.Schedule/Jobs/Ombi/Interfaces/IIssuesPurge.cs @@ -0,0 +1,8 @@ +using System.Threading.Tasks; + +namespace Ombi.Schedule.Jobs.Ombi +{ + public interface IIssuesPurge : IBaseJob + { + } +} \ No newline at end of file diff --git a/src/Ombi.Schedule/Jobs/Ombi/Interfaces/IMediaDatabaseRefresh.cs b/src/Ombi.Schedule/Jobs/Ombi/Interfaces/IMediaDatabaseRefresh.cs new file mode 100644 index 000000000..06ac9421c --- /dev/null +++ b/src/Ombi.Schedule/Jobs/Ombi/Interfaces/IMediaDatabaseRefresh.cs @@ -0,0 +1,8 @@ +using System.Threading.Tasks; + +namespace Ombi.Schedule.Jobs.Plex.Interfaces +{ + public interface IMediaDatabaseRefresh : IBaseJob + { + } +} \ No newline at end of file diff --git a/src/Ombi.Schedule/Jobs/Ombi/INewsletterJob.cs b/src/Ombi.Schedule/Jobs/Ombi/Interfaces/INewsletterJob.cs similarity index 91% rename from src/Ombi.Schedule/Jobs/Ombi/INewsletterJob.cs rename to src/Ombi.Schedule/Jobs/Ombi/Interfaces/INewsletterJob.cs index 887508d34..d8b5c3f3f 100644 --- a/src/Ombi.Schedule/Jobs/Ombi/INewsletterJob.cs +++ b/src/Ombi.Schedule/Jobs/Ombi/Interfaces/INewsletterJob.cs @@ -5,7 +5,6 @@ namespace Ombi.Schedule.Jobs.Ombi { public interface INewsletterJob : IBaseJob { - Task Start(); Task Start(NewsletterSettings settings, bool test); } } \ No newline at end of file diff --git a/src/Ombi.Schedule/Jobs/Ombi/IOmbiAutomaticUpdater.cs b/src/Ombi.Schedule/Jobs/Ombi/Interfaces/IOmbiAutomaticUpdater.cs similarity index 85% rename from src/Ombi.Schedule/Jobs/Ombi/IOmbiAutomaticUpdater.cs rename to src/Ombi.Schedule/Jobs/Ombi/Interfaces/IOmbiAutomaticUpdater.cs index 48f03b65d..484700c85 100644 --- a/src/Ombi.Schedule/Jobs/Ombi/IOmbiAutomaticUpdater.cs +++ b/src/Ombi.Schedule/Jobs/Ombi/Interfaces/IOmbiAutomaticUpdater.cs @@ -5,7 +5,6 @@ namespace Ombi.Schedule.Jobs.Ombi { public interface IOmbiAutomaticUpdater : IBaseJob { - Task Update(PerformContext context); string[] GetVersion(); Task UpdateAvailable(string branch, string currentVersion); } diff --git a/src/Ombi.Schedule/Jobs/Ombi/IRefreshMetadata.cs b/src/Ombi.Schedule/Jobs/Ombi/Interfaces/IRefreshMetadata.cs similarity index 64% rename from src/Ombi.Schedule/Jobs/Ombi/IRefreshMetadata.cs rename to src/Ombi.Schedule/Jobs/Ombi/Interfaces/IRefreshMetadata.cs index ed13280b0..c29a80994 100644 --- a/src/Ombi.Schedule/Jobs/Ombi/IRefreshMetadata.cs +++ b/src/Ombi.Schedule/Jobs/Ombi/Interfaces/IRefreshMetadata.cs @@ -5,7 +5,5 @@ namespace Ombi.Schedule.Jobs.Ombi { public interface IRefreshMetadata : IBaseJob { - Task Start(); - Task ProcessPlexServerContent(IEnumerable contentIds); } } \ No newline at end of file diff --git a/src/Ombi.Schedule/Jobs/Ombi/Interfaces/IResendFailedRequests.cs b/src/Ombi.Schedule/Jobs/Ombi/Interfaces/IResendFailedRequests.cs new file mode 100644 index 000000000..c740b71d9 --- /dev/null +++ b/src/Ombi.Schedule/Jobs/Ombi/Interfaces/IResendFailedRequests.cs @@ -0,0 +1,9 @@ +using Quartz; +using System.Threading.Tasks; + +namespace Ombi.Schedule.Jobs.Ombi +{ + public interface IResendFailedRequests : IJob + { + } +} \ No newline at end of file diff --git a/src/Ombi.Schedule/Jobs/Ombi/IWelcomeEmail.cs b/src/Ombi.Schedule/Jobs/Ombi/Interfaces/IWelcomeEmail.cs similarity index 76% rename from src/Ombi.Schedule/Jobs/Ombi/IWelcomeEmail.cs rename to src/Ombi.Schedule/Jobs/Ombi/Interfaces/IWelcomeEmail.cs index 6b1270669..b4be7cbef 100644 --- a/src/Ombi.Schedule/Jobs/Ombi/IWelcomeEmail.cs +++ b/src/Ombi.Schedule/Jobs/Ombi/Interfaces/IWelcomeEmail.cs @@ -3,7 +3,7 @@ using Ombi.Store.Entities; namespace Ombi.Schedule.Jobs.Ombi { - public interface IWelcomeEmail : IBaseJob + public interface IWelcomeEmail { Task SendEmail(OmbiUser user); } diff --git a/src/Ombi.Schedule/Jobs/Ombi/Interfaces/IssuesPurge.cs b/src/Ombi.Schedule/Jobs/Ombi/Interfaces/IssuesPurge.cs new file mode 100644 index 000000000..6a16aad70 --- /dev/null +++ b/src/Ombi.Schedule/Jobs/Ombi/Interfaces/IssuesPurge.cs @@ -0,0 +1,64 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using Ombi.Core.Settings; +using Ombi.Settings.Settings.Models; +using Ombi.Store.Entities.Requests; +using Ombi.Store.Repository; +using Quartz; + +namespace Ombi.Schedule.Jobs.Ombi +{ + public class IssuesPurge : IIssuesPurge + { + public IssuesPurge(IRepository issuesRepo, ISettingsService issueSettings) + { + _issuesRepository = issuesRepo; + _settings = issueSettings; + _settings.ClearCache(); + } + + private readonly IRepository _issuesRepository; + private readonly ISettingsService _settings; + + public async Task Execute(IJobExecutionContext job) + { + var settings = await _settings.GetSettingsAsync(); + if (!settings.DeleteIssues) + { + return; + } + + var deletionDate = DateTime.Now.AddDays(settings.DaysAfterResolvedToDelete).Date; + var resolved = _issuesRepository.GetAll().Where(x => x.Status == IssueStatus.Resolved); + var toDelete = resolved.Where(x => x.ResovledDate.HasValue && x.ResovledDate.Value.Date >= deletionDate); + + foreach (var d in toDelete) + { + d.Status = IssueStatus.Deleted; + } + + await _issuesRepository.SaveChangesAsync(); + } + + private bool _disposed; + protected virtual void Dispose(bool disposing) + { + if (_disposed) + return; + + if (disposing) + { + _issuesRepository?.Dispose(); + _settings?.Dispose(); + } + _disposed = true; + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + } +} \ No newline at end of file diff --git a/src/Ombi.Schedule/Jobs/Ombi/MediaDatabaseRefresh.cs b/src/Ombi.Schedule/Jobs/Ombi/MediaDatabaseRefresh.cs new file mode 100644 index 000000000..cf8cbd831 --- /dev/null +++ b/src/Ombi.Schedule/Jobs/Ombi/MediaDatabaseRefresh.cs @@ -0,0 +1,114 @@ +using System; +using System.Threading.Tasks; +using Hangfire; +using Microsoft.Extensions.Logging; +using Ombi.Api.Plex; +using Ombi.Core.Settings; +using Ombi.Core.Settings.Models.External; +using Ombi.Helpers; +using Ombi.Schedule.Jobs.Emby; +using Ombi.Schedule.Jobs.Plex.Interfaces; +using Ombi.Store.Repository; +using Quartz; + +namespace Ombi.Schedule.Jobs.Ombi +{ + public class MediaDatabaseRefresh : IMediaDatabaseRefresh + { + public MediaDatabaseRefresh(ISettingsService s, ILogger log, + IPlexContentRepository plexRepo, IEmbyContentRepository embyRepo) + { + _settings = s; + _log = log; + _plexRepo = plexRepo; + _embyRepo = embyRepo; + _settings.ClearCache(); + } + + private readonly ISettingsService _settings; + private readonly ILogger _log; + private readonly IPlexContentRepository _plexRepo; + private readonly IEmbyContentRepository _embyRepo; + + public async Task Execute(IJobExecutionContext job) + { + try + { + await RemovePlexData(); + await RemoveEmbyData(); + } + catch (Exception e) + { + _log.LogError(LoggingEvents.MediaReferesh, e, "Refreshing Media Data Failed"); + } + + } + + private async Task RemoveEmbyData() + { + try + { + var s = await _settings.GetSettingsAsync(); + if (!s.Enable) + { + return; + } + const string episodeSQL = "DELETE FROM EmbyEpisode"; + const string mainSql = "DELETE FROM EmbyContent"; + await _embyRepo.ExecuteSql(episodeSQL); + await _embyRepo.ExecuteSql(mainSql); + + await OmbiQuartz.TriggerJob(nameof(IEmbyContentSync), "Emby"); + } + catch (Exception e) + { + _log.LogError(LoggingEvents.MediaReferesh, e, "Refreshing Emby Data Failed"); + } + } + + private async Task RemovePlexData() + { + try + { + var s = await _settings.GetSettingsAsync(); + if (!s.Enable) + { + return; + } + + const string episodeSQL = "DELETE FROM PlexEpisode"; + const string seasonsSql = "DELETE FROM PlexSeasonsContent"; + const string mainSql = "DELETE FROM PlexServerContent"; + await _plexRepo.ExecuteSql(episodeSQL); + await _plexRepo.ExecuteSql(seasonsSql); + await _plexRepo.ExecuteSql(mainSql); + } + catch (Exception e) + { + _log.LogError(LoggingEvents.MediaReferesh, e, "Refreshing Plex Data Failed"); + } + } + + + + private bool _disposed; + protected virtual void Dispose(bool disposing) + { + if (_disposed) + return; + + if (disposing) + { + _plexRepo?.Dispose(); + _settings?.Dispose(); + } + _disposed = true; + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + } +} \ No newline at end of file diff --git a/src/Ombi.Schedule/Jobs/Ombi/NewsletterJob.cs b/src/Ombi.Schedule/Jobs/Ombi/NewsletterJob.cs index 6e89d167e..f6d61b30d 100644 --- a/src/Ombi.Schedule/Jobs/Ombi/NewsletterJob.cs +++ b/src/Ombi.Schedule/Jobs/Ombi/NewsletterJob.cs @@ -9,18 +9,25 @@ using MailKit; using Microsoft.AspNetCore.Identity; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; +using MimeKit; +using Ombi.Api.Lidarr; +using Ombi.Api.Lidarr.Models; using Ombi.Api.TheMovieDb; using Ombi.Api.TheMovieDb.Models; using Ombi.Api.TvMaze; using Ombi.Core.Settings; +using Ombi.Core.Settings.Models.External; using Ombi.Helpers; using Ombi.Notifications; using Ombi.Notifications.Models; using Ombi.Notifications.Templates; using Ombi.Settings.Settings.Models; +using Ombi.Settings.Settings.Models.External; using Ombi.Settings.Settings.Models.Notifications; using Ombi.Store.Entities; using Ombi.Store.Repository; +using Quartz; +using ContentType = Ombi.Store.Entities.ContentType; namespace Ombi.Schedule.Jobs.Ombi { @@ -29,7 +36,9 @@ namespace Ombi.Schedule.Jobs.Ombi public NewsletterJob(IPlexContentRepository plex, IEmbyContentRepository emby, IRepository addedLog, IMovieDbApi movieApi, ITvMazeApi tvApi, IEmailProvider email, ISettingsService custom, ISettingsService emailSettings, INotificationTemplatesRepository templateRepo, - UserManager um, ISettingsService newsletter, ILogger log) + UserManager um, ISettingsService newsletter, ILogger log, + ILidarrApi lidarrApi, IRepository albumCache, ISettingsService lidarrSettings, + ISettingsService ombiSettings, ISettingsService plexSettings, ISettingsService embySettings) { _plex = plex; _emby = emby; @@ -42,10 +51,17 @@ namespace Ombi.Schedule.Jobs.Ombi _emailSettings = emailSettings; _newsletterSettings = newsletter; _userManager = um; + _log = log; + _lidarrApi = lidarrApi; + _lidarrAlbumRepository = albumCache; + _lidarrSettings = lidarrSettings; + _ombiSettings = ombiSettings; + _plexSettings = plexSettings; + _embySettings = embySettings; + _ombiSettings.ClearCache(); + _plexSettings.ClearCache(); _emailSettings.ClearCache(); _customizationSettings.ClearCache(); - _newsletterSettings.ClearCache(); - _log = log; } private readonly IPlexContentRepository _plex; @@ -58,8 +74,14 @@ namespace Ombi.Schedule.Jobs.Ombi private readonly INotificationTemplatesRepository _templateRepo; private readonly ISettingsService _emailSettings; private readonly ISettingsService _newsletterSettings; + private readonly ISettingsService _ombiSettings; private readonly UserManager _userManager; private readonly ILogger _log; + private readonly ILidarrApi _lidarrApi; + private readonly IRepository _lidarrAlbumRepository; + private readonly ISettingsService _lidarrSettings; + private readonly ISettingsService _plexSettings; + private readonly ISettingsService _embySettings; public async Task Start(NewsletterSettings settings, bool test) { @@ -87,21 +109,26 @@ namespace Ombi.Schedule.Jobs.Ombi // Get the Content var plexContent = _plex.GetAll().Include(x => x.Episodes).AsNoTracking(); var embyContent = _emby.GetAll().Include(x => x.Episodes).AsNoTracking(); + var lidarrContent = _lidarrAlbumRepository.GetAll().Where(x => x.FullyAvailable).AsNoTracking(); var addedLog = _recentlyAddedLog.GetAll(); - var addedPlexMovieLogIds = addedLog.Where(x => x.Type == RecentlyAddedType.Plex && x.ContentType == ContentType.Parent).Select(x => x.ContentId); - var addedEmbyMoviesLogIds = addedLog.Where(x => x.Type == RecentlyAddedType.Emby && x.ContentType == ContentType.Parent).Select(x => x.ContentId); + var addedPlexMovieLogIds = addedLog.Where(x => x.Type == RecentlyAddedType.Plex && x.ContentType == ContentType.Parent).Select(x => x.ContentId).ToHashSet(); + var addedEmbyMoviesLogIds = addedLog.Where(x => x.Type == RecentlyAddedType.Emby && x.ContentType == ContentType.Parent).Select(x => x.ContentId).ToHashSet(); + var addedAlbumLogIds = addedLog.Where(x => x.Type == RecentlyAddedType.Lidarr && x.ContentType == ContentType.Album).Select(x => x.AlbumId).ToHashSet(); var addedPlexEpisodesLogIds = addedLog.Where(x => x.Type == RecentlyAddedType.Plex && x.ContentType == ContentType.Episode); var addedEmbyEpisodesLogIds = addedLog.Where(x => x.Type == RecentlyAddedType.Emby && x.ContentType == ContentType.Episode); + // Filter out the ones that we haven't sent yet var plexContentMoviesToSend = plexContent.Where(x => x.Type == PlexMediaTypeEntity.Movie && x.HasTheMovieDb && !addedPlexMovieLogIds.Contains(StringHelper.IntParseLinq(x.TheMovieDbId))); var embyContentMoviesToSend = embyContent.Where(x => x.Type == EmbyMediaType.Movie && x.HasTheMovieDb && !addedEmbyMoviesLogIds.Contains(StringHelper.IntParseLinq(x.TheMovieDbId))); + var lidarrContentAlbumsToSend = lidarrContent.Where(x => !addedAlbumLogIds.Contains(x.ForeignAlbumId)).ToHashSet(); _log.LogInformation("Plex Movies to send: {0}", plexContentMoviesToSend.Count()); _log.LogInformation("Emby Movies to send: {0}", embyContentMoviesToSend.Count()); + _log.LogInformation("Albums to send: {0}", lidarrContentAlbumsToSend.Count()); var plexEpisodesToSend = FilterPlexEpisodes(_plex.GetAllEpisodes().Include(x => x.Series).Where(x => x.Series.HasTvDb).AsNoTracking(), addedPlexEpisodesLogIds); @@ -110,6 +137,8 @@ namespace Ombi.Schedule.Jobs.Ombi _log.LogInformation("Plex Episodes to send: {0}", plexEpisodesToSend.Count()); _log.LogInformation("Emby Episodes to send: {0}", embyEpisodesToSend.Count()); + var plexSettings = await _plexSettings.GetSettingsAsync(); + var embySettings = await _embySettings.GetSettingsAsync(); var body = string.Empty; if (test) { @@ -117,11 +146,12 @@ namespace Ombi.Schedule.Jobs.Ombi var embym = embyContent.Where(x => x.Type == EmbyMediaType.Movie ).OrderByDescending(x => x.AddedAt).Take(10); var plext = _plex.GetAllEpisodes().Include(x => x.Series).OrderByDescending(x => x.Series.AddedAt).Take(10).ToHashSet(); var embyt = _emby.GetAllEpisodes().Include(x => x.Series).OrderByDescending(x => x.AddedAt).Take(10).ToHashSet(); - body = await BuildHtml(plexm, embym, plext, embyt, settings); + var lidarr = lidarrContent.OrderByDescending(x => x.AddedAt).Take(10).ToHashSet(); + body = await BuildHtml(plexm, embym, plext, embyt, lidarr, settings, embySettings, plexSettings); } else { - body = await BuildHtml(plexContentMoviesToSend, embyContentMoviesToSend, plexEpisodesToSend, embyEpisodesToSend, settings); + body = await BuildHtml(plexContentMoviesToSend, embyContentMoviesToSend, plexEpisodesToSend, embyEpisodesToSend, lidarrContentAlbumsToSend, settings, embySettings, plexSettings); if (body.IsNullOrEmpty()) { return; @@ -145,7 +175,23 @@ namespace Ombi.Schedule.Jobs.Ombi Email = emails }); } - var emailTasks = new List(); + + var messageContent = ParseTemplate(template, customization); + var email = new NewsletterTemplate(); + + var html = email.LoadTemplate(messageContent.Subject, messageContent.Message, body, customization.Logo); + + var bodyBuilder = new BodyBuilder + { + HtmlBody = html, + }; + + var message = new MimeMessage + { + Body = bodyBuilder.ToMessageBody(), + Subject = messageContent.Subject + }; + foreach (var user in users) { // Get the users to send it to @@ -153,17 +199,13 @@ namespace Ombi.Schedule.Jobs.Ombi { continue; } - - var messageContent = ParseTemplate(template, customization, user); - var email = new NewsletterTemplate(); - - var html = email.LoadTemplate(messageContent.Subject, messageContent.Message, body, customization.Logo); - - emailTasks.Add(_email.Send( - new NotificationMessage { Message = html, Subject = messageContent.Subject, To = user.Email }, - emailSettings)); + // BCC the messages + message.Bcc.Add(new MailboxAddress(user.Email, user.Email)); } + // Send the email + await _email.Send(message, emailSettings); + // Now add all of this to the Recently Added log var recentlyAddedLog = new HashSet(); foreach (var p in plexContentMoviesToSend) @@ -217,7 +259,6 @@ namespace Ombi.Schedule.Jobs.Ombi }); } await _recentlyAddedLog.AddRange(recentlyAddedLog); - await Task.WhenAll(emailTasks.ToArray()); } else { @@ -228,7 +269,7 @@ namespace Ombi.Schedule.Jobs.Ombi { continue; } - var messageContent = ParseTemplate(template, customization, a); + var messageContent = ParseTemplate(template, customization); var email = new NewsletterTemplate(); @@ -248,7 +289,7 @@ namespace Ombi.Schedule.Jobs.Ombi } } - public async Task Start() + public async Task Execute(IJobExecutionContext job) { var newsletterSettings = await _newsletterSettings.GetSettingsAsync(); await Start(newsletterSettings, false); @@ -288,18 +329,21 @@ namespace Ombi.Schedule.Jobs.Ombi return itemsToReturn; } - private NotificationMessageContent ParseTemplate(NotificationTemplates template, CustomizationSettings settings, OmbiUser username) + private NotificationMessageContent ParseTemplate(NotificationTemplates template, CustomizationSettings settings) { var resolver = new NotificationMessageResolver(); var curlys = new NotificationMessageCurlys(); - curlys.SetupNewsletter(settings, username); + curlys.SetupNewsletter(settings); return resolver.ParseMessage(template, curlys); } - private async Task BuildHtml(IQueryable plexContentToSend, IQueryable embyContentToSend, HashSet plexEpisodes, HashSet embyEp, NewsletterSettings settings) + private async Task BuildHtml(IQueryable plexContentToSend, IQueryable embyContentToSend, + HashSet plexEpisodes, HashSet embyEp, HashSet albums, NewsletterSettings settings, EmbySettings embySettings, + PlexSettings plexSettings) { + var ombiSettings = await _ombiSettings.GetSettingsAsync(); var sb = new StringBuilder(); var plexMovies = plexContentToSend.Where(x => x.Type == PlexMediaTypeEntity.Movie); @@ -313,8 +357,16 @@ namespace Ombi.Schedule.Jobs.Ombi sb.Append(""); @@ -331,8 +383,34 @@ namespace Ombi.Schedule.Jobs.Ombi sb.Append(""); + sb.Append(""); + sb.Append("
"); - sb.AppendFormat("", url); - sb.AppendFormat("

{0}

", title); - sb.Append("
"); + sb.Append("
"); + if(url.HasValue()) sb.AppendFormat("", url); + sb.AppendFormat("

{0}

", title); + if (url.HasValue()) sb.Append("
"); sb.Append("
"); - sb.AppendFormat("

{0}

", text); + sb.Append("
"); + sb.AppendFormat("

{0}

", text); sb.Append("
"); - sb.AppendFormat("

{0}

", episodes); - sb.AppendFormat("
{0}
", summary); + sb.Append("
"); + sb.AppendFormat("

{0}

", episodes); + sb.AppendFormat("
{0}
", summary); sb.Append("
"); - sb.AppendFormat("{0}", text); + sb.Append("
"); + sb.AppendFormat("{0}", text); sb.Append("
"); sb.Append(""); sb.Append(""); - await ProcessPlexMovies(plexMovies, sb); - await ProcessEmbyMovies(embyMovies, sb); + if (plexSettings.Enable) + { + await ProcessPlexMovies(plexMovies, sb, ombiSettings.DefaultLanguageCode); + } + + if (embySettings.Enable) + { + await ProcessEmbyMovies(embyMovies, sb, ombiSettings.DefaultLanguageCode, embySettings.Servers.FirstOrDefault()?.ServerHostname ?? string.Empty); + } + sb.Append(""); sb.Append("
"); sb.Append("
"); sb.Append(""); sb.Append(""); - await ProcessPlexTv(plexEpisodes, sb); - await ProcessEmbyTv(embyEp, sb); + if (plexSettings.Enable) + { + await ProcessPlexTv(plexEpisodes, sb); + } + + if (embySettings.Enable) + { + await ProcessEmbyTv(embyEp, sb, embySettings.Servers.FirstOrDefault()?.ServerHostname ?? string.Empty); + } + + sb.Append(""); + sb.Append("
"); + sb.Append("
"); + } + + + if (albums.Any() && !settings.DisableMusic) + { + sb.Append("

New Albums



"); + sb.Append( + ""); + sb.Append(""); + sb.Append(""); @@ -343,7 +421,7 @@ namespace Ombi.Schedule.Jobs.Ombi return sb.ToString(); } - private async Task ProcessPlexMovies(IQueryable plexContentToSend, StringBuilder sb) + private async Task ProcessPlexMovies(IQueryable plexContentToSend, StringBuilder sb, string defaultLanguageCode) { int count = 0; var ordered = plexContentToSend.OrderByDescending(x => x.AddedAt); @@ -354,7 +432,7 @@ namespace Ombi.Schedule.Jobs.Ombi { continue; } - var info = await _movieApi.GetMovieInformationWithExtraInfo(movieDbId); + var info = await _movieApi.GetMovieInformationWithExtraInfo(movieDbId, defaultLanguageCode); var mediaurl = content.Url; if (info == null) { @@ -382,8 +460,42 @@ namespace Ombi.Schedule.Jobs.Ombi } } } + private async Task ProcessAlbums(HashSet albumsToSend, StringBuilder sb) + { + var settings = await _lidarrSettings.GetSettingsAsync(); + int count = 0; + var ordered = albumsToSend.OrderByDescending(x => x.AddedAt); + foreach (var content in ordered) + { + var info = await _lidarrApi.GetAlbumByForeignId(content.ForeignAlbumId, settings.ApiKey, settings.FullUri); + if (info == null) + { + continue; + } + try + { + CreateAlbumHtmlContent(sb, info); + count += 1; + } + catch (Exception e) + { + _log.LogError(e, "Error when Processing Lidarr Album {0}", info.title); + } + finally + { + EndLoopHtml(sb); + } - private async Task ProcessEmbyMovies(IQueryable embyContent, StringBuilder sb) + if (count == 2) + { + count = 0; + sb.Append(""); + sb.Append(""); + } + } + } + + private async Task ProcessEmbyMovies(IQueryable embyContent, StringBuilder sb, string defaultLangaugeCode, string customUrl) { int count = 0; var ordered = embyContent.OrderByDescending(x => x.AddedAt); @@ -404,7 +516,11 @@ namespace Ombi.Schedule.Jobs.Ombi } var mediaurl = content.Url; - var info = await _movieApi.GetMovieInformationWithExtraInfo(StringHelper.IntParseLinq(theMovieDbId)); + if (customUrl.HasValue()) + { + mediaurl = customUrl; + } + var info = await _movieApi.GetMovieInformationWithExtraInfo(StringHelper.IntParseLinq(theMovieDbId), defaultLangaugeCode); if (info == null) { continue; @@ -467,6 +583,41 @@ namespace Ombi.Schedule.Jobs.Ombi } } + private void CreateAlbumHtmlContent(StringBuilder sb, AlbumLookup info) + { + var cover = info.images + .FirstOrDefault(x => x.coverType.Equals("cover", StringComparison.InvariantCultureIgnoreCase))?.url; + if (cover.IsNullOrEmpty()) + { + cover = info.remoteCover; + } + AddBackgroundInsideTable(sb, cover); + var disk = info.images + .FirstOrDefault(x => x.coverType.Equals("disc", StringComparison.InvariantCultureIgnoreCase))?.url; + if (disk.IsNullOrEmpty()) + { + disk = info.remoteCover; + } + AddPosterInsideTable(sb, disk); + + AddMediaServerUrl(sb, string.Empty, string.Empty); + AddInfoTable(sb); + + var releaseDate = $"({info.releaseDate.Year})"; + + AddTitle(sb, string.Empty, $"{info.title} {releaseDate}"); + + var summary = info.artist?.artistName ?? string.Empty; + if (summary.Length > 280) + { + summary = summary.Remove(280); + summary = summary + "...

"; + } + AddParagraph(sb, summary); + + AddGenres(sb, $"Type: {info.albumType}"); + } + private async Task ProcessPlexTv(HashSet plexContent, StringBuilder sb) { var series = new List(); @@ -531,7 +682,7 @@ namespace Ombi.Schedule.Jobs.Ombi var banner = info.image?.original; if (!string.IsNullOrEmpty(banner)) { - banner = banner.Replace("http", "https"); // Always use the Https banners + banner = banner.ToHttpsUrl(); // Always use the Https banners } var tvInfo = await _movieApi.GetTVInfo(t.TheMovieDbId); @@ -549,7 +700,7 @@ namespace Ombi.Schedule.Jobs.Ombi AddInfoTable(sb); var title = ""; - if (!String.IsNullOrEmpty(info.premiered) && info.premiered.Length > 4) + if (!string.IsNullOrEmpty(info.premiered) && info.premiered.Length > 4) { title = $"{t.Title} ({info.premiered.Remove(4)})"; } else @@ -563,7 +714,8 @@ namespace Ombi.Schedule.Jobs.Ombi (key, g) => new { SeasonNumber = key, - Episodes = g.ToList() + Episodes = g.ToList(), + EpisodeAirDate = tvInfo.seasons.Where(x => x.season_number == key).Select(x => x.air_date) } ); @@ -572,8 +724,9 @@ namespace Ombi.Schedule.Jobs.Ombi foreach (var epInformation in results.OrderBy(x => x.SeasonNumber)) { var orderedEpisodes = epInformation.Episodes.OrderBy(x => x.EpisodeNumber).ToList(); - var episodeString = BuildEpisodeList(orderedEpisodes.Select(x => x.EpisodeNumber)); - finalsb.Append($"Season: {epInformation.SeasonNumber} - Episodes: {episodeString}"); + var episodeString = StringHelper.BuildEpisodeList(orderedEpisodes.Select(x => x.EpisodeNumber)); + var episodeAirDate = epInformation.EpisodeAirDate; + finalsb.Append($"Season: {epInformation.SeasonNumber} - Episodes: {episodeString} {episodeAirDate}"); finalsb.Append("
"); } @@ -610,50 +763,9 @@ namespace Ombi.Schedule.Jobs.Ombi } } - public string BuildEpisodeList(IEnumerable orderedEpisodes) - { - var epSb = new StringBuilder(); - var previousEpisodes = new List(); - var previousEpisode = -1; - foreach (var ep in orderedEpisodes) - { - if (ep - 1 == previousEpisode) - { - // This is the next one - previousEpisodes.Add(ep); - } - else - { - if (previousEpisodes.Count > 1) - { - // End it - epSb.Append($"{previousEpisodes.First()}-{previousEpisodes.Last()}, "); - } - else if (previousEpisodes.Count == 1) - { - epSb.Append($"{previousEpisodes.FirstOrDefault()}, "); - } - // New one - previousEpisodes.Clear(); - previousEpisodes.Add(ep); - } - previousEpisode = ep; - } + - if (previousEpisodes.Count > 1) - { - // Got some left over - epSb.Append($"{previousEpisodes.First()}-{previousEpisodes.Last()}"); - } - else if(previousEpisodes.Count == 1) - { - epSb.Append(previousEpisodes.FirstOrDefault()); - } - - return epSb.ToString(); - } - - private async Task ProcessEmbyTv(HashSet embyContent, StringBuilder sb) + private async Task ProcessEmbyTv(HashSet embyContent, StringBuilder sb, string serverUrl) { var series = new List(); foreach (var episode in embyContent) @@ -694,7 +806,7 @@ namespace Ombi.Schedule.Jobs.Ombi var banner = info.image?.original; if (!string.IsNullOrEmpty(banner)) { - banner = banner.Replace("http", "https"); // Always use the Https banners + banner = banner.ToHttpsUrl(); // Always use the Https banners } var tvInfo = await _movieApi.GetTVInfo(t.TheMovieDbId); @@ -708,7 +820,7 @@ namespace Ombi.Schedule.Jobs.Ombi AddBackgroundInsideTable(sb, $"https://image.tmdb.org/t/p/w1280/"); } AddPosterInsideTable(sb, banner); - AddMediaServerUrl(sb, t.Url, banner); + AddMediaServerUrl(sb, serverUrl.HasValue() ? serverUrl : t.Url, banner); AddInfoTable(sb); var title = ""; @@ -727,7 +839,8 @@ namespace Ombi.Schedule.Jobs.Ombi (key, g) => new { SeasonNumber = key, - Episodes = g.ToList() + Episodes = g.ToList(), + EpisodeAirDate = tvInfo.seasons.Where(x => x.season_number == key).Select(x => x.air_date) } ); @@ -736,8 +849,9 @@ namespace Ombi.Schedule.Jobs.Ombi foreach (var epInformation in results.OrderBy(x => x.SeasonNumber)) { var orderedEpisodes = epInformation.Episodes.OrderBy(x => x.EpisodeNumber).ToList(); - var episodeString = BuildEpisodeList(orderedEpisodes.Select(x => x.EpisodeNumber)); - finalsb.Append($"Season: {epInformation.SeasonNumber} - Episodes: {episodeString}"); + var episodeString = StringHelper.BuildEpisodeList(orderedEpisodes.Select(x => x.EpisodeNumber)); + var episodeAirDate = epInformation.EpisodeAirDate; + finalsb.Append($"Season: {epInformation.SeasonNumber} - Episodes: {episodeString} {episodeAirDate}"); finalsb.Append("
"); } diff --git a/src/Ombi.Schedule/Jobs/Ombi/OmbiAutomaticUpdater.cs b/src/Ombi.Schedule/Jobs/Ombi/OmbiAutomaticUpdater.cs index da3b3305c..82187ed50 100644 --- a/src/Ombi.Schedule/Jobs/Ombi/OmbiAutomaticUpdater.cs +++ b/src/Ombi.Schedule/Jobs/Ombi/OmbiAutomaticUpdater.cs @@ -5,18 +5,13 @@ using System.IO; using System.IO.Compression; using System.Linq; using System.Net; -using System.Net.Http; using System.Reflection; using System.Runtime.InteropServices; using System.Text; using System.Threading.Tasks; using Hangfire; -using Hangfire.Console; using Hangfire.Server; using Microsoft.Extensions.Logging; - -using Ombi.Api.Service; -using Ombi.Api.Service.Models; using Ombi.Core.Processor; using Ombi.Core.Settings; using Ombi.Helpers; @@ -25,6 +20,8 @@ using Ombi.Settings.Settings.Models; using Ombi.Store.Entities; using Ombi.Store.Repository; using Ombi.Updater; +using Quartz; +using SharpCompress.Common; using SharpCompress.Readers; using SharpCompress.Readers.Tar; @@ -33,22 +30,20 @@ namespace Ombi.Schedule.Jobs.Ombi public class OmbiAutomaticUpdater : IOmbiAutomaticUpdater { public OmbiAutomaticUpdater(ILogger log, IChangeLogProcessor service, - ISettingsService s, IProcessProvider proc, IRepository appConfig) + ISettingsService s, IProcessProvider proc, IApplicationConfigRepository appConfig) { Logger = log; Processor = service; Settings = s; _processProvider = proc; _appConfig = appConfig; - Settings.ClearCache(); } private ILogger Logger { get; } private IChangeLogProcessor Processor { get; } private ISettingsService Settings { get; } private readonly IProcessProvider _processProvider; - private static PerformContext Ctx { get; set; } - private readonly IRepository _appConfig; + private readonly IApplicationConfigRepository _appConfig; public string[] GetVersion() { @@ -65,14 +60,12 @@ namespace Ombi.Schedule.Jobs.Ombi } - [AutomaticRetry(Attempts = 1)] - public async Task Update(PerformContext c) + public async Task Execute(IJobExecutionContext job) { - Ctx = c; Logger.LogDebug(LoggingEvents.Updater, "Starting Update job"); var settings = await Settings.GetSettingsAsync(); - if (!settings.AutoUpdateEnabled) + if (!settings.AutoUpdateEnabled && !settings.TestMode) { Logger.LogDebug(LoggingEvents.Updater, "Auto update is not enabled"); return; @@ -83,7 +76,7 @@ namespace Ombi.Schedule.Jobs.Ombi var productVersion = AssemblyHelper.GetRuntimeVersion(); Logger.LogDebug(LoggingEvents.Updater, "Product Version {0}", productVersion); - + var serverVersion = string.Empty; try { var productArray = GetVersion(); @@ -96,13 +89,17 @@ namespace Ombi.Schedule.Jobs.Ombi Logger.LogDebug(LoggingEvents.Updater, "Branch {0}", branch); Logger.LogDebug(LoggingEvents.Updater, "Looking for updates now"); + //TODO this fails because the branch = featureupdater when it should be feature/updater var updates = await Processor.Process(branch); Logger.LogDebug(LoggingEvents.Updater, "Updates: {0}", updates); - var serverVersion = updates.UpdateVersionString; + + + serverVersion = updates.UpdateVersionString; Logger.LogDebug(LoggingEvents.Updater, "Service Version {0}", updates.UpdateVersionString); - if (!serverVersion.Equals(version, StringComparison.CurrentCultureIgnoreCase)) + + if (!serverVersion.Equals(version, StringComparison.CurrentCultureIgnoreCase) || settings.TestMode) { // Let's download the correct zip var desc = RuntimeInformation.OSDescription; @@ -135,7 +132,8 @@ namespace Ombi.Schedule.Jobs.Ombi if (process == Architecture.Arm) { download = updates.Downloads.FirstOrDefault(x => x.Name.Contains("arm.", CompareOptions.IgnoreCase)); - } else if (process == Architecture.Arm64) + } + else if (process == Architecture.Arm64) { download = updates.Downloads.FirstOrDefault(x => x.Name.Contains("arm64.", CompareOptions.IgnoreCase)); } @@ -183,7 +181,7 @@ namespace Ombi.Schedule.Jobs.Ombi } Logger.LogDebug(LoggingEvents.Updater, "Starting Download"); - await DownloadAsync(download.Url, zipDir, c); + await DownloadAsync(download.Url, zipDir); Logger.LogDebug(LoggingEvents.Updater, "Finished Download"); } catch (Exception e) @@ -206,33 +204,35 @@ namespace Ombi.Schedule.Jobs.Ombi updaterExtension = ".exe"; } var updaterFile = Path.Combine(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), - "TempUpdate", $"Ombi.Updater{updaterExtension}"); + "TempUpdate", "updater", $"Ombi.Updater{updaterExtension}"); // Make sure the file is an executable - ExecLinuxCommand($"chmod +x {updaterFile}"); + //ExecLinuxCommand($"chmod +x {updaterFile}"); + // There must be an update var start = new ProcessStartInfo { - UseShellExecute = true, - CreateNoWindow = false, // Ignored if UseShellExecute is set to true + UseShellExecute = false, + CreateNoWindow = true, // Ignored if UseShellExecute is set to true FileName = updaterFile, Arguments = GetArgs(settings), WorkingDirectory = Path.Combine(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), "TempUpdate"), }; - if (settings.Username.HasValue()) + //if (settings.Username.HasValue()) + //{ + // start.UserName = settings.Username; + //} + //if (settings.Password.HasValue()) + //{ + // start.Password = settings.Password.ToSecureString(); + //} + using (var proc = new Process { StartInfo = start }) { - start.UserName = settings.Username; + proc.Start(); } - if (settings.Password.HasValue()) - { - start.Password = settings.Password.ToSecureString(); - } - var proc = new Process { StartInfo = start }; - proc.Start(); - Logger.LogDebug(LoggingEvents.Updater, "Bye bye"); } } @@ -245,19 +245,18 @@ namespace Ombi.Schedule.Jobs.Ombi private string GetArgs(UpdateSettings settings) { - var config = _appConfig.GetAll(); - var url = config.FirstOrDefault(x => x.Type == ConfigurationTypes.Url); - var storage = config.FirstOrDefault(x => x.Type == ConfigurationTypes.StoragePath); + var url = _appConfig.Get(ConfigurationTypes.Url); + var storage = _appConfig.Get(ConfigurationTypes.StoragePath); var currentLocation = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location); var processName = (settings.ProcessName.HasValue() ? settings.ProcessName : "Ombi"); var sb = new StringBuilder(); sb.Append($"--applicationPath \"{currentLocation}\" --processname \"{processName}\" "); - if (settings.WindowsService) - { - sb.Append($"--windowsServiceName \"{settings.WindowsServiceName}\" "); - } + //if (settings.WindowsService) + //{ + // sb.Append($"--windowsServiceName \"{settings.WindowsServiceName}\" "); + //} var sb2 = new StringBuilder(); if (url?.Value.HasValue() ?? false) { @@ -321,7 +320,7 @@ namespace Ombi.Schedule.Jobs.Ombi } } - public async Task DownloadAsync(string requestUri, string filename, PerformContext ctx) + public async Task DownloadAsync(string requestUri, string filename) { Logger.LogDebug(LoggingEvents.Updater, "Starting the DownloadAsync"); using (var client = new WebClient()) @@ -338,7 +337,6 @@ namespace Ombi.Schedule.Jobs.Ombi if (disposing) { - _appConfig?.Dispose(); Settings?.Dispose(); } _disposed = true; diff --git a/src/Ombi.Schedule/Jobs/Ombi/RefreshMetadata.cs b/src/Ombi.Schedule/Jobs/Ombi/RefreshMetadata.cs index d58c29ddc..0ccb736dc 100644 --- a/src/Ombi.Schedule/Jobs/Ombi/RefreshMetadata.cs +++ b/src/Ombi.Schedule/Jobs/Ombi/RefreshMetadata.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Threading.Tasks; using Hangfire; using Microsoft.Extensions.Logging; +using Ombi.Api.Emby; using Ombi.Api.TheMovieDb; using Ombi.Api.TheMovieDb.Models; using Ombi.Api.TvMaze; @@ -14,6 +15,7 @@ using Ombi.Schedule.Jobs.Emby; using Ombi.Schedule.Jobs.Plex; using Ombi.Store.Entities; using Ombi.Store.Repository; +using Quartz; namespace Ombi.Schedule.Jobs.Ombi { @@ -21,7 +23,7 @@ namespace Ombi.Schedule.Jobs.Ombi { public RefreshMetadata(IPlexContentRepository plexRepo, IEmbyContentRepository embyRepo, ILogger log, ITvMazeApi tvApi, ISettingsService plexSettings, - IMovieDbApi movieApi, ISettingsService embySettings, IPlexAvailabilityChecker plexAvailability, IEmbyAvaliabilityChecker embyAvaliability) + IMovieDbApi movieApi, ISettingsService embySettings, IEmbyApi embyApi) { _plexRepo = plexRepo; _embyRepo = embyRepo; @@ -30,21 +32,19 @@ namespace Ombi.Schedule.Jobs.Ombi _tvApi = tvApi; _plexSettings = plexSettings; _embySettings = embySettings; - _plexAvailabilityChecker = plexAvailability; - _embyAvaliabilityChecker = embyAvaliability; + _embyApi = embyApi; } private readonly IPlexContentRepository _plexRepo; private readonly IEmbyContentRepository _embyRepo; - private readonly IPlexAvailabilityChecker _plexAvailabilityChecker; - private readonly IEmbyAvaliabilityChecker _embyAvaliabilityChecker; private readonly ILogger _log; private readonly IMovieDbApi _movieApi; private readonly ITvMazeApi _tvApi; private readonly ISettingsService _plexSettings; private readonly ISettingsService _embySettings; + private readonly IEmbyApi _embyApi; - public async Task Start() + public async Task Execute(IJobExecutionContext job) { _log.LogInformation("Starting the Metadata refresh"); try @@ -58,7 +58,7 @@ namespace Ombi.Schedule.Jobs.Ombi var embySettings = await _embySettings.GetSettingsAsync(); if (embySettings.Enable) { - await StartEmby(); + await StartEmby(embySettings); } } catch (Exception e) @@ -68,72 +68,34 @@ namespace Ombi.Schedule.Jobs.Ombi } } - public async Task ProcessPlexServerContent(IEnumerable contentIds) - { - _log.LogInformation("Starting the Metadata refresh from RecentlyAddedSync"); - var plexSettings = await _plexSettings.GetSettingsAsync(); - var embySettings = await _embySettings.GetSettingsAsync(); - try - { - if (plexSettings.Enable) - { - await StartPlexWithKnownContent(contentIds); - } - } - catch (Exception e) - { - _log.LogError(e, "Exception when refreshing the Plex Metadata"); - throw; - } - finally - { - if (plexSettings.Enable) - { - BackgroundJob.Enqueue(() => _plexAvailabilityChecker.Start()); - } - - if (embySettings.Enable) - { - BackgroundJob.Enqueue(() => _embyAvaliabilityChecker.Start()); - - } - } - } - - private async Task StartPlexWithKnownContent(IEnumerable contentids) - { - var everything = _plexRepo.GetAll().Where(x => contentids.Contains(x.Id)); - var allMovies = everything.Where(x => x.Type == PlexMediaTypeEntity.Movie); - await StartPlexMovies(allMovies); - - // Now Tv - var allTv = everything.Where(x => x.Type == PlexMediaTypeEntity.Show); - await StartPlexTv(allTv); - } - private async Task StartPlex() { + // Ensure we check that we have not linked this item to a request var allMovies = _plexRepo.GetAll().Where(x => - x.Type == PlexMediaTypeEntity.Movie && (!x.TheMovieDbId.HasValue() || !x.ImdbId.HasValue())); + x.Type == PlexMediaTypeEntity.Movie && !x.RequestId.HasValue && (!x.TheMovieDbId.HasValue() || !x.ImdbId.HasValue())); await StartPlexMovies(allMovies); // Now Tv var allTv = _plexRepo.GetAll().Where(x => - x.Type == PlexMediaTypeEntity.Show && (!x.TheMovieDbId.HasValue() || !x.ImdbId.HasValue() || !x.TvDbId.HasValue())); + x.Type == PlexMediaTypeEntity.Show && !x.RequestId.HasValue && (!x.TheMovieDbId.HasValue() || !x.ImdbId.HasValue() || !x.TvDbId.HasValue())); await StartPlexTv(allTv); } - private async Task StartEmby() + private async Task StartEmby(EmbySettings s) { - await StartEmbyMovies(); + await StartEmbyMovies(s); await StartEmbyTv(); } private async Task StartPlexTv(IQueryable allTv) { - var tvCount = 0; foreach (var show in allTv) - { + { + // Just double check there is no associated request id + if (show.RequestId.HasValue) + { + continue; + } var hasImdb = show.ImdbId.HasValue(); var hasTheMovieDb = show.TheMovieDbId.HasValue(); var hasTvDbId = show.TvDbId.HasValue(); @@ -157,21 +119,15 @@ namespace Ombi.Schedule.Jobs.Ombi show.TvDbId = id; _plexRepo.UpdateWithoutSave(show); } - tvCount++; - if (tvCount >= 20) - { - await _plexRepo.SaveChangesAsync(); - tvCount = 0; - } + await _plexRepo.SaveChangesAsync(); } - await _plexRepo.SaveChangesAsync(); } private async Task StartEmbyTv() { var allTv = _embyRepo.GetAll().Where(x => x.Type == EmbyMediaType.Series && (!x.TheMovieDbId.HasValue() || !x.ImdbId.HasValue() || !x.TvDbId.HasValue())); - var tvCount = 0; + foreach (var show in allTv) { var hasImdb = show.ImdbId.HasValue(); @@ -197,21 +153,20 @@ namespace Ombi.Schedule.Jobs.Ombi show.TvDbId = id; _embyRepo.UpdateWithoutSave(show); } - tvCount++; - if (tvCount >= 20) - { - await _embyRepo.SaveChangesAsync(); - tvCount = 0; - } + + await _embyRepo.SaveChangesAsync(); } - await _embyRepo.SaveChangesAsync(); } private async Task StartPlexMovies(IQueryable allMovies) { - int movieCount = 0; foreach (var movie in allMovies) { + // Just double check there is no associated request id + if (movie.RequestId.HasValue) + { + continue; + } var hasImdb = movie.ImdbId.HasValue(); var hasTheMovieDb = movie.TheMovieDbId.HasValue(); // Movies don't really use TheTvDb @@ -228,49 +183,64 @@ namespace Ombi.Schedule.Jobs.Ombi movie.TheMovieDbId = id; _plexRepo.UpdateWithoutSave(movie); } - movieCount++; - if (movieCount >= 20) - { - await _plexRepo.SaveChangesAsync(); - movieCount = 0; - } - } - await _plexRepo.SaveChangesAsync(); + await _plexRepo.SaveChangesAsync(); + } } - private async Task StartEmbyMovies() + private async Task StartEmbyMovies(EmbySettings settings) { var allMovies = _embyRepo.GetAll().Where(x => x.Type == EmbyMediaType.Movie && (!x.TheMovieDbId.HasValue() || !x.ImdbId.HasValue())); - int movieCount = 0; foreach (var movie in allMovies) { - var hasImdb = movie.ImdbId.HasValue(); - var hasTheMovieDb = movie.TheMovieDbId.HasValue(); + movie.ImdbId.HasValue(); + movie.TheMovieDbId.HasValue(); // Movies don't really use TheTvDb - if (!hasImdb) + // Check if it even has 1 ID + if (!movie.HasImdb && !movie.HasTheMovieDb) { - var imdbId = await GetImdbId(hasTheMovieDb, false, movie.Title, movie.TheMovieDbId, string.Empty); + // Ok this sucks, + // The only think I can think that has happened is that we scanned Emby before Emby has got the metadata + // So let's recheck emby to see if they have got the metadata now + // + // Yeah your right that does suck - Future Jamie + _log.LogInformation($"Movie {movie.Title} does not have a ImdbId or TheMovieDbId, so rechecking emby"); + foreach (var server in settings.Servers) + { + _log.LogInformation($"Checking server {server.Name} for upto date metadata"); + var movieInfo = await _embyApi.GetMovieInformation(movie.EmbyId, server.ApiKey, server.AdministratorId, + server.FullUri); + + if (movieInfo.ProviderIds?.Imdb.HasValue() ?? false) + { + movie.ImdbId = movieInfo.ProviderIds.Imdb; + } + + if (movieInfo.ProviderIds?.Tmdb.HasValue() ?? false) + { + movie.TheMovieDbId = movieInfo.ProviderIds.Tmdb; + } + } + } + + if (!movie.HasImdb) + { + var imdbId = await GetImdbId(movie.HasTheMovieDb, false, movie.Title, movie.TheMovieDbId, string.Empty); movie.ImdbId = imdbId; _embyRepo.UpdateWithoutSave(movie); } - if (!hasTheMovieDb) + if (!movie.HasTheMovieDb) { - var id = await GetTheMovieDbId(false, hasImdb, string.Empty, movie.ImdbId, movie.Title, true); + var id = await GetTheMovieDbId(false, movie.HasImdb, string.Empty, movie.ImdbId, movie.Title, true); movie.TheMovieDbId = id; _embyRepo.UpdateWithoutSave(movie); } - movieCount++; - if (movieCount >= 20) - { - await _embyRepo.SaveChangesAsync(); - movieCount = 0; - } - } - await _embyRepo.SaveChangesAsync(); + await _embyRepo.SaveChangesAsync(); + + } } private async Task GetTheMovieDbId(bool hasTvDbId, bool hasImdb, string tvdbID, string imdbId, string title, bool movie) @@ -353,7 +323,7 @@ namespace Ombi.Schedule.Jobs.Ombi { var result = await _movieApi.GetTvExternals(id); - return result.tvdb_id.ToString(); + return result.tvdb_id.ToString(); } } diff --git a/src/Ombi.Schedule/Jobs/Ombi/ResendFailedRequests.cs b/src/Ombi.Schedule/Jobs/Ombi/ResendFailedRequests.cs new file mode 100644 index 000000000..344e3a874 --- /dev/null +++ b/src/Ombi.Schedule/Jobs/Ombi/ResendFailedRequests.cs @@ -0,0 +1,76 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Ombi.Core; +using Ombi.Core.Senders; +using Ombi.Store.Entities; +using Ombi.Store.Repository; +using Ombi.Store.Repository.Requests; +using Quartz; + +namespace Ombi.Schedule.Jobs.Ombi +{ + public class ResendFailedRequests : IResendFailedRequests + { + public ResendFailedRequests(IRepository queue, IMovieSender movieSender, ITvSender tvSender, IMusicSender musicSender, + IMovieRequestRepository movieRepo, ITvRequestRepository tvRepo, IMusicRequestRepository music) + { + _requestQueue = queue; + _movieSender = movieSender; + _tvSender = tvSender; + _musicSender = musicSender; + _movieRequestRepository = movieRepo; + _tvRequestRepository = tvRepo; + _musicRequestRepository = music; + } + + private readonly IRepository _requestQueue; + private readonly IMovieSender _movieSender; + private readonly ITvSender _tvSender; + private readonly IMusicSender _musicSender; + private readonly IMovieRequestRepository _movieRequestRepository; + private readonly ITvRequestRepository _tvRequestRepository; + private readonly IMusicRequestRepository _musicRequestRepository; + + public async Task Execute(IJobExecutionContext job) + { + // Get all the failed ones! + var failedRequests = _requestQueue.GetAll().Where(x => !x.Completed.HasValue); + + foreach (var request in failedRequests) + { + if (request.Type == RequestType.Movie) + { + var movieRequest = await _movieRequestRepository.GetAll().FirstOrDefaultAsync(x => x.Id == request.RequestId); + var result = await _movieSender.Send(movieRequest); + if (result.Success) + { + request.Completed = DateTime.UtcNow; + await _requestQueue.SaveChangesAsync(); + } + } + if (request.Type == RequestType.TvShow) + { + var tvRequest = await _tvRequestRepository.GetChild().FirstOrDefaultAsync(x => x.Id == request.RequestId); + var result = await _tvSender.Send(tvRequest); + if (result.Success) + { + request.Completed = DateTime.UtcNow; + await _requestQueue.SaveChangesAsync(); + } + } + if (request.Type == RequestType.Album) + { + var musicRequest = await _musicRequestRepository.GetAll().FirstOrDefaultAsync(x => x.Id == request.RequestId); + var result = await _musicSender.Send(musicRequest); + if (result.Success) + { + request.Completed = DateTime.UtcNow; + await _requestQueue.SaveChangesAsync(); + } + } + } + } + } +} \ No newline at end of file diff --git a/src/Ombi.Schedule/Jobs/Ombi/WelcomeEmail.cs b/src/Ombi.Schedule/Jobs/Ombi/WelcomeEmail.cs index e260ebed3..f98072e9e 100644 --- a/src/Ombi.Schedule/Jobs/Ombi/WelcomeEmail.cs +++ b/src/Ombi.Schedule/Jobs/Ombi/WelcomeEmail.cs @@ -20,8 +20,6 @@ namespace Ombi.Schedule.Jobs.Ombi _email = provider; _templates = template; _customizationSettings = c; - email.ClearCache(); - _customizationSettings.ClearCache(); } private readonly ISettingsService _emailSettings; diff --git a/src/Ombi.Schedule/Jobs/Plex/Interfaces/IPlexAvailabilityChecker.cs b/src/Ombi.Schedule/Jobs/Plex/Interfaces/IPlexAvailabilityChecker.cs index 2d115ab88..112e3be38 100644 --- a/src/Ombi.Schedule/Jobs/Plex/Interfaces/IPlexAvailabilityChecker.cs +++ b/src/Ombi.Schedule/Jobs/Plex/Interfaces/IPlexAvailabilityChecker.cs @@ -5,6 +5,5 @@ namespace Ombi.Schedule.Jobs.Plex { public interface IPlexAvailabilityChecker : IBaseJob { - Task Start(); } } \ No newline at end of file diff --git a/src/Ombi.Schedule/Jobs/Plex/Interfaces/IPlexContentSync.cs b/src/Ombi.Schedule/Jobs/Plex/Interfaces/IPlexContentSync.cs index 17a8bbb4f..9d2752d85 100644 --- a/src/Ombi.Schedule/Jobs/Plex/Interfaces/IPlexContentSync.cs +++ b/src/Ombi.Schedule/Jobs/Plex/Interfaces/IPlexContentSync.cs @@ -1,9 +1,9 @@ using System.Threading.Tasks; +using Quartz; namespace Ombi.Schedule.Jobs { - public interface IPlexContentSync : IBaseJob + public interface IPlexContentSync : IJob { - Task CacheContent(bool recentlyAddedSearch = false); } } \ No newline at end of file diff --git a/src/Ombi.Schedule/Jobs/Plex/Interfaces/IPlexEpisodeSync.cs b/src/Ombi.Schedule/Jobs/Plex/Interfaces/IPlexEpisodeSync.cs index 7de7c3c0c..8eed35066 100644 --- a/src/Ombi.Schedule/Jobs/Plex/Interfaces/IPlexEpisodeSync.cs +++ b/src/Ombi.Schedule/Jobs/Plex/Interfaces/IPlexEpisodeSync.cs @@ -9,7 +9,6 @@ namespace Ombi.Schedule.Jobs.Plex.Interfaces { public interface IPlexEpisodeSync : IBaseJob { - Task Start(); Task> ProcessEpsiodes(Metadata[] episodes, IQueryable currentEpisodes); } } \ No newline at end of file diff --git a/src/Ombi.Schedule/Jobs/Plex/Interfaces/IPlexUserImporter.cs b/src/Ombi.Schedule/Jobs/Plex/Interfaces/IPlexUserImporter.cs index 431ce3ee3..fede60475 100644 --- a/src/Ombi.Schedule/Jobs/Plex/Interfaces/IPlexUserImporter.cs +++ b/src/Ombi.Schedule/Jobs/Plex/Interfaces/IPlexUserImporter.cs @@ -4,6 +4,5 @@ namespace Ombi.Schedule.Jobs.Plex { public interface IPlexUserImporter : IBaseJob { - Task Start(); } } \ No newline at end of file diff --git a/src/Ombi.Schedule/Jobs/Plex/Models/ProcessedContent.cs b/src/Ombi.Schedule/Jobs/Plex/Models/ProcessedContent.cs index be70d0029..fc46e88b7 100644 --- a/src/Ombi.Schedule/Jobs/Plex/Models/ProcessedContent.cs +++ b/src/Ombi.Schedule/Jobs/Plex/Models/ProcessedContent.cs @@ -8,7 +8,7 @@ namespace Ombi.Schedule.Jobs.Plex.Models public IEnumerable Content { get; set; } public IEnumerable Episodes { get; set; } - public bool HasProcessedContent => Content.Any(); - public bool HasProcessedEpisodes => Episodes.Any(); + public bool HasProcessedContent => Content?.Any() ?? false; + public bool HasProcessedEpisodes => Episodes?.Any() ?? false; } } \ No newline at end of file diff --git a/src/Ombi.Schedule/Jobs/Plex/PlexAvailabilityChecker.cs b/src/Ombi.Schedule/Jobs/Plex/PlexAvailabilityChecker.cs index 9978b7e2b..72d7f5a2c 100644 --- a/src/Ombi.Schedule/Jobs/Plex/PlexAvailabilityChecker.cs +++ b/src/Ombi.Schedule/Jobs/Plex/PlexAvailabilityChecker.cs @@ -12,6 +12,7 @@ using Ombi.Store.Entities; using Ombi.Store.Entities.Requests; using Ombi.Store.Repository; using Ombi.Store.Repository.Requests; +using Quartz; namespace Ombi.Schedule.Jobs.Plex { @@ -35,7 +36,7 @@ namespace Ombi.Schedule.Jobs.Plex private readonly IBackgroundJobClient _backgroundJobClient; private readonly ILogger _log; - public async Task Start() + public async Task Execute(IJobExecutionContext job) { try { @@ -44,7 +45,7 @@ namespace Ombi.Schedule.Jobs.Plex } catch (Exception e) { - _log.LogError(e, "Exception thrown in Plex availbility checker"); + _log.LogError(e, "Exception thrown in Plex availability checker"); } } @@ -71,7 +72,7 @@ namespace Ombi.Schedule.Jobs.Plex { useTvDb = true; } - + var tvDbId = child.ParentRequest.TvDbId; var imdbId = child.ParentRequest.ImdbId; IQueryable seriesEpisodes = null; @@ -79,7 +80,7 @@ namespace Ombi.Schedule.Jobs.Plex { seriesEpisodes = plexEpisodes.Where(x => x.Series.ImdbId == imdbId.ToString()); } - if (useTvDb && (seriesEpisodes == null || !seriesEpisodes.Any()) ) + if (useTvDb && (seriesEpisodes == null || !seriesEpisodes.Any())) { seriesEpisodes = plexEpisodes.Where(x => x.Series.TvDbId == tvDbId.ToString()); } @@ -93,8 +94,8 @@ namespace Ombi.Schedule.Jobs.Plex { // Let's try and match the series by name seriesEpisodes = plexEpisodes.Where(x => - x.Series.Title.Equals(child.Title, StringComparison.CurrentCultureIgnoreCase) && - x.Series.ReleaseYear == child.ParentRequest.ReleaseDate.Year.ToString()); + x.Series.Title.Equals(child.Title, StringComparison.InvariantCultureIgnoreCase) && + x.Series.ReleaseYear.Equals(child.ParentRequest.ReleaseDate.Year.ToString(), StringComparison.InvariantCultureIgnoreCase)); } @@ -121,16 +122,18 @@ namespace Ombi.Schedule.Jobs.Plex var allAvailable = child.SeasonRequests.All(x => x.Episodes.All(c => c.Available)); if (allAvailable) { - // We have fulfulled this request! + _log.LogInformation("[PAC] - Child request {0} is now available, sending notification", $"{child.Title} - {child.Id}"); + // We have ful-fulled this request! child.Available = true; - _backgroundJobClient.Enqueue(() => _notificationService.Publish(new NotificationOptions + child.MarkedAsAvailable = DateTime.Now; + await _notificationService.Publish(new NotificationOptions { DateTime = DateTime.Now, NotificationType = NotificationType.RequestAvailable, RequestId = child.Id, RequestType = RequestType.TvShow, Recipient = child.RequestedUser.Email - })); + }); } } @@ -163,20 +166,23 @@ namespace Ombi.Schedule.Jobs.Plex } movie.Available = true; - if (movie.Available) + movie.MarkedAsAvailable = DateTime.Now; + item.RequestId = movie.Id; + + _log.LogInformation("[PAC] - Movie request {0} is now available, sending notification", $"{movie.Title} - {movie.Id}"); + await _notificationService.Publish(new NotificationOptions { - _backgroundJobClient.Enqueue(() => _notificationService.Publish(new NotificationOptions - { - DateTime = DateTime.Now, - NotificationType = NotificationType.RequestAvailable, - RequestId = movie.Id, - RequestType = RequestType.Movie, - Recipient = movie.RequestedUser != null ? movie.RequestedUser.Email : string.Empty - })); - } + DateTime = DateTime.Now, + NotificationType = NotificationType.RequestAvailable, + RequestId = movie.Id, + RequestType = RequestType.Movie, + Recipient = movie.RequestedUser != null ? movie.RequestedUser.Email : string.Empty + }); + } await _movieRepo.Save(); + await _repo.SaveChangesAsync(); } private bool _disposed; diff --git a/src/Ombi.Schedule/Jobs/Plex/PlexContentSync.cs b/src/Ombi.Schedule/Jobs/Plex/PlexContentSync.cs index 796cc89a0..a8d77a71b 100644 --- a/src/Ombi.Schedule/Jobs/Plex/PlexContentSync.cs +++ b/src/Ombi.Schedule/Jobs/Plex/PlexContentSync.cs @@ -42,22 +42,21 @@ using Ombi.Schedule.Jobs.Plex.Interfaces; using Ombi.Schedule.Jobs.Plex.Models; using Ombi.Store.Entities; using Ombi.Store.Repository; +using Quartz; namespace Ombi.Schedule.Jobs.Plex { public class PlexContentSync : IPlexContentSync { public PlexContentSync(ISettingsService plex, IPlexApi plexApi, ILogger logger, IPlexContentRepository repo, - IPlexEpisodeSync epsiodeSync, IRefreshMetadata metadataRefresh, IPlexAvailabilityChecker checker) + IPlexEpisodeSync epsiodeSync) { Plex = plex; PlexApi = plexApi; Logger = logger; Repo = repo; EpisodeSync = epsiodeSync; - Metadata = metadataRefresh; - Checker = checker; - plex.ClearCache(); + Plex.ClearCache(); } private ISettingsService Plex { get; } @@ -65,11 +64,12 @@ namespace Ombi.Schedule.Jobs.Plex private ILogger Logger { get; } private IPlexContentRepository Repo { get; } private IPlexEpisodeSync EpisodeSync { get; } - private IRefreshMetadata Metadata { get; } - private IPlexAvailabilityChecker Checker { get; } - public async Task CacheContent(bool recentlyAddedSearch = false) + public async Task Execute(IJobExecutionContext context) { + JobDataMap dataMap = context.JobDetail.JobDataMap; + var recentlyAddedSearch = dataMap.GetBooleanValueFromString(JobDataKeys.RecentlyAddedSearch); + var plexSettings = await Plex.GetSettingsAsync(); if (!plexSettings.Enable) { @@ -81,7 +81,9 @@ namespace Ombi.Schedule.Jobs.Plex return; } var processedContent = new ProcessedContent(); - Logger.LogInformation("Starting Plex Content Cacher"); + Logger.LogInformation(recentlyAddedSearch + ? "Starting Plex Content Cacher Recently Added Scan" + : "Starting Plex Content Cacher"); try { if (recentlyAddedSearch) @@ -101,19 +103,17 @@ namespace Ombi.Schedule.Jobs.Plex if (!recentlyAddedSearch) { Logger.LogInformation("Starting EP Cacher"); - BackgroundJob.Enqueue(() => EpisodeSync.Start()); + await OmbiQuartz.TriggerJob(nameof(IPlexEpisodeSync), "Plex"); } if ((processedContent?.HasProcessedContent ?? false) && recentlyAddedSearch) { + Logger.LogInformation("Starting Metadata refresh"); // Just check what we send it - BackgroundJob.Enqueue(() => Metadata.ProcessPlexServerContent(processedContent.Content)); + await OmbiQuartz.TriggerJob(nameof(IRefreshMetadata), "System"); } - if ((processedContent?.HasProcessedEpisodes ?? false) && recentlyAddedSearch) - { - BackgroundJob.Enqueue(() => Checker.Start()); - } + Logger.LogInformation("Finished Plex Content Cacher, with processed content: {0}, episodes: {1}. Recently Added Scan: {2}", processedContent?.Content?.Count() ?? 0, processedContent?.Episodes?.Count() ?? 0, recentlyAddedSearch); } private async Task StartTheCache(PlexSettings plexSettings, bool recentlyAddedSearch) @@ -161,7 +161,8 @@ namespace Ombi.Schedule.Jobs.Plex foreach (var content in allContent) { - if (content.viewGroup.Equals(PlexMediaType.Episode.ToString(), StringComparison.CurrentCultureIgnoreCase)) + Logger.LogDebug($"Got type '{content.viewGroup}' to process"); + if (content.viewGroup.Equals(PlexMediaType.Episode.ToString(), StringComparison.InvariantCultureIgnoreCase)) { Logger.LogDebug("Found some episodes, this must be a recently added sync"); var count = 0; @@ -190,10 +191,10 @@ namespace Ombi.Schedule.Jobs.Plex } contentToAdd.Clear(); } - if (count > 200) + if (count > 30) { await Repo.SaveChangesAsync(); - + count = 0; } } @@ -205,7 +206,7 @@ namespace Ombi.Schedule.Jobs.Plex episodesProcessed.AddRange(episodesAdded.Select(x => x.Id)); } } - if (content.viewGroup.Equals(PlexMediaType.Show.ToString(), StringComparison.CurrentCultureIgnoreCase)) + if (content.viewGroup.Equals(PlexMediaType.Show.ToString(), StringComparison.InvariantCultureIgnoreCase)) { // Process Shows Logger.LogDebug("Processing TV Shows"); @@ -227,7 +228,7 @@ namespace Ombi.Schedule.Jobs.Plex } contentToAdd.Clear(); } - if (count > 200) + if (count > 30) { await Repo.SaveChangesAsync(); } @@ -235,7 +236,7 @@ namespace Ombi.Schedule.Jobs.Plex await Repo.SaveChangesAsync(); } - if (content.viewGroup.Equals(PlexMediaType.Movie.ToString(), StringComparison.CurrentCultureIgnoreCase)) + if (content.viewGroup.Equals(PlexMediaType.Movie.ToString(), StringComparison.InvariantCultureIgnoreCase)) { Logger.LogDebug("Processing Movies"); foreach (var movie in content?.Metadata ?? new Metadata[] { }) @@ -381,6 +382,19 @@ namespace Ombi.Schedule.Jobs.Plex if (existingContent != null) { + // Let's make sure that we have some sort of ID e.g. Imdbid for this, + // Looks like it's possible to not have an Id for a show + // I suspect we cached that show just as it was added to Plex. + + if (!existingContent.HasImdb && !existingContent.HasTheMovieDb && !existingContent.HasTvDb) + { + var showMetadata = await PlexApi.GetMetadata(servers.PlexAuthToken, servers.FullUri, + existingContent.Key); + GetProviderIds(showMetadata, existingContent); + + await Repo.Update(existingContent); + } + // Just check the key if (existingKey != null) { @@ -478,10 +492,7 @@ namespace Ombi.Schedule.Jobs.Plex // But it does not contain the `guid` property that we need to pull out thetvdb id... var showMetadata = await PlexApi.GetMetadata(servers.PlexAuthToken, servers.FullUri, show.ratingKey); - var providerIds = - PlexHelper.GetProviderIdFromPlexGuid(showMetadata.MediaContainer.Metadata.FirstOrDefault() - .guid); - + var item = new PlexServerContent { AddedAt = DateTime.Now, @@ -492,20 +503,7 @@ namespace Ombi.Schedule.Jobs.Plex Url = PlexHelper.GetPlexMediaUrl(servers.MachineIdentifier, show.ratingKey), Seasons = new List() }; - if (providerIds.Type == ProviderType.ImdbId) - { - item.ImdbId = providerIds.ImdbId; - } - - if (providerIds.Type == ProviderType.TheMovieDbId) - { - item.TheMovieDbId = providerIds.TheMovieDb; - } - - if (providerIds.Type == ProviderType.TvDbId) - { - item.TvDbId = providerIds.TheTvDb; - } + GetProviderIds(showMetadata, item); // Let's just double check to make sure we do not have it now we have some id's var existingImdb = false; @@ -547,6 +545,27 @@ namespace Ombi.Schedule.Jobs.Plex } } + private static void GetProviderIds(PlexMetadata showMetadata, PlexServerContent existingContent) + { + var providerIds = + PlexHelper.GetProviderIdFromPlexGuid(showMetadata.MediaContainer.Metadata.FirstOrDefault() + .guid); + if (providerIds.Type == ProviderType.ImdbId) + { + existingContent.ImdbId = providerIds.ImdbId; + } + + if (providerIds.Type == ProviderType.TheMovieDbId) + { + existingContent.TheMovieDbId = providerIds.TheMovieDb; + } + + if (providerIds.Type == ProviderType.TvDbId) + { + existingContent.TvDbId = providerIds.TheTvDb; + } + } + /// /// Gets all the library sections. /// If the user has specified only certain libraries then we will only look for those diff --git a/src/Ombi.Schedule/Jobs/Plex/PlexEpisodeSync.cs b/src/Ombi.Schedule/Jobs/Plex/PlexEpisodeSync.cs index 5652d126b..7414294be 100644 --- a/src/Ombi.Schedule/Jobs/Plex/PlexEpisodeSync.cs +++ b/src/Ombi.Schedule/Jobs/Plex/PlexEpisodeSync.cs @@ -13,19 +13,19 @@ using Ombi.Helpers; using Ombi.Schedule.Jobs.Plex.Interfaces; using Ombi.Store.Entities; using Ombi.Store.Repository; +using Quartz; namespace Ombi.Schedule.Jobs.Plex { public class PlexEpisodeSync : IPlexEpisodeSync { public PlexEpisodeSync(ISettingsService s, ILogger log, IPlexApi plexApi, - IPlexContentRepository repo, IPlexAvailabilityChecker a) + IPlexContentRepository repo) { _settings = s; _log = log; _api = plexApi; _repo = repo; - _availabilityChecker = a; _settings.ClearCache(); } @@ -33,9 +33,8 @@ namespace Ombi.Schedule.Jobs.Plex private readonly ILogger _log; private readonly IPlexApi _api; private readonly IPlexContentRepository _repo; - private readonly IPlexAvailabilityChecker _availabilityChecker; - public async Task Start() + public async Task Execute(IJobExecutionContext job) { try { @@ -56,7 +55,8 @@ namespace Ombi.Schedule.Jobs.Plex _log.LogError(LoggingEvents.Cacher, e, "Caching Episodes Failed"); } - BackgroundJob.Enqueue(() => _availabilityChecker.Start()); + + await OmbiQuartz.TriggerJob(nameof(IPlexAvailabilityChecker), "Plex"); } private async Task Cache(PlexServers settings) diff --git a/src/Ombi.Schedule/Jobs/Plex/PlexRecentlyAddedSync.cs b/src/Ombi.Schedule/Jobs/Plex/PlexRecentlyAddedSync.cs index 51596f891..e26f07ba6 100644 --- a/src/Ombi.Schedule/Jobs/Plex/PlexRecentlyAddedSync.cs +++ b/src/Ombi.Schedule/Jobs/Plex/PlexRecentlyAddedSync.cs @@ -1,40 +1,40 @@ -using System; -using System.Threading.Tasks; -using Hangfire; +//using System; +//using System.Threading.Tasks; +//using Hangfire; -namespace Ombi.Schedule.Jobs.Plex -{ - public class PlexRecentlyAddedSync : IPlexRecentlyAddedSync - { - public PlexRecentlyAddedSync(IPlexContentSync sync) - { - _sync = sync; - } +//namespace Ombi.Schedule.Jobs.Plex +//{ +// public class PlexRecentlyAddedSync : IPlexRecentlyAddedSync +// { +// public PlexRecentlyAddedSync(IPlexContentSync sync) +// { +// _sync = sync; +// } - private readonly IPlexContentSync _sync; +// private readonly IPlexContentSync _sync; - public void Start() - { - BackgroundJob.Enqueue(() => _sync.CacheContent(true)); - } +// public void Start() +// { +// BackgroundJob.Enqueue(() => _sync.CacheContent(true)); +// } - private bool _disposed; - protected virtual void Dispose(bool disposing) - { - if (_disposed) - return; +// private bool _disposed; +// protected virtual void Dispose(bool disposing) +// { +// if (_disposed) +// return; - if (disposing) - { - _sync?.Dispose(); - } - _disposed = true; - } +// if (disposing) +// { +// _sync?.Dispose(); +// } +// _disposed = true; +// } - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - } -} \ No newline at end of file +// public void Dispose() +// { +// Dispose(true); +// GC.SuppressFinalize(this); +// } +// } +//} \ No newline at end of file diff --git a/src/Ombi.Schedule/Jobs/Plex/PlexUserImporter.cs b/src/Ombi.Schedule/Jobs/Plex/PlexUserImporter.cs index 4f6bf2550..7d30d780a 100644 --- a/src/Ombi.Schedule/Jobs/Plex/PlexUserImporter.cs +++ b/src/Ombi.Schedule/Jobs/Plex/PlexUserImporter.cs @@ -11,6 +11,7 @@ using Ombi.Core.Settings.Models.External; using Ombi.Helpers; using Ombi.Settings.Settings.Models; using Ombi.Store.Entities; +using Quartz; namespace Ombi.Schedule.Jobs.Plex { @@ -24,8 +25,8 @@ namespace Ombi.Schedule.Jobs.Plex _log = log; _plexSettings = plexSettings; _userManagementSettings = ums; - _userManagementSettings.ClearCache(); _plexSettings.ClearCache(); + _userManagementSettings.ClearCache(); } private readonly IPlexApi _api; @@ -35,7 +36,7 @@ namespace Ombi.Schedule.Jobs.Plex private readonly ISettingsService _userManagementSettings; - public async Task Start() + public async Task Execute(IJobExecutionContext job) { var userManagementSettings = await _userManagementSettings.GetSettingsAsync(); if (!userManagementSettings.ImportPlexUsers) diff --git a/src/Ombi.Schedule/Jobs/Radarr/IRadarrSync.cs b/src/Ombi.Schedule/Jobs/Radarr/IRadarrSync.cs index a9f8edfcd..1fb191fd1 100644 --- a/src/Ombi.Schedule/Jobs/Radarr/IRadarrSync.cs +++ b/src/Ombi.Schedule/Jobs/Radarr/IRadarrSync.cs @@ -6,7 +6,6 @@ namespace Ombi.Schedule.Jobs.Radarr { public interface IRadarrSync : IBaseJob { - Task CacheContent(); Task> GetCachedContent(); } } \ No newline at end of file diff --git a/src/Ombi.Schedule/Jobs/Radarr/RadarrSync.cs b/src/Ombi.Schedule/Jobs/Radarr/RadarrSync.cs index 0aea8cdc2..56499aea6 100644 --- a/src/Ombi.Schedule/Jobs/Radarr/RadarrSync.cs +++ b/src/Ombi.Schedule/Jobs/Radarr/RadarrSync.cs @@ -10,13 +10,14 @@ using Ombi.Helpers; using Ombi.Settings.Settings.Models.External; using Ombi.Store.Context; using Ombi.Store.Entities; +using Quartz; using Serilog; namespace Ombi.Schedule.Jobs.Radarr { public class RadarrSync : IRadarrSync { - public RadarrSync(ISettingsService radarr, IRadarrApi radarrApi, ILogger log, IOmbiContext ctx) + public RadarrSync(ISettingsService radarr, IRadarrApi radarrApi, ILogger log, IExternalContext ctx) { RadarrSettings = radarr; RadarrApi = radarrApi; @@ -28,11 +29,11 @@ namespace Ombi.Schedule.Jobs.Radarr private ISettingsService RadarrSettings { get; } private IRadarrApi RadarrApi { get; } private ILogger Logger { get; } - private readonly IOmbiContext _ctx; + private readonly IExternalContext _ctx; private static readonly SemaphoreSlim SemaphoreSlim = new SemaphoreSlim(1, 1); - public async Task CacheContent() + public async Task Execute(IJobExecutionContext job) { await SemaphoreSlim.WaitAsync(); try @@ -46,10 +47,16 @@ namespace Ombi.Schedule.Jobs.Radarr if (movies != null) { // Let's remove the old cached data - await _ctx.Database.ExecuteSqlCommandAsync("DELETE FROM RadarrCache"); + using (var tran = await _ctx.Database.BeginTransactionAsync()) + { + await _ctx.Database.ExecuteSqlCommandAsync("DELETE FROM RadarrCache"); + tran.Commit(); + } var movieIds = new List(); foreach (var m in movies) + { + if(m.monitored) { if (m.tmdbId > 0) { @@ -64,9 +71,15 @@ namespace Ombi.Schedule.Jobs.Radarr Logger.LogError("TMDBId is not > 0 for movie {0}", m.title); } } - await _ctx.RadarrCache.AddRangeAsync(movieIds); + } - await _ctx.SaveChangesAsync(); + using (var tran = await _ctx.Database.BeginTransactionAsync()) + { + await _ctx.RadarrCache.AddRangeAsync(movieIds); + + await _ctx.SaveChangesAsync(); + tran.Commit(); + } } } catch (System.Exception ex) @@ -110,4 +123,4 @@ namespace Ombi.Schedule.Jobs.Radarr GC.SuppressFinalize(this); } } -} \ No newline at end of file +} diff --git a/src/Ombi.Schedule/Jobs/SickRage/ISickRageSync.cs b/src/Ombi.Schedule/Jobs/SickRage/ISickRageSync.cs index df51698fe..2fcd435a8 100644 --- a/src/Ombi.Schedule/Jobs/SickRage/ISickRageSync.cs +++ b/src/Ombi.Schedule/Jobs/SickRage/ISickRageSync.cs @@ -4,6 +4,5 @@ namespace Ombi.Schedule.Jobs.SickRage { public interface ISickRageSync : IBaseJob { - Task Start(); } } \ No newline at end of file diff --git a/src/Ombi.Schedule/Jobs/SickRage/SickRageSync.cs b/src/Ombi.Schedule/Jobs/SickRage/SickRageSync.cs index d2330197d..b6cca4c78 100644 --- a/src/Ombi.Schedule/Jobs/SickRage/SickRageSync.cs +++ b/src/Ombi.Schedule/Jobs/SickRage/SickRageSync.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Threading; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; @@ -12,12 +11,13 @@ using Ombi.Helpers; using Ombi.Settings.Settings.Models.External; using Ombi.Store.Context; using Ombi.Store.Entities; +using Quartz; namespace Ombi.Schedule.Jobs.SickRage { public class SickRageSync : ISickRageSync { - public SickRageSync(ISettingsService s, ISickRageApi api, ILogger l, IOmbiContext ctx) + public SickRageSync(ISettingsService s, ISickRageApi api, ILogger l, IExternalContext ctx) { _settings = s; _api = api; @@ -29,9 +29,9 @@ namespace Ombi.Schedule.Jobs.SickRage private readonly ISettingsService _settings; private readonly ISickRageApi _api; private readonly ILogger _log; - private readonly IOmbiContext _ctx; + private readonly IExternalContext _ctx; - public async Task Start() + public async Task Execute(IJobExecutionContext job) { try { @@ -46,8 +46,12 @@ namespace Ombi.Schedule.Jobs.SickRage { var srShows = shows.data.Values; var ids = srShows.Select(x => x.tvdbid); + using (var tran = await _ctx.Database.BeginTransactionAsync()) + { + await _ctx.Database.ExecuteSqlCommandAsync("DELETE FROM SickRageCache"); + tran.Commit(); + } - await _ctx.Database.ExecuteSqlCommandAsync("DELETE FROM SickRageCache"); var entites = ids.Select(id => new SickRageCache { TvDbId = id }).ToList(); await _ctx.SickRageCache.AddRangeAsync(entites); @@ -74,8 +78,12 @@ namespace Ombi.Schedule.Jobs.SickRage } - await _ctx.SickRageEpisodeCache.AddRangeAsync(episodesToAdd); - await _ctx.SaveChangesAsync(); + using (var tran = await _ctx.Database.BeginTransactionAsync()) + { + await _ctx.SickRageEpisodeCache.AddRangeAsync(episodesToAdd); + await _ctx.SaveChangesAsync(); + tran.Commit(); + } } } catch (Exception e) diff --git a/src/Ombi.Schedule/Jobs/Sonarr/ISonarrSync.cs b/src/Ombi.Schedule/Jobs/Sonarr/ISonarrSync.cs index a1ba9f3c2..f190be3c5 100644 --- a/src/Ombi.Schedule/Jobs/Sonarr/ISonarrSync.cs +++ b/src/Ombi.Schedule/Jobs/Sonarr/ISonarrSync.cs @@ -4,6 +4,5 @@ namespace Ombi.Schedule.Jobs.Sonarr { public interface ISonarrSync : IBaseJob { - Task Start(); } } \ No newline at end of file diff --git a/src/Ombi.Schedule/Jobs/Sonarr/SonarrSync.cs b/src/Ombi.Schedule/Jobs/Sonarr/SonarrSync.cs index 5ee55d167..f375ef064 100644 --- a/src/Ombi.Schedule/Jobs/Sonarr/SonarrSync.cs +++ b/src/Ombi.Schedule/Jobs/Sonarr/SonarrSync.cs @@ -14,12 +14,13 @@ using Ombi.Helpers; using Ombi.Settings.Settings.Models.External; using Ombi.Store.Context; using Ombi.Store.Entities; +using Quartz; namespace Ombi.Schedule.Jobs.Sonarr { public class SonarrSync : ISonarrSync { - public SonarrSync(ISettingsService s, ISonarrApi api, ILogger l, IOmbiContext ctx) + public SonarrSync(ISettingsService s, ISonarrApi api, ILogger l, IExternalContext ctx) { _settings = s; _api = api; @@ -31,9 +32,9 @@ namespace Ombi.Schedule.Jobs.Sonarr private readonly ISettingsService _settings; private readonly ISonarrApi _api; private readonly ILogger _log; - private readonly IOmbiContext _ctx; + private readonly IExternalContext _ctx; - public async Task Start() + public async Task Execute(IJobExecutionContext job) { try { @@ -47,31 +48,48 @@ namespace Ombi.Schedule.Jobs.Sonarr { var sonarrSeries = series as ImmutableHashSet ?? series.ToImmutableHashSet(); var ids = sonarrSeries.Select(x => x.tvdbId); + using (var tran = await _ctx.Database.BeginTransactionAsync()) + { + await _ctx.Database.ExecuteSqlCommandAsync("DELETE FROM SonarrCache"); + tran.Commit(); + } - await _ctx.Database.ExecuteSqlCommandAsync("DELETE FROM SonarrCache"); var entites = ids.Select(id => new SonarrCache { TvDbId = id }).ToImmutableHashSet(); await _ctx.SonarrCache.AddRangeAsync(entites); entites.Clear(); + using (var tran = await _ctx.Database.BeginTransactionAsync()) + { + await _ctx.Database.ExecuteSqlCommandAsync("DELETE FROM SonarrEpisodeCache"); + tran.Commit(); + } - await _ctx.Database.ExecuteSqlCommandAsync("DELETE FROM SonarrEpisodeCache"); foreach (var s in sonarrSeries) { + if (!s.monitored) + { + continue; + } _log.LogDebug("Syncing series: {0}", s.title); var episodes = await _api.GetEpisodes(s.id, settings.ApiKey, settings.FullUri); var monitoredEpisodes = episodes.Where(x => x.monitored || x.hasFile); // Add to DB _log.LogDebug("We have the episodes, adding to db transaction"); - await _ctx.SonarrEpisodeCache.AddRangeAsync(monitoredEpisodes.Select(episode => new SonarrEpisodeCache + using (var tran = await _ctx.Database.BeginTransactionAsync()) { - EpisodeNumber = episode.episodeNumber, - SeasonNumber = episode.seasonNumber, - TvDbId = s.tvdbId, - HasFile = episode.hasFile - })); - _log.LogDebug("Commiting the transaction"); - await _ctx.SaveChangesAsync(); + await _ctx.SonarrEpisodeCache.AddRangeAsync(monitoredEpisodes.Select(episode => + new SonarrEpisodeCache + { + EpisodeNumber = episode.episodeNumber, + SeasonNumber = episode.seasonNumber, + TvDbId = s.tvdbId, + HasFile = episode.hasFile + })); + _log.LogDebug("Commiting the transaction"); + await _ctx.SaveChangesAsync(); + tran.Commit(); + } } } diff --git a/src/Ombi.Schedule/Ombi.Schedule.csproj b/src/Ombi.Schedule/Ombi.Schedule.csproj index 47e599e80..14d3c5197 100644 --- a/src/Ombi.Schedule/Ombi.Schedule.csproj +++ b/src/Ombi.Schedule/Ombi.Schedule.csproj @@ -10,14 +10,15 @@ - - + + - - + + + @@ -27,12 +28,14 @@ + + diff --git a/src/Ombi.Schedule/OmbiQuartz.cs b/src/Ombi.Schedule/OmbiQuartz.cs new file mode 100644 index 000000000..715bb187b --- /dev/null +++ b/src/Ombi.Schedule/OmbiQuartz.cs @@ -0,0 +1,87 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using Ombi.Helpers; +using Quartz; +using Quartz.Impl; +using Quartz.Spi; + +namespace Ombi.Schedule +{ + public class OmbiQuartz + { + protected IScheduler _scheduler { get; set; } + + public static IScheduler Scheduler => Instance._scheduler; + + // Singleton + protected static OmbiQuartz _instance; + + /// + /// Singleton + /// + public static OmbiQuartz Instance => _instance ?? (_instance = new OmbiQuartz()); + + protected OmbiQuartz() + { + Init(); + } + + private async void Init() + { + _scheduler = await new StdSchedulerFactory().GetScheduler(); + } + + public IScheduler UseJobFactory(IJobFactory jobFactory) + { + Scheduler.JobFactory = jobFactory; + return Scheduler; + } + + public async Task AddJob(string name, string group, string cronExpression, Dictionary jobData = null) + where T : IJob + { + var jobBuilder = JobBuilder.Create() + .WithIdentity(new JobKey(name, group)); + if (jobData != null) + { + foreach (var o in jobData) + { + jobBuilder.UsingJobData(o.Key, o.Value); + } + } + + if(!cronExpression.HasValue()) + { + jobBuilder.StoreDurably(true); + } + + var job = jobBuilder.Build(); + if (cronExpression.HasValue()) + { + ITrigger jobTrigger = TriggerBuilder.Create() + .WithIdentity(name + "Trigger", group) + .WithCronSchedule(cronExpression, + x => x.WithMisfireHandlingInstructionFireAndProceed()) + .ForJob(name, group) + .StartNow() + .Build(); + await Scheduler.ScheduleJob(job, jobTrigger); + } + else + { + await Scheduler.AddJob(job, true); + } + + } + + public static async Task TriggerJob(string jobName, string group) + { + await Scheduler.TriggerJob(new JobKey(jobName, group)); + } + + public static async Task Start() + { + await Scheduler.Start(); + } + } +} \ No newline at end of file diff --git a/src/Ombi.Schedule/OmbiScheduler.cs b/src/Ombi.Schedule/OmbiScheduler.cs new file mode 100644 index 000000000..cfe3bbf27 --- /dev/null +++ b/src/Ombi.Schedule/OmbiScheduler.cs @@ -0,0 +1,97 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Builder; +using Ombi.Core.Settings; +using Ombi.Schedule.Jobs; +using Ombi.Schedule.Jobs.Couchpotato; +using Ombi.Schedule.Jobs.Emby; +using Ombi.Schedule.Jobs.Lidarr; +using Ombi.Schedule.Jobs.Ombi; +using Ombi.Schedule.Jobs.Plex; +using Ombi.Schedule.Jobs.Plex.Interfaces; +using Ombi.Schedule.Jobs.Radarr; +using Ombi.Schedule.Jobs.SickRage; +using Ombi.Schedule.Jobs.Sonarr; +using Ombi.Settings.Settings.Models; +using Quartz; +using Quartz.Spi; + +namespace Ombi.Schedule +{ + public static class OmbiScheduler + { + //public void Setup() + //{ + // CreateJobDefinitions(); + //} + + //public void CreateJobDefinitions() + //{ + // var contentSync = JobBuilder.Create() + // .UsingJobData("recentlyAddedSearch", false) + // .WithIdentity(nameof(PlexContentSync), "Plex") + // .Build(); + + // var recentlyAdded = JobBuilder.Create() + // .UsingJobData("recentlyAddedSearch", true) + // .WithIdentity("PlexRecentlyAdded", "Plex") + // .Build(); + //} + + public static async Task UseQuartz(this IApplicationBuilder app) + { + // Job Factory through IOC container + var jobFactory = (IJobFactory)app.ApplicationServices.GetService(typeof(IJobFactory)); + var service = (ISettingsService)app.ApplicationServices.GetService(typeof(ISettingsService)); + var s = service.GetSettings(); + // Set job factory + OmbiQuartz.Instance.UseJobFactory(jobFactory); + + // Run configuration + await AddPlex(s); + await AddEmby(s); + await AddDvrApps(s); + await AddSystem(s); + + // Run Quartz + await OmbiQuartz.Start(); + } + + private static async Task AddSystem(JobSettings s) + { + await OmbiQuartz.Instance.AddJob(nameof(IRefreshMetadata), "System", null); + await OmbiQuartz.Instance.AddJob(nameof(IIssuesPurge), "System", JobSettingsHelper.IssuePurge(s)); + //OmbiQuartz.Instance.AddJob(nameof(IOmbiAutomaticUpdater), "System", JobSettingsHelper.Updater(s)); + await OmbiQuartz.Instance.AddJob(nameof(INewsletterJob), "System", JobSettingsHelper.Newsletter(s)); + await OmbiQuartz.Instance.AddJob(nameof(IResendFailedRequests), "System", JobSettingsHelper.ResendFailedRequests(s)); + await OmbiQuartz.Instance.AddJob(nameof(IMediaDatabaseRefresh), "System", JobSettingsHelper.MediaDatabaseRefresh(s)); + } + + private static async Task AddDvrApps(JobSettings s) + { + await OmbiQuartz.Instance.AddJob(nameof(ISonarrSync), "DVR", JobSettingsHelper.Sonarr(s)); + await OmbiQuartz.Instance.AddJob(nameof(IRadarrSync), "DVR", JobSettingsHelper.Radarr(s)); + await OmbiQuartz.Instance.AddJob(nameof(ICouchPotatoSync), "DVR", JobSettingsHelper.CouchPotato(s)); + await OmbiQuartz.Instance.AddJob(nameof(ISickRageSync), "DVR", JobSettingsHelper.SickRageSync(s)); + await OmbiQuartz.Instance.AddJob(nameof(ILidarrArtistSync), "DVR", JobSettingsHelper.LidarrArtistSync(s)); + } + + private static async Task AddPlex(JobSettings s) + { + await OmbiQuartz.Instance.AddJob(nameof(IPlexContentSync), "Plex", JobSettingsHelper.PlexContent(s), new Dictionary { { "recentlyAddedSearch", "false" } }); + await OmbiQuartz.Instance.AddJob(nameof(IPlexContentSync) + "RecentlyAdded", "Plex", JobSettingsHelper.PlexRecentlyAdded(s), new Dictionary { { JobDataKeys.RecentlyAddedSearch, "true" } }); + await OmbiQuartz.Instance.AddJob(nameof(IPlexUserImporter), "Plex", JobSettingsHelper.UserImporter(s)); + await OmbiQuartz.Instance.AddJob(nameof(IPlexEpisodeSync), "Plex", null); + await OmbiQuartz.Instance.AddJob(nameof(IPlexAvailabilityChecker), "Plex", null); + } + + private static async Task AddEmby(JobSettings s) + { + await OmbiQuartz.Instance.AddJob(nameof(IEmbyContentSync), "Emby", JobSettingsHelper.EmbyContent(s)); + await OmbiQuartz.Instance.AddJob(nameof(IEmbyEpisodeSync), "Emby", null); + await OmbiQuartz.Instance.AddJob(nameof(IEmbyAvaliabilityChecker), "Emby", null); + await OmbiQuartz.Instance.AddJob(nameof(IEmbyUserImporter), "Emby", JobSettingsHelper.UserImporter(s)); + } + } +} \ No newline at end of file diff --git a/src/Ombi.Schedule/Processor/ChangeLogProcessor.cs b/src/Ombi.Schedule/Processor/ChangeLogProcessor.cs index e645097ef..a34bd89e1 100644 --- a/src/Ombi.Schedule/Processor/ChangeLogProcessor.cs +++ b/src/Ombi.Schedule/Processor/ChangeLogProcessor.cs @@ -174,7 +174,7 @@ namespace Ombi.Schedule.Processor var client = new GitHubClient(Octokit.ProductHeaderValue.Parse("OmbiV3")); var releases = await client.Repository.Release.GetAll("tidusjar", "ombi"); - var latest = releases.FirstOrDefault(x => x.TagName == releaseTag); + var latest = releases.FirstOrDefault(x => x.TagName.Equals(releaseTag, StringComparison.InvariantCultureIgnoreCase)); if (latest.Name.Contains("V2", CompareOptions.IgnoreCase)) { latest = null; diff --git a/src/Ombi.Settings/Ombi.Settings.csproj b/src/Ombi.Settings/Ombi.Settings.csproj index 19a415a47..39e53c9ff 100644 --- a/src/Ombi.Settings/Ombi.Settings.csproj +++ b/src/Ombi.Settings/Ombi.Settings.csproj @@ -9,8 +9,9 @@ - - + + + diff --git a/src/Ombi.Settings/Settings/Models/CustomPageSettings.cs b/src/Ombi.Settings/Settings/Models/CustomPageSettings.cs new file mode 100644 index 000000000..18c0125cb --- /dev/null +++ b/src/Ombi.Settings/Settings/Models/CustomPageSettings.cs @@ -0,0 +1,9 @@ +namespace Ombi.Settings.Settings.Models +{ + public class CustomPageSettings : Settings + { + public string Title { get; set; } + public string Html { get; set; } + public string FontAwesomeIcon { get; set; } + } +} \ No newline at end of file diff --git a/src/Ombi.Settings/Settings/Models/CustomizationSettings.cs b/src/Ombi.Settings/Settings/Models/CustomizationSettings.cs index 120c14bd8..5f0287fc4 100644 --- a/src/Ombi.Settings/Settings/Models/CustomizationSettings.cs +++ b/src/Ombi.Settings/Settings/Models/CustomizationSettings.cs @@ -1,54 +1,16 @@ -using System; -using System.ComponentModel.DataAnnotations.Schema; -using Newtonsoft.Json; -using Ombi.Helpers; - -namespace Ombi.Settings.Settings.Models +namespace Ombi.Settings.Settings.Models { public class CustomizationSettings : Settings { public string ApplicationName { get; set; } public string ApplicationUrl { get; set; } - public string CustomCssLink { get; set; } + public string CustomCss { get; set; } public bool EnableCustomDonations { get; set; } public string CustomDonationUrl { get; set; } public string CustomDonationMessage { get; set; } public string Logo { get; set; } - - public string PresetThemeName { get; set; } - public string PresetThemeContent { get; set; } public bool RecentlyAddedPage { get; set; } - - [NotMapped] - public string PresetThemeVersion - { - get - { - if (HasPresetTheme) - { - var parts = PresetThemeName.Split('-'); - return parts[3].Replace(".css", string.Empty); - } - return string.Empty; - } - } - - [NotMapped] - public string PresetThemeDisplayName - { - get - { - if (HasPresetTheme) - { - var parts = PresetThemeName.Split('-'); - return parts[1]; - } - return string.Empty; - } - } - - [NotMapped] - public bool HasPresetTheme => PresetThemeName.HasValue() || PresetThemeContent.HasValue(); + public bool UseCustomPage { get; set; } public void AddToUrl(string part) { diff --git a/src/Ombi.Settings/Settings/Models/External/EmbySettings.cs b/src/Ombi.Settings/Settings/Models/External/EmbySettings.cs index 96df77281..3ade5746b 100644 --- a/src/Ombi.Settings/Settings/Models/External/EmbySettings.cs +++ b/src/Ombi.Settings/Settings/Models/External/EmbySettings.cs @@ -6,6 +6,7 @@ namespace Ombi.Core.Settings.Models.External public sealed class EmbySettings : Ombi.Settings.Settings.Models.Settings { public bool Enable { get; set; } + public bool IsJellyfin { get; set; } public List Servers { get; set; } = new List(); } @@ -14,6 +15,7 @@ namespace Ombi.Core.Settings.Models.External public string Name { get; set; } public string ApiKey { get; set; } public string AdministratorId { get; set; } + public string ServerHostname { get; set; } public bool EnableEpisodeSearching { get; set; } } } \ No newline at end of file diff --git a/src/Ombi.Settings/Settings/Models/External/LidarrSettings.cs b/src/Ombi.Settings/Settings/Models/External/LidarrSettings.cs new file mode 100644 index 000000000..5f2c5722d --- /dev/null +++ b/src/Ombi.Settings/Settings/Models/External/LidarrSettings.cs @@ -0,0 +1,15 @@ +using Ombi.Core.Settings.Models.External; + +namespace Ombi.Settings.Settings.Models.External +{ + public class LidarrSettings : ExternalSettings + { + public bool Enabled { get; set; } + public string ApiKey { get; set; } + public string DefaultQualityProfile { get; set; } + public string DefaultRootPath { get; set; } + public bool AlbumFolder { get; set; } + public int MetadataProfileId { get; set; } + public bool AddOnly { get; set; } + } +} \ No newline at end of file diff --git a/src/Ombi.Settings/Settings/Models/External/SonarrSettings.cs b/src/Ombi.Settings/Settings/Models/External/SonarrSettings.cs index 0c7e17900..bbbe58fd3 100644 --- a/src/Ombi.Settings/Settings/Models/External/SonarrSettings.cs +++ b/src/Ombi.Settings/Settings/Models/External/SonarrSettings.cs @@ -18,5 +18,7 @@ public string QualityProfileAnime { get; set; } public string RootPathAnime { get; set; } public bool AddOnly { get; set; } + public bool V3 { get; set; } + public int LanguageProfile { get; set; } } } \ No newline at end of file diff --git a/src/Ombi.Settings/Settings/Models/IssueSettings.cs b/src/Ombi.Settings/Settings/Models/IssueSettings.cs index e025c82d1..d7a35c0d9 100644 --- a/src/Ombi.Settings/Settings/Models/IssueSettings.cs +++ b/src/Ombi.Settings/Settings/Models/IssueSettings.cs @@ -4,5 +4,8 @@ { public bool Enabled { get; set; } public bool EnableInProgress { get; set; } + + public bool DeleteIssues { get; set; } + public int DaysAfterResolvedToDelete { get; set; } } } \ No newline at end of file diff --git a/src/Ombi.Settings/Settings/Models/JobSettings.cs b/src/Ombi.Settings/Settings/Models/JobSettings.cs index bb536a685..4376088a3 100644 --- a/src/Ombi.Settings/Settings/Models/JobSettings.cs +++ b/src/Ombi.Settings/Settings/Models/JobSettings.cs @@ -13,5 +13,9 @@ public string SickRageSync { get; set; } public string RefreshMetadata { get; set; } public string Newsletter { get; set; } + public string LidarrArtistSync { get; set; } + public string IssuesPurge { get; set; } + public string RetryRequests { get; set; } + public string MediaDatabaseRefresh { get; set; } } } \ No newline at end of file diff --git a/src/Ombi.Settings/Settings/Models/JobSettingsHelper.cs b/src/Ombi.Settings/Settings/Models/JobSettingsHelper.cs index a5afbeba7..272ad3444 100644 --- a/src/Ombi.Settings/Settings/Models/JobSettingsHelper.cs +++ b/src/Ombi.Settings/Settings/Models/JobSettingsHelper.cs @@ -1,5 +1,6 @@ using System; using Ombi.Helpers; +using Quartz; namespace Ombi.Settings.Settings.Models { @@ -7,55 +8,93 @@ namespace Ombi.Settings.Settings.Models { public static string Radarr(JobSettings s) { - return Get(s.RadarrSync, Cron.Hourly(15)); + return ValidateCron(Get(s.RadarrSync, Cron.Hourly(15))); } public static string Sonarr(JobSettings s) { - return Get(s.SonarrSync, Cron.Hourly(10)); + return ValidateCron(Get(s.SonarrSync, Cron.Hourly(10))); } public static string EmbyContent(JobSettings s) { - return Get(s.EmbyContentSync, Cron.Hourly(5)); + return ValidateCron(Get(s.EmbyContentSync, Cron.Hourly(5))); } + public static string PlexContent(JobSettings s) { - return Get(s.PlexContentSync, Cron.Daily(2)); + return ValidateCron(Get(s.PlexContentSync, Cron.Daily(2))); } + public static string PlexRecentlyAdded(JobSettings s) { - return Get(s.PlexRecentlyAddedSync, Cron.MinuteInterval(30)); + return ValidateCron(Get(s.PlexRecentlyAddedSync, Cron.MinuteInterval(30))); } + public static string CouchPotato(JobSettings s) { - return Get(s.CouchPotatoSync, Cron.Hourly(30)); + return ValidateCron(Get(s.CouchPotatoSync, Cron.Hourly(30))); } public static string Updater(JobSettings s) { - return Get(s.AutomaticUpdater, Cron.HourInterval(6)); + return ValidateCron(Get(s.AutomaticUpdater, Cron.HourInterval(6))); } + public static string UserImporter(JobSettings s) { - return Get(s.UserImporter, Cron.Daily()); + return ValidateCron(Get(s.UserImporter, Cron.Daily())); } + public static string Newsletter(JobSettings s) { - return Get(s.Newsletter, Cron.Weekly(DayOfWeek.Friday, 12)); + return ValidateCron(Get(s.Newsletter, Cron.Weekly(Helpers.DayOfWeek.Friday, 12))); } + public static string SickRageSync(JobSettings s) { - return Get(s.SickRageSync, Cron.Hourly(35)); + return ValidateCron(Get(s.SickRageSync, Cron.Hourly(35))); } + public static string RefreshMetadata(JobSettings s) { - return Get(s.RefreshMetadata, Cron.DayInterval(3)); + return ValidateCron(Get(s.RefreshMetadata, Cron.DayInterval(3))); + } + + public static string LidarrArtistSync(JobSettings s) + { + return ValidateCron(Get(s.LidarrArtistSync, Cron.Hourly(40))); + } + + public static string IssuePurge(JobSettings s) + { + return ValidateCron(Get(s.IssuesPurge, Cron.Daily())); + } + + public static string ResendFailedRequests(JobSettings s) + { + return ValidateCron(Get(s.RetryRequests, Cron.Daily(6))); + } + + public static string MediaDatabaseRefresh(JobSettings s) + { + return ValidateCron(Get(s.MediaDatabaseRefresh, Cron.DayInterval(5))); } private static string Get(string settings, string defaultCron) { return settings.HasValue() ? settings : defaultCron; } + + private const string _defaultCron = "0 0 12 1/1 * ? *"; + + private static string ValidateCron(string cron) + { + if (CronExpression.IsValidExpression(cron)) + { + return cron; + } + return _defaultCron; + } } } \ No newline at end of file diff --git a/src/Ombi.Settings/Settings/Models/Notifications/GotifySettings.cs b/src/Ombi.Settings/Settings/Models/Notifications/GotifySettings.cs new file mode 100644 index 000000000..f0325b0f2 --- /dev/null +++ b/src/Ombi.Settings/Settings/Models/Notifications/GotifySettings.cs @@ -0,0 +1,10 @@ +namespace Ombi.Settings.Settings.Models.Notifications +{ + public class GotifySettings : Settings + { + public bool Enabled { get; set; } + public string BaseUrl { get; set; } + public string ApplicationToken { get; set; } + public sbyte Priority { get; set; } = 4; + } +} \ No newline at end of file diff --git a/src/Ombi.Settings/Settings/Models/Notifications/NewsletterSettings.cs b/src/Ombi.Settings/Settings/Models/Notifications/NewsletterSettings.cs index e79f3182c..3f6416af5 100644 --- a/src/Ombi.Settings/Settings/Models/Notifications/NewsletterSettings.cs +++ b/src/Ombi.Settings/Settings/Models/Notifications/NewsletterSettings.cs @@ -6,6 +6,7 @@ namespace Ombi.Settings.Settings.Models.Notifications { public bool DisableTv { get; set; } public bool DisableMovies { get; set; } + public bool DisableMusic { get; set; } public bool Enabled { get; set; } public List ExternalEmails { get; set; } = new List(); } diff --git a/src/Ombi.Settings/Settings/Models/Notifications/PushoverSettings.cs b/src/Ombi.Settings/Settings/Models/Notifications/PushoverSettings.cs index d845e8695..9c3dfc350 100644 --- a/src/Ombi.Settings/Settings/Models/Notifications/PushoverSettings.cs +++ b/src/Ombi.Settings/Settings/Models/Notifications/PushoverSettings.cs @@ -8,5 +8,7 @@ namespace Ombi.Settings.Settings.Models.Notifications public bool Enabled { get; set; } public string AccessToken { get; set; } public string UserToken { get; set; } + public sbyte Priority { get; set; } = 0; + public string Sound { get; set; } = "pushover"; } } \ No newline at end of file diff --git a/src/Ombi.Settings/Settings/Models/OmbiSettings.cs b/src/Ombi.Settings/Settings/Models/OmbiSettings.cs index bf4d29eb4..e0787326e 100644 --- a/src/Ombi.Settings/Settings/Models/OmbiSettings.cs +++ b/src/Ombi.Settings/Settings/Models/OmbiSettings.cs @@ -4,11 +4,12 @@ { public string BaseUrl { get; set; } public bool CollectAnalyticData { get; set; } + public bool Set { get; set; } public bool Wizard { get; set; } public string ApiKey { get; set; } public bool IgnoreCertificateErrors { get; set; } public bool DoNotSendNotificationsForAutoApprove { get; set; } public bool HideRequestsUsers { get; set; } - + public string DefaultLanguageCode { get; set; } = "en"; } } \ No newline at end of file diff --git a/src/Ombi.Settings/Settings/Models/UpdateSettings.cs b/src/Ombi.Settings/Settings/Models/UpdateSettings.cs index 23c80c392..4180ab760 100644 --- a/src/Ombi.Settings/Settings/Models/UpdateSettings.cs +++ b/src/Ombi.Settings/Settings/Models/UpdateSettings.cs @@ -10,5 +10,6 @@ public string ScriptLocation { get; set; } public string WindowsServiceName { get; set; } public bool WindowsService { get; set; } + public bool TestMode { get; set; } } } \ No newline at end of file diff --git a/src/Ombi.Settings/Settings/Models/VoteSettings.cs b/src/Ombi.Settings/Settings/Models/VoteSettings.cs new file mode 100644 index 000000000..4d63cfea4 --- /dev/null +++ b/src/Ombi.Settings/Settings/Models/VoteSettings.cs @@ -0,0 +1,10 @@ +namespace Ombi.Settings.Settings.Models +{ + public class VoteSettings : Settings + { + public bool Enabled { get; set; } + public int MovieVoteMax { get; set; } + public int MusicVoteMax { get; set; } + public int TvShowVoteMax { get; set; } + } +} \ No newline at end of file diff --git a/src/Ombi.Settings/Settings/SettingsService.cs b/src/Ombi.Settings/Settings/SettingsService.cs index 3162c34c2..57ae482a2 100644 --- a/src/Ombi.Settings/Settings/SettingsService.cs +++ b/src/Ombi.Settings/Settings/SettingsService.cs @@ -61,7 +61,7 @@ namespace Ombi.Settings.Settings var model = obj; return model; - }, DateTime.Now.AddHours(2)); + }, DateTime.Now.AddHours(5)); } public bool SaveSettings(T model) diff --git a/src/Ombi.Store/Context/ExternalContext.cs b/src/Ombi.Store/Context/ExternalContext.cs new file mode 100644 index 000000000..19cb77fc4 --- /dev/null +++ b/src/Ombi.Store/Context/ExternalContext.cs @@ -0,0 +1,74 @@ +using System.IO; +using Microsoft.EntityFrameworkCore; +using Ombi.Helpers; +using Ombi.Store.Entities; + +namespace Ombi.Store.Context +{ + public sealed class ExternalContext : DbContext, IExternalContext + { + private static bool _created; + public ExternalContext() + { + if (_created) return; + + _created = true; + Database.SetCommandTimeout(60); + Database.Migrate(); + } + + public DbSet PlexServerContent { get; set; } + public DbSet PlexSeasonsContent { get; set; } + public DbSet PlexEpisode { get; set; } + public DbSet RadarrCache { get; set; } + public DbSet CouchPotatoCache { get; set; } + public DbSet EmbyContent { get; set; } + public DbSet EmbyEpisode { get; set; } + + public DbSet SonarrCache { get; set; } + public DbSet LidarrArtistCache { get; set; } + public DbSet LidarrAlbumCache { get; set; } + public DbSet SonarrEpisodeCache { get; set; } + public DbSet SickRageCache { get; set; } + public DbSet SickRageEpisodeCache { get; set; } + + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + { + var i = StoragePathSingleton.Instance; + if (string.IsNullOrEmpty(i.StoragePath)) + { + i.StoragePath = string.Empty; + } + optionsBuilder.UseSqlite($"Data Source={Path.Combine(i.StoragePath, "OmbiExternal.db")}"); + } + + protected override void OnModelCreating(ModelBuilder builder) + { + builder.Entity().HasMany(x => x.Episodes) + .WithOne(x => x.Series) + .HasPrincipalKey(x => x.Key) + .HasForeignKey(x => x.GrandparentKey); + + builder.Entity() + .HasOne(p => p.Series) + .WithMany(b => b.Episodes) + .HasPrincipalKey(x => x.EmbyId) + .HasForeignKey(p => p.ParentId); + + base.OnModelCreating(builder); + } + + + public void Seed() + { + // VACUUM; + Database.ExecuteSqlCommand("VACUUM;"); + + using (var tran = Database.BeginTransaction()) + { + SaveChanges(); + tran.Commit(); + } + } + } +} \ No newline at end of file diff --git a/src/Ombi.Store/Context/IDbContext.cs b/src/Ombi.Store/Context/IDbContext.cs new file mode 100644 index 000000000..d84aaaa3d --- /dev/null +++ b/src/Ombi.Store/Context/IDbContext.cs @@ -0,0 +1,22 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.ChangeTracking; +using Microsoft.EntityFrameworkCore.Infrastructure; + +namespace Ombi.Store.Context +{ + public interface IDbContext : IDisposable + { + EntityEntry Update(object entity); + EntityEntry Update(TEntity entity) where TEntity : class; + int SaveChanges(); + void Seed(); + Task SaveChangesAsync(CancellationToken cancellationToken = default(CancellationToken)); + DatabaseFacade Database { get; } + EntityEntry Entry(T entry) where T : class; + EntityEntry Attach(TEntity entity) where TEntity : class; + DbSet Set() where TEntity : class; + } +} \ No newline at end of file diff --git a/src/Ombi.Store/Context/IExternalContext.cs b/src/Ombi.Store/Context/IExternalContext.cs new file mode 100644 index 000000000..3f5d79a79 --- /dev/null +++ b/src/Ombi.Store/Context/IExternalContext.cs @@ -0,0 +1,21 @@ +using Microsoft.EntityFrameworkCore; +using Ombi.Store.Entities; + +namespace Ombi.Store.Context +{ + public interface IExternalContext : IDbContext + { + DbSet CouchPotatoCache { get; set; } + DbSet EmbyContent { get; set; } + DbSet EmbyEpisode { get; set; } + DbSet LidarrAlbumCache { get; set; } + DbSet LidarrArtistCache { get; set; } + DbSet PlexEpisode { get; set; } + DbSet PlexServerContent { get; set; } + DbSet RadarrCache { get; set; } + DbSet SickRageCache { get; set; } + DbSet SickRageEpisodeCache { get; set; } + DbSet SonarrCache { get; set; } + DbSet SonarrEpisodeCache { get; set; } + } +} \ No newline at end of file diff --git a/src/Ombi.Store/Context/IOmbiContext.cs b/src/Ombi.Store/Context/IOmbiContext.cs index 0c716c7c4..a7fb3b47d 100644 --- a/src/Ombi.Store/Context/IOmbiContext.cs +++ b/src/Ombi.Store/Context/IOmbiContext.cs @@ -9,37 +9,34 @@ using Ombi.Store.Entities.Requests; namespace Ombi.Store.Context { - public interface IOmbiContext : IDisposable + public interface IOmbiContext : IDbContext { - int SaveChanges(); - Task SaveChangesAsync(CancellationToken cancellationToken = default(CancellationToken)); + + //DbSet PlexServerContent { get; set; } + //DbSet PlexEpisode { get; set; } DbSet Settings { get; set; } - DbSet PlexServerContent { get; set; } - DbSet PlexEpisode { get; set; } - DbSet RadarrCache { get; set; } - DbSet EmbyContent { get; set; } - DbSet EmbyEpisode { get; set; } - DatabaseFacade Database { get; } - EntityEntry Entry(T entry) where T : class; - EntityEntry Attach(TEntity entity) where TEntity : class; - DbSet Set() where TEntity : class; + //DbSet RadarrCache { get; set; } + //DbSet EmbyContent { get; set; } + //DbSet EmbyEpisode { get; set; } DbSet NotificationTemplates { get; set; } DbSet ApplicationConfigurations { get; set; } + DbSet Votes { get; set; } void Seed(); DbSet Audit { get; set; } DbSet MovieRequests { get; set; } + DbSet AlbumRequests { get; set; } DbSet TvRequests { get; set; } DbSet ChildRequests { get; set; } DbSet Issues { get; set; } DbSet IssueCategories { get; set; } DbSet Tokens { get; set; } DbSet SonarrCache { get; set; } - DbSet SonarrEpisodeCache { get; set; } - EntityEntry Update(object entity); - EntityEntry Update(TEntity entity) where TEntity : class; - DbSet CouchPotatoCache { get; set; } - DbSet SickRageCache { get; set; } - DbSet SickRageEpisodeCache { get; set; } + //DbSet SonarrEpisodeCache { get; set; } + //DbSet CouchPotatoCache { get; set; } + //DbSet SickRageCache { get; set; } + //DbSet LidarrArtistCache { get; set; } + //DbSet LidarrAlbumCache { get; set; } + //DbSet SickRageEpisodeCache { get; set; } DbSet RequestLogs { get; set; } DbSet RecentlyAddedLogs { get; set; } DbSet RequestSubscription { get; set; } diff --git a/src/Ombi.Store/Context/ISettingsContext.cs b/src/Ombi.Store/Context/ISettingsContext.cs new file mode 100644 index 000000000..3c209c68a --- /dev/null +++ b/src/Ombi.Store/Context/ISettingsContext.cs @@ -0,0 +1,11 @@ +using Microsoft.EntityFrameworkCore; +using Ombi.Store.Entities; + +namespace Ombi.Store.Context +{ + public interface ISettingsContext : IDbContext + { + DbSet ApplicationConfigurations { get; set; } + DbSet Settings { get; set; } + } +} \ No newline at end of file diff --git a/src/Ombi.Store/Context/OmbiContext.cs b/src/Ombi.Store/Context/OmbiContext.cs index 8d6036877..28b27107e 100644 --- a/src/Ombi.Store/Context/OmbiContext.cs +++ b/src/Ombi.Store/Context/OmbiContext.cs @@ -17,20 +17,25 @@ namespace Ombi.Store.Context { if (_created) return; + _created = true; + Database.SetCommandTimeout(60); Database.Migrate(); } public DbSet NotificationTemplates { get; set; } - public DbSet Settings { get; set; } + public DbSet ApplicationConfigurations { get; set; } public DbSet PlexServerContent { get; set; } + public DbSet PlexSeasonsContent { get; set; } public DbSet PlexEpisode { get; set; } + public DbSet Settings { get; set; } public DbSet RadarrCache { get; set; } public DbSet CouchPotatoCache { get; set; } public DbSet EmbyContent { get; set; } public DbSet EmbyEpisode { get; set; } public DbSet MovieRequests { get; set; } + public DbSet AlbumRequests { get; set; } public DbSet TvRequests { get; set; } public DbSet ChildRequests { get; set; } @@ -39,17 +44,21 @@ namespace Ombi.Store.Context public DbSet IssueComments { get; set; } public DbSet RequestLogs { get; set; } public DbSet RecentlyAddedLogs { get; set; } + public DbSet Votes { get; set; } public DbSet Audit { get; set; } public DbSet Tokens { get; set; } public DbSet SonarrCache { get; set; } + public DbSet LidarrArtistCache { get; set; } + public DbSet LidarrAlbumCache { get; set; } public DbSet SonarrEpisodeCache { get; set; } public DbSet SickRageCache { get; set; } public DbSet SickRageEpisodeCache { get; set; } public DbSet RequestSubscription { get; set; } - - public DbSet ApplicationConfigurations { get; set; } + public DbSet UserNotificationPreferences { get; set; } + public DbSet UserQualityProfileses { get; set; } + public DbSet RequestQueue { get; set; } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { @@ -81,64 +90,21 @@ namespace Ombi.Store.Context public void Seed() { - // Add the tokens - var fanArt = ApplicationConfigurations.FirstOrDefault(x => x.Type == ConfigurationTypes.FanartTv); - if (fanArt == null) + using (var tran = Database.BeginTransaction()) { - ApplicationConfigurations.Add(new ApplicationConfiguration + // Make sure we have the API User + var apiUserExists = Users.Any(x => x.UserName.Equals("Api", StringComparison.CurrentCultureIgnoreCase)); + if (!apiUserExists) { - Type = ConfigurationTypes.FanartTv, - Value = "4b6d983efa54d8f45c68432521335f15" - }); - SaveChanges(); - } - var movieDb = ApplicationConfigurations.FirstOrDefault(x => x.Type == ConfigurationTypes.FanartTv); - if (movieDb == null) - { - ApplicationConfigurations.Add(new ApplicationConfiguration - { - Type = ConfigurationTypes.TheMovieDb, - Value = "b8eabaf5608b88d0298aa189dd90bf00" - }); - SaveChanges(); - } - var notification = ApplicationConfigurations.FirstOrDefault(x => x.Type == ConfigurationTypes.Notification); - if (notification == null) - { - ApplicationConfigurations.Add(new ApplicationConfiguration - { - Type = ConfigurationTypes.Notification, - Value = "4f0260c4-9c3d-41ab-8d68-27cb5a593f0e" - }); - SaveChanges(); - } - - // VACUUM; - Database.ExecuteSqlCommand("VACUUM;"); - - // Make sure we have the roles - var roles = Roles.Where(x => x.Name == OmbiRoles.ReceivesNewsletter); - if (!roles.Any()) - { - Roles.Add(new IdentityRole(OmbiRoles.ReceivesNewsletter) - { - NormalizedName = OmbiRoles.ReceivesNewsletter.ToUpper() - }); - SaveChanges(); - } - - // Make sure we have the API User - var apiUserExists = Users.Any(x => x.UserName.Equals("Api", StringComparison.CurrentCultureIgnoreCase)); - if (!apiUserExists) - { - Users.Add(new OmbiUser - { - UserName = "Api", - UserType = UserType.SystemUser, - NormalizedUserName = "API", - - }); - SaveChanges(); + Users.Add(new OmbiUser + { + UserName = "Api", + UserType = UserType.SystemUser, + NormalizedUserName = "API", + }); + SaveChanges(); + tran.Commit(); + } } //Check if templates exist @@ -147,6 +113,7 @@ namespace Ombi.Store.Context var allAgents = Enum.GetValues(typeof(NotificationAgent)).Cast().ToList(); var allTypes = Enum.GetValues(typeof(NotificationType)).Cast().ToList(); + var needToSave = false; foreach (var agent in allAgents) { foreach (var notificationType in allTypes) @@ -156,6 +123,8 @@ namespace Ombi.Store.Context // We already have this continue; } + + needToSave = true; NotificationTemplates notificationToAdd; switch (notificationType) { @@ -212,7 +181,15 @@ namespace Ombi.Store.Context }; break; case NotificationType.ItemAddedToFaultQueue: - continue; + notificationToAdd = new NotificationTemplates + { + NotificationType = notificationType, + Message = "Hello! The user '{UserName}' has requested {Title} but it could not be added. This has been added into the requests queue and will keep retrying", + Subject = "Item Added To Retry Queue", + Agent = agent, + Enabled = true, + }; + break; case NotificationType.WelcomeEmail: notificationToAdd = new NotificationTemplates { @@ -262,7 +239,16 @@ namespace Ombi.Store.Context NotificationTemplates.Add(notificationToAdd); } } - SaveChanges(); + + if (needToSave) + { + + using (var tran = Database.BeginTransaction()) + { + SaveChanges(); + tran.Commit(); + } + } } } } \ No newline at end of file diff --git a/src/Ombi.Store/Context/SettingsContext.cs b/src/Ombi.Store/Context/SettingsContext.cs new file mode 100644 index 000000000..48ef0e6c7 --- /dev/null +++ b/src/Ombi.Store/Context/SettingsContext.cs @@ -0,0 +1,77 @@ +using System.IO; +using System.Linq; +using Microsoft.EntityFrameworkCore; +using Ombi.Helpers; +using Ombi.Store.Entities; + +namespace Ombi.Store.Context +{ + public sealed class SettingsContext : DbContext, ISettingsContext + { + private static bool _created; + public SettingsContext() + { + if (_created) return; + + _created = true; + Database.SetCommandTimeout(60); + Database.Migrate(); + } + + public DbSet Settings { get; set; } + public DbSet ApplicationConfigurations { get; set; } + + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + { + var i = StoragePathSingleton.Instance; + if (string.IsNullOrEmpty(i.StoragePath)) + { + i.StoragePath = string.Empty; + } + optionsBuilder.UseSqlite($"Data Source={Path.Combine(i.StoragePath, "OmbiSettings.db")}"); + } + + public void Seed() + { + + using (var tran = Database.BeginTransaction()) + { + // Add the tokens + var fanArt = ApplicationConfigurations.FirstOrDefault(x => x.Type == ConfigurationTypes.FanartTv); + if (fanArt == null) + { + ApplicationConfigurations.Add(new ApplicationConfiguration + { + Type = ConfigurationTypes.FanartTv, + Value = "4b6d983efa54d8f45c68432521335f15" + }); + SaveChanges(); + } + + var movieDb = ApplicationConfigurations.FirstOrDefault(x => x.Type == ConfigurationTypes.FanartTv); + if (movieDb == null) + { + ApplicationConfigurations.Add(new ApplicationConfiguration + { + Type = ConfigurationTypes.TheMovieDb, + Value = "b8eabaf5608b88d0298aa189dd90bf00" + }); + SaveChanges(); + } + + var notification = + ApplicationConfigurations.FirstOrDefault(x => x.Type == ConfigurationTypes.Notification); + if (notification == null) + { + ApplicationConfigurations.Add(new ApplicationConfiguration + { + Type = ConfigurationTypes.Notification, + Value = "4f0260c4-9c3d-41ab-8d68-27cb5a593f0e" + }); + SaveChanges(); + } + tran.Commit(); + } + } + } +} \ No newline at end of file diff --git a/src/Ombi.Store/Entities/LidarrAlbumCache.cs b/src/Ombi.Store/Entities/LidarrAlbumCache.cs new file mode 100644 index 000000000..03099face --- /dev/null +++ b/src/Ombi.Store/Entities/LidarrAlbumCache.cs @@ -0,0 +1,23 @@ +using System; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Ombi.Store.Entities +{ + [Table("LidarrAlbumCache")] + public class LidarrAlbumCache : Entity + { + public int ArtistId { get; set; } + public string ForeignAlbumId { get; set; } + public int TrackCount { get; set; } + public DateTime ReleaseDate { get; set; } + public bool Monitored { get; set; } + public string Title { get; set; } + public decimal PercentOfTracks { get; set; } + public DateTime AddedAt { get; set; } + + [NotMapped] + public bool PartiallyAvailable => PercentOfTracks != 100 && PercentOfTracks > 0; + [NotMapped] + public bool FullyAvailable => PercentOfTracks == 100; + } +} \ No newline at end of file diff --git a/src/Ombi.Store/Entities/LidarrArtistCache.cs b/src/Ombi.Store/Entities/LidarrArtistCache.cs new file mode 100644 index 000000000..dd78b4e2c --- /dev/null +++ b/src/Ombi.Store/Entities/LidarrArtistCache.cs @@ -0,0 +1,14 @@ +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Ombi.Store.Entities +{ + [Table("LidarrArtistCache")] + public class LidarrArtistCache : Entity + { + public int ArtistId { get; set; } + public string ArtistName { get; set; } + public string ForeignArtistId { get; set; } + public bool Monitored { get; set; } + } +} \ No newline at end of file diff --git a/src/Ombi.Store/Entities/OmbiUser.cs b/src/Ombi.Store/Entities/OmbiUser.cs index f67183982..edb9708a9 100644 --- a/src/Ombi.Store/Entities/OmbiUser.cs +++ b/src/Ombi.Store/Entities/OmbiUser.cs @@ -23,16 +23,18 @@ namespace Ombi.Store.Entities public int? MovieRequestLimit { get; set; } public int? EpisodeRequestLimit { get; set; } + public int? MusicRequestLimit { get; set; } public string UserAccessToken { get; set; } public List NotificationUserIds { get; set; } + public List UserNotificationPreferences { get; set; } [NotMapped] public bool IsEmbyConnect => UserType == UserType.EmbyUser && EmbyConnectUserId.HasValue(); [NotMapped] - public string UserAlias => string.IsNullOrEmpty(Alias) ? UserName : Alias; + public virtual string UserAlias => string.IsNullOrEmpty(Alias) ? UserName : Alias; [NotMapped] public bool EmailLogin { get; set; } @@ -59,5 +61,6 @@ namespace Ombi.Store.Entities get => base.ConcurrencyStamp; set => base.ConcurrencyStamp = value; } + } } \ No newline at end of file diff --git a/src/Ombi.Store/Entities/PlexServerContent.cs b/src/Ombi.Store/Entities/PlexServerContent.cs index 14028cb57..f8e0e01d4 100644 --- a/src/Ombi.Store/Entities/PlexServerContent.cs +++ b/src/Ombi.Store/Entities/PlexServerContent.cs @@ -42,13 +42,9 @@ namespace Ombi.Store.Entities public PlexMediaTypeEntity Type { get; set; } public string Url { get; set; } - - /// - /// Only used for TV Shows - /// - public virtual ICollection Seasons { get; set; } public ICollection Episodes { get; set; } + public ICollection Seasons { get; set; } /// /// Plex's internal ID for this item @@ -57,6 +53,8 @@ namespace Ombi.Store.Entities public DateTime AddedAt { get; set; } public string Quality { get; set; } + public int? RequestId { get; set; } + [NotMapped] public bool HasImdb => !string.IsNullOrEmpty(ImdbId); diff --git a/src/Ombi.Store/Entities/RecentlyAddedLog.cs b/src/Ombi.Store/Entities/RecentlyAddedLog.cs index 1ef091149..782d89e3f 100644 --- a/src/Ombi.Store/Entities/RecentlyAddedLog.cs +++ b/src/Ombi.Store/Entities/RecentlyAddedLog.cs @@ -11,18 +11,21 @@ namespace Ombi.Store.Entities public int ContentId { get; set; } // This is dependant on the type, it's either TMDBID or TVDBID public int? EpisodeNumber { get; set; } public int? SeasonNumber { get; set; } + public string AlbumId { get; set; } public DateTime AddedAt { get; set; } } public enum RecentlyAddedType { Plex = 0, - Emby = 1 + Emby = 1, + Lidarr = 2 } public enum ContentType { Parent = 0, - Episode = 1 + Episode = 1, + Album = 2, } } \ No newline at end of file diff --git a/src/Ombi.Store/Entities/RequestQueue.cs b/src/Ombi.Store/Entities/RequestQueue.cs new file mode 100644 index 000000000..85f73e04b --- /dev/null +++ b/src/Ombi.Store/Entities/RequestQueue.cs @@ -0,0 +1,16 @@ +using System; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Ombi.Store.Entities +{ + [Table("RequestQueue")] + public class RequestQueue : Entity + { + public int RequestId { get; set; } + public RequestType Type { get; set; } + public DateTime Dts { get; set; } + public string Error { get; set; } + public DateTime? Completed { get; set; } + public int RetryCount { get; set; } + } +} \ No newline at end of file diff --git a/src/Ombi.Store/Entities/RequestType.cs b/src/Ombi.Store/Entities/RequestType.cs index 42356985f..4d2d20ac4 100644 --- a/src/Ombi.Store/Entities/RequestType.cs +++ b/src/Ombi.Store/Entities/RequestType.cs @@ -1,12 +1,9 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace Ombi.Store.Entities +namespace Ombi.Store.Entities { public enum RequestType { - TvShow, - Movie + TvShow = 0, + Movie = 1, + Album = 2, } } diff --git a/src/Ombi.Store/Entities/Requests/AlbumRequest.cs b/src/Ombi.Store/Entities/Requests/AlbumRequest.cs new file mode 100644 index 000000000..2735603c6 --- /dev/null +++ b/src/Ombi.Store/Entities/Requests/AlbumRequest.cs @@ -0,0 +1,21 @@ +using System; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Ombi.Store.Entities.Requests +{ + [Table("AlbumRequests")] + public class AlbumRequest : BaseRequest + { + public string ForeignAlbumId { get; set; } + public string ForeignArtistId { get; set; } + public string Disk { get; set; } + public string Cover { get; set; } + public decimal Rating { get; set; } + public DateTime ReleaseDate { get; set; } + public string ArtistName { get; set; } + [NotMapped] + public bool Subscribed { get; set; } + [NotMapped] + public bool ShowSubscribe { get; set; } + } +} \ No newline at end of file diff --git a/src/Ombi.Store/Entities/Requests/BaseRequest.cs b/src/Ombi.Store/Entities/Requests/BaseRequest.cs index 95395d0bf..596041a51 100644 --- a/src/Ombi.Store/Entities/Requests/BaseRequest.cs +++ b/src/Ombi.Store/Entities/Requests/BaseRequest.cs @@ -8,12 +8,16 @@ namespace Ombi.Store.Entities.Requests { public string Title { get; set; } public bool Approved { get; set; } + public DateTime MarkedAsApproved { get; set; } public DateTime RequestedDate { get; set; } public bool Available { get; set; } + public DateTime? MarkedAsAvailable { get; set; } public string RequestedUserId { get; set; } public bool? Denied { get; set; } + public DateTime MarkedAsDenied { get; set; } public string DeniedReason { get; set; } public RequestType RequestType { get; set; } + public string RequestedByAlias { get; set; } [ForeignKey(nameof(RequestedUserId))] public OmbiUser RequestedUser { get; set; } diff --git a/src/Ombi.Store/Entities/Requests/ChildRequests.cs b/src/Ombi.Store/Entities/Requests/ChildRequests.cs index 3b5156ce5..f212aa3e7 100644 --- a/src/Ombi.Store/Entities/Requests/ChildRequests.cs +++ b/src/Ombi.Store/Entities/Requests/ChildRequests.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.ComponentModel.DataAnnotations.Schema; using Ombi.Store.Repository.Requests; @@ -22,6 +23,8 @@ namespace Ombi.Store.Entities.Requests [NotMapped] public bool ShowSubscribe { get; set; } + [NotMapped] + public DateTime ReleaseYear { get; set; } // Used in the ExistingPlexRequestRule.cs [ForeignKey(nameof(IssueId))] public List Issues { get; set; } diff --git a/src/Ombi.Store/Entities/Requests/Issues.cs b/src/Ombi.Store/Entities/Requests/Issues.cs index b1021e362..9fbc6a83e 100644 --- a/src/Ombi.Store/Entities/Requests/Issues.cs +++ b/src/Ombi.Store/Entities/Requests/Issues.cs @@ -29,5 +29,6 @@ namespace Ombi.Store.Entities.Requests Pending = 0, InProgress = 1, Resolved = 2, + Deleted = 3, } } \ No newline at end of file diff --git a/src/Ombi.Store/Entities/Requests/MovieRequests.cs b/src/Ombi.Store/Entities/Requests/MovieRequests.cs index 675035140..677a4292c 100644 --- a/src/Ombi.Store/Entities/Requests/MovieRequests.cs +++ b/src/Ombi.Store/Entities/Requests/MovieRequests.cs @@ -1,6 +1,7 @@ -using System; +using Ombi.Helpers; using System.Collections.Generic; using System.ComponentModel.DataAnnotations.Schema; +using Newtonsoft.Json; namespace Ombi.Store.Entities.Requests { @@ -19,5 +20,14 @@ namespace Ombi.Store.Entities.Requests public int RootPathOverride { get; set; } public int QualityOverride { get; set; } + + /// + /// Only Use for setting the Language Code, Use the LanguageCode property for reading + /// + public string LangCode { get; set; } + + [NotMapped] + [JsonIgnore] + public string LanguageCode => LangCode.IsNullOrEmpty() ? "en" : LangCode; } } diff --git a/src/Ombi.Store/Entities/Requests/SeasonRequests.cs b/src/Ombi.Store/Entities/Requests/SeasonRequests.cs index 521cf5b94..6abaa4de3 100644 --- a/src/Ombi.Store/Entities/Requests/SeasonRequests.cs +++ b/src/Ombi.Store/Entities/Requests/SeasonRequests.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations.Schema; +using System.Globalization; using Ombi.Store.Entities; using Ombi.Store.Entities.Requests; @@ -27,9 +28,10 @@ namespace Ombi.Store.Repository.Requests public bool Approved { get; set; } public bool Requested { get; set; } - public int SeasonId { get; set; } [ForeignKey(nameof(SeasonId))] public SeasonRequests Season { get; set; } + + [NotMapped] public string AirDateDisplay => AirDate == DateTime.MinValue ? "Unknown" : AirDate.ToString(CultureInfo.InvariantCulture); } } \ No newline at end of file diff --git a/src/Ombi.Store/Entities/Requests/TvRequests.cs b/src/Ombi.Store/Entities/Requests/TvRequests.cs index 432bc88ab..5e33c016f 100644 --- a/src/Ombi.Store/Entities/Requests/TvRequests.cs +++ b/src/Ombi.Store/Entities/Requests/TvRequests.cs @@ -17,10 +17,6 @@ namespace Ombi.Store.Entities.Requests public DateTime ReleaseDate { get; set; } public string Status { get; set; } - /// - /// This is so we can correctly send the right amount of seasons to Sonarr - /// - [NotMapped] public int TotalSeasons { get; set; } public List ChildRequests { get; set; } diff --git a/src/Ombi.Store/Entities/UserNotificationPreferences.cs b/src/Ombi.Store/Entities/UserNotificationPreferences.cs new file mode 100644 index 000000000..7196d38ca --- /dev/null +++ b/src/Ombi.Store/Entities/UserNotificationPreferences.cs @@ -0,0 +1,19 @@ +using System.ComponentModel.DataAnnotations.Schema; +using Newtonsoft.Json; +using Ombi.Helpers; + +namespace Ombi.Store.Entities +{ + [Table(nameof(UserNotificationPreferences))] + public class UserNotificationPreferences : Entity + { + public string UserId { get; set; } + public NotificationAgent Agent { get; set; } + public bool Enabled { get; set; } + public string Value { get; set; } + + [ForeignKey(nameof(UserId))] + [JsonIgnore] + public OmbiUser User { get; set; } + } +} diff --git a/src/Ombi.Store/Entities/UserQualityProfiles.cs b/src/Ombi.Store/Entities/UserQualityProfiles.cs new file mode 100644 index 000000000..d944c81b2 --- /dev/null +++ b/src/Ombi.Store/Entities/UserQualityProfiles.cs @@ -0,0 +1,23 @@ +using System.ComponentModel.DataAnnotations.Schema; +using Newtonsoft.Json; +using Ombi.Helpers; + +namespace Ombi.Store.Entities +{ + [Table(nameof(UserQualityProfiles))] + public class UserQualityProfiles : Entity + { + public string UserId { get; set; } + + public int SonarrQualityProfileAnime { get; set; } + public int SonarrRootPathAnime { get; set; } + public int SonarrRootPath { get; set; } + public int SonarrQualityProfile { get; set; } + public int RadarrRootPath { get; set; } + public int RadarrQualityProfile { get; set; } + + [ForeignKey(nameof(UserId))] + [JsonIgnore] + public OmbiUser User { get; set; } + } +} diff --git a/src/Ombi.Store/Entities/Votes.cs b/src/Ombi.Store/Entities/Votes.cs new file mode 100644 index 000000000..61e5651da --- /dev/null +++ b/src/Ombi.Store/Entities/Votes.cs @@ -0,0 +1,25 @@ +using System; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Ombi.Store.Entities +{ + [Table("Votes")] + public class Votes : Entity + { + public int RequestId { get; set; } + public VoteType VoteType { get; set; } + public RequestType RequestType { get; set; } + public string UserId { get; set; } + public DateTime Date { get; set; } + public bool Deleted { get; set; } + + [ForeignKey(nameof(UserId))] + public OmbiUser User { get; set; } + } + + public enum VoteType + { + Upvote = 0, + Downvote = 1 + } +} \ No newline at end of file diff --git a/src/Ombi.Store/Migrations/20180730085903_UserStats.Designer.cs b/src/Ombi.Store/Migrations/20180730085903_UserStats.Designer.cs new file mode 100644 index 000000000..1f34d280f --- /dev/null +++ b/src/Ombi.Store/Migrations/20180730085903_UserStats.Designer.cs @@ -0,0 +1,988 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Ombi.Store.Context; + +namespace Ombi.Store.Migrations +{ + [DbContext(typeof(OmbiContext))] + [Migration("20180730085903_UserStats")] + partial class UserStats + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.1.1-rtm-30846"); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken(); + + b.Property("Name") + .HasMaxLength(256); + + b.Property("NormalizedName") + .HasMaxLength(256); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasName("RoleNameIndex"); + + b.ToTable("AspNetRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ClaimType"); + + b.Property("ClaimValue"); + + b.Property("RoleId") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ClaimType"); + + b.Property("ClaimValue"); + + b.Property("UserId") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider"); + + b.Property("ProviderKey"); + + b.Property("ProviderDisplayName"); + + b.Property("UserId") + .IsRequired(); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId"); + + b.Property("RoleId"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId"); + + b.Property("LoginProvider"); + + b.Property("Name"); + + b.Property("Value"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.ApplicationConfiguration", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Type"); + + b.Property("Value"); + + b.HasKey("Id"); + + b.ToTable("ApplicationConfiguration"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Audit", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AuditArea"); + + b.Property("AuditType"); + + b.Property("DateTime"); + + b.Property("Description"); + + b.Property("User"); + + b.HasKey("Id"); + + b.ToTable("Audit"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.CouchPotatoCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("TheMovieDbId"); + + b.HasKey("Id"); + + b.ToTable("CouchPotatoCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.EmbyContent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("EmbyId") + .IsRequired(); + + b.Property("ImdbId"); + + b.Property("ProviderId"); + + b.Property("TheMovieDbId"); + + b.Property("Title"); + + b.Property("TvDbId"); + + b.Property("Type"); + + b.Property("Url"); + + b.HasKey("Id"); + + b.ToTable("EmbyContent"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.EmbyEpisode", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("EmbyId"); + + b.Property("EpisodeNumber"); + + b.Property("ImdbId"); + + b.Property("ParentId"); + + b.Property("ProviderId"); + + b.Property("SeasonNumber"); + + b.Property("TheMovieDbId"); + + b.Property("Title"); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.HasIndex("ParentId"); + + b.ToTable("EmbyEpisode"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.GlobalSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Content"); + + b.Property("SettingsName"); + + b.HasKey("Id"); + + b.ToTable("GlobalSettings"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.NotificationTemplates", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Agent"); + + b.Property("Enabled"); + + b.Property("Message"); + + b.Property("NotificationType"); + + b.Property("Subject"); + + b.HasKey("Id"); + + b.ToTable("NotificationTemplates"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.NotificationUserId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("PlayerId"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("NotificationUserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.OmbiUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AccessFailedCount"); + + b.Property("Alias"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken(); + + b.Property("Email") + .HasMaxLength(256); + + b.Property("EmailConfirmed"); + + b.Property("EmbyConnectUserId"); + + b.Property("EpisodeRequestLimit"); + + b.Property("LastLoggedIn"); + + b.Property("LockoutEnabled"); + + b.Property("LockoutEnd"); + + b.Property("MovieRequestLimit"); + + b.Property("NormalizedEmail") + .HasMaxLength(256); + + b.Property("NormalizedUserName") + .HasMaxLength(256); + + b.Property("PasswordHash"); + + b.Property("PhoneNumber"); + + b.Property("PhoneNumberConfirmed"); + + b.Property("ProviderUserId"); + + b.Property("SecurityStamp"); + + b.Property("TwoFactorEnabled"); + + b.Property("UserAccessToken"); + + b.Property("UserName") + .HasMaxLength(256); + + b.Property("UserType"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasName("UserNameIndex"); + + b.ToTable("AspNetUsers"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexEpisode", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("EpisodeNumber"); + + b.Property("GrandparentKey"); + + b.Property("Key"); + + b.Property("ParentKey"); + + b.Property("SeasonNumber"); + + b.Property("Title"); + + b.HasKey("Id"); + + b.HasIndex("GrandparentKey"); + + b.ToTable("PlexEpisode"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexSeasonsContent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ParentKey"); + + b.Property("PlexContentId"); + + b.Property("PlexServerContentId"); + + b.Property("SeasonKey"); + + b.Property("SeasonNumber"); + + b.HasKey("Id"); + + b.HasIndex("PlexServerContentId"); + + b.ToTable("PlexSeasonsContent"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexServerContent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("ImdbId"); + + b.Property("Key"); + + b.Property("Quality"); + + b.Property("ReleaseYear"); + + b.Property("TheMovieDbId"); + + b.Property("Title"); + + b.Property("TvDbId"); + + b.Property("Type"); + + b.Property("Url"); + + b.HasKey("Id"); + + b.ToTable("PlexServerContent"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.RadarrCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("HasFile"); + + b.Property("TheMovieDbId"); + + b.HasKey("Id"); + + b.ToTable("RadarrCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.RecentlyAddedLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("ContentId"); + + b.Property("ContentType"); + + b.Property("EpisodeNumber"); + + b.Property("SeasonNumber"); + + b.Property("Type"); + + b.HasKey("Id"); + + b.ToTable("RecentlyAddedLog"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.ChildRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Approved"); + + b.Property("Available"); + + b.Property("Denied"); + + b.Property("DeniedReason"); + + b.Property("IssueId"); + + b.Property("MarkedAsApproved"); + + b.Property("MarkedAsAvailable"); + + b.Property("MarkedAsDenied"); + + b.Property("ParentRequestId"); + + b.Property("RequestType"); + + b.Property("RequestedDate"); + + b.Property("RequestedUserId"); + + b.Property("SeriesType"); + + b.Property("Title"); + + b.HasKey("Id"); + + b.HasIndex("ParentRequestId"); + + b.HasIndex("RequestedUserId"); + + b.ToTable("ChildRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Value"); + + b.HasKey("Id"); + + b.ToTable("IssueCategory"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueComments", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Comment"); + + b.Property("Date"); + + b.Property("IssuesId"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("IssuesId"); + + b.HasIndex("UserId"); + + b.ToTable("IssueComments"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.Issues", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Description"); + + b.Property("IssueCategoryId"); + + b.Property("IssueId"); + + b.Property("ProviderId"); + + b.Property("RequestId"); + + b.Property("RequestType"); + + b.Property("ResovledDate"); + + b.Property("Status"); + + b.Property("Subject"); + + b.Property("Title"); + + b.Property("UserReportedId"); + + b.HasKey("Id"); + + b.HasIndex("IssueCategoryId"); + + b.HasIndex("IssueId"); + + b.HasIndex("UserReportedId"); + + b.ToTable("Issues"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.MovieRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Approved"); + + b.Property("Available"); + + b.Property("Background"); + + b.Property("Denied"); + + b.Property("DeniedReason"); + + b.Property("DigitalReleaseDate"); + + b.Property("ImdbId"); + + b.Property("IssueId"); + + b.Property("MarkedAsApproved"); + + b.Property("MarkedAsAvailable"); + + b.Property("MarkedAsDenied"); + + b.Property("Overview"); + + b.Property("PosterPath"); + + b.Property("QualityOverride"); + + b.Property("ReleaseDate"); + + b.Property("RequestType"); + + b.Property("RequestedDate"); + + b.Property("RequestedUserId"); + + b.Property("RootPathOverride"); + + b.Property("Status"); + + b.Property("TheMovieDbId"); + + b.Property("Title"); + + b.HasKey("Id"); + + b.HasIndex("RequestedUserId"); + + b.ToTable("MovieRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.RequestLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("EpisodeCount"); + + b.Property("RequestDate"); + + b.Property("RequestId"); + + b.Property("RequestType"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("RequestLog"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.TvRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Background"); + + b.Property("ImdbId"); + + b.Property("Overview"); + + b.Property("PosterPath"); + + b.Property("QualityOverride"); + + b.Property("ReleaseDate"); + + b.Property("RootFolder"); + + b.Property("Status"); + + b.Property("Title"); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.ToTable("TvRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.RequestSubscription", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("RequestId"); + + b.Property("RequestType"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("RequestSubscription"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.SickRageCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.ToTable("SickRageCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.SickRageEpisodeCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("EpisodeNumber"); + + b.Property("SeasonNumber"); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.ToTable("SickRageEpisodeCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.SonarrCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.ToTable("SonarrCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.SonarrEpisodeCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("EpisodeNumber"); + + b.Property("HasFile"); + + b.Property("SeasonNumber"); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.ToTable("SonarrEpisodeCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Tokens", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Token"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Tokens"); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.EpisodeRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AirDate"); + + b.Property("Approved"); + + b.Property("Available"); + + b.Property("EpisodeNumber"); + + b.Property("Requested"); + + b.Property("SeasonId"); + + b.Property("Title"); + + b.Property("Url"); + + b.HasKey("Id"); + + b.HasIndex("SeasonId"); + + b.ToTable("EpisodeRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.SeasonRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChildRequestId"); + + b.Property("SeasonNumber"); + + b.HasKey("Id"); + + b.HasIndex("ChildRequestId"); + + b.ToTable("SeasonRequests"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole") + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole") + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Ombi.Store.Entities.OmbiUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Ombi.Store.Entities.EmbyEpisode", b => + { + b.HasOne("Ombi.Store.Entities.EmbyContent", "Series") + .WithMany("Episodes") + .HasForeignKey("ParentId") + .HasPrincipalKey("EmbyId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.NotificationUserId", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany("NotificationUserIds") + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexEpisode", b => + { + b.HasOne("Ombi.Store.Entities.PlexServerContent", "Series") + .WithMany("Episodes") + .HasForeignKey("GrandparentKey") + .HasPrincipalKey("Key") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexSeasonsContent", b => + { + b.HasOne("Ombi.Store.Entities.PlexServerContent") + .WithMany("Seasons") + .HasForeignKey("PlexServerContentId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.ChildRequests", b => + { + b.HasOne("Ombi.Store.Entities.Requests.TvRequests", "ParentRequest") + .WithMany("ChildRequests") + .HasForeignKey("ParentRequestId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Ombi.Store.Entities.OmbiUser", "RequestedUser") + .WithMany() + .HasForeignKey("RequestedUserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueComments", b => + { + b.HasOne("Ombi.Store.Entities.Requests.Issues", "Issues") + .WithMany("Comments") + .HasForeignKey("IssuesId"); + + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.Issues", b => + { + b.HasOne("Ombi.Store.Entities.Requests.IssueCategory", "IssueCategory") + .WithMany() + .HasForeignKey("IssueCategoryId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Ombi.Store.Entities.Requests.ChildRequests") + .WithMany("Issues") + .HasForeignKey("IssueId"); + + b.HasOne("Ombi.Store.Entities.Requests.MovieRequests") + .WithMany("Issues") + .HasForeignKey("IssueId"); + + b.HasOne("Ombi.Store.Entities.OmbiUser", "UserReported") + .WithMany() + .HasForeignKey("UserReportedId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.MovieRequests", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "RequestedUser") + .WithMany() + .HasForeignKey("RequestedUserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.RequestLog", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.RequestSubscription", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Tokens", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.EpisodeRequests", b => + { + b.HasOne("Ombi.Store.Repository.Requests.SeasonRequests", "Season") + .WithMany("Episodes") + .HasForeignKey("SeasonId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.SeasonRequests", b => + { + b.HasOne("Ombi.Store.Entities.Requests.ChildRequests", "ChildRequest") + .WithMany("SeasonRequests") + .HasForeignKey("ChildRequestId") + .OnDelete(DeleteBehavior.Cascade); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Ombi.Store/Migrations/20180730085903_UserStats.cs b/src/Ombi.Store/Migrations/20180730085903_UserStats.cs new file mode 100644 index 000000000..576ff28c6 --- /dev/null +++ b/src/Ombi.Store/Migrations/20180730085903_UserStats.cs @@ -0,0 +1,72 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Ombi.Store.Migrations +{ + public partial class UserStats : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "MarkedAsApproved", + table: "MovieRequests", + nullable: false, + defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified)); + + migrationBuilder.AddColumn( + name: "MarkedAsAvailable", + table: "MovieRequests", + nullable: true); + + migrationBuilder.AddColumn( + name: "MarkedAsDenied", + table: "MovieRequests", + nullable: false, + defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified)); + + migrationBuilder.AddColumn( + name: "MarkedAsApproved", + table: "ChildRequests", + nullable: false, + defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified)); + + migrationBuilder.AddColumn( + name: "MarkedAsAvailable", + table: "ChildRequests", + nullable: true); + + migrationBuilder.AddColumn( + name: "MarkedAsDenied", + table: "ChildRequests", + nullable: false, + defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified)); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "MarkedAsApproved", + table: "MovieRequests"); + + migrationBuilder.DropColumn( + name: "MarkedAsAvailable", + table: "MovieRequests"); + + migrationBuilder.DropColumn( + name: "MarkedAsDenied", + table: "MovieRequests"); + + migrationBuilder.DropColumn( + name: "MarkedAsApproved", + table: "ChildRequests"); + + migrationBuilder.DropColumn( + name: "MarkedAsAvailable", + table: "ChildRequests"); + + migrationBuilder.DropColumn( + name: "MarkedAsDenied", + table: "ChildRequests"); + } + } +} diff --git a/src/Ombi.Store/Migrations/20180824152254_MusicRequests.Designer.cs b/src/Ombi.Store/Migrations/20180824152254_MusicRequests.Designer.cs new file mode 100644 index 000000000..415563212 --- /dev/null +++ b/src/Ombi.Store/Migrations/20180824152254_MusicRequests.Designer.cs @@ -0,0 +1,1045 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Ombi.Store.Context; + +namespace Ombi.Store.Migrations +{ + [DbContext(typeof(OmbiContext))] + [Migration("20180824152254_MusicRequests")] + partial class MusicRequests + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.1.1-rtm-30846"); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken(); + + b.Property("Name") + .HasMaxLength(256); + + b.Property("NormalizedName") + .HasMaxLength(256); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasName("RoleNameIndex"); + + b.ToTable("AspNetRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ClaimType"); + + b.Property("ClaimValue"); + + b.Property("RoleId") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ClaimType"); + + b.Property("ClaimValue"); + + b.Property("UserId") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider"); + + b.Property("ProviderKey"); + + b.Property("ProviderDisplayName"); + + b.Property("UserId") + .IsRequired(); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId"); + + b.Property("RoleId"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId"); + + b.Property("LoginProvider"); + + b.Property("Name"); + + b.Property("Value"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.ApplicationConfiguration", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Type"); + + b.Property("Value"); + + b.HasKey("Id"); + + b.ToTable("ApplicationConfiguration"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Audit", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AuditArea"); + + b.Property("AuditType"); + + b.Property("DateTime"); + + b.Property("Description"); + + b.Property("User"); + + b.HasKey("Id"); + + b.ToTable("Audit"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.CouchPotatoCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("TheMovieDbId"); + + b.HasKey("Id"); + + b.ToTable("CouchPotatoCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.EmbyContent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("EmbyId") + .IsRequired(); + + b.Property("ImdbId"); + + b.Property("ProviderId"); + + b.Property("TheMovieDbId"); + + b.Property("Title"); + + b.Property("TvDbId"); + + b.Property("Type"); + + b.Property("Url"); + + b.HasKey("Id"); + + b.ToTable("EmbyContent"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.EmbyEpisode", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("EmbyId"); + + b.Property("EpisodeNumber"); + + b.Property("ImdbId"); + + b.Property("ParentId"); + + b.Property("ProviderId"); + + b.Property("SeasonNumber"); + + b.Property("TheMovieDbId"); + + b.Property("Title"); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.HasIndex("ParentId"); + + b.ToTable("EmbyEpisode"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.GlobalSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Content"); + + b.Property("SettingsName"); + + b.HasKey("Id"); + + b.ToTable("GlobalSettings"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.NotificationTemplates", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Agent"); + + b.Property("Enabled"); + + b.Property("Message"); + + b.Property("NotificationType"); + + b.Property("Subject"); + + b.HasKey("Id"); + + b.ToTable("NotificationTemplates"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.NotificationUserId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("PlayerId"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("NotificationUserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.OmbiUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AccessFailedCount"); + + b.Property("Alias"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken(); + + b.Property("Email") + .HasMaxLength(256); + + b.Property("EmailConfirmed"); + + b.Property("EmbyConnectUserId"); + + b.Property("EpisodeRequestLimit"); + + b.Property("LastLoggedIn"); + + b.Property("LockoutEnabled"); + + b.Property("LockoutEnd"); + + b.Property("MovieRequestLimit"); + + b.Property("MusicRequestLimit"); + + b.Property("NormalizedEmail") + .HasMaxLength(256); + + b.Property("NormalizedUserName") + .HasMaxLength(256); + + b.Property("PasswordHash"); + + b.Property("PhoneNumber"); + + b.Property("PhoneNumberConfirmed"); + + b.Property("ProviderUserId"); + + b.Property("SecurityStamp"); + + b.Property("TwoFactorEnabled"); + + b.Property("UserAccessToken"); + + b.Property("UserName") + .HasMaxLength(256); + + b.Property("UserType"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasName("UserNameIndex"); + + b.ToTable("AspNetUsers"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexEpisode", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("EpisodeNumber"); + + b.Property("GrandparentKey"); + + b.Property("Key"); + + b.Property("ParentKey"); + + b.Property("SeasonNumber"); + + b.Property("Title"); + + b.HasKey("Id"); + + b.HasIndex("GrandparentKey"); + + b.ToTable("PlexEpisode"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexSeasonsContent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ParentKey"); + + b.Property("PlexContentId"); + + b.Property("PlexServerContentId"); + + b.Property("SeasonKey"); + + b.Property("SeasonNumber"); + + b.HasKey("Id"); + + b.HasIndex("PlexServerContentId"); + + b.ToTable("PlexSeasonsContent"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexServerContent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("ImdbId"); + + b.Property("Key"); + + b.Property("Quality"); + + b.Property("ReleaseYear"); + + b.Property("TheMovieDbId"); + + b.Property("Title"); + + b.Property("TvDbId"); + + b.Property("Type"); + + b.Property("Url"); + + b.HasKey("Id"); + + b.ToTable("PlexServerContent"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.RadarrCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("HasFile"); + + b.Property("TheMovieDbId"); + + b.HasKey("Id"); + + b.ToTable("RadarrCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.RecentlyAddedLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("ContentId"); + + b.Property("ContentType"); + + b.Property("EpisodeNumber"); + + b.Property("SeasonNumber"); + + b.Property("Type"); + + b.HasKey("Id"); + + b.ToTable("RecentlyAddedLog"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.AlbumRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Approved"); + + b.Property("ArtistName"); + + b.Property("Available"); + + b.Property("Cover"); + + b.Property("Denied"); + + b.Property("DeniedReason"); + + b.Property("Disk"); + + b.Property("ForeignAlbumId"); + + b.Property("ForeignArtistId"); + + b.Property("MarkedAsApproved"); + + b.Property("MarkedAsAvailable"); + + b.Property("MarkedAsDenied"); + + b.Property("Rating"); + + b.Property("ReleaseDate"); + + b.Property("RequestType"); + + b.Property("RequestedDate"); + + b.Property("RequestedUserId"); + + b.Property("Title"); + + b.HasKey("Id"); + + b.HasIndex("RequestedUserId"); + + b.ToTable("AlbumRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.ChildRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Approved"); + + b.Property("Available"); + + b.Property("Denied"); + + b.Property("DeniedReason"); + + b.Property("IssueId"); + + b.Property("MarkedAsApproved"); + + b.Property("MarkedAsAvailable"); + + b.Property("MarkedAsDenied"); + + b.Property("ParentRequestId"); + + b.Property("RequestType"); + + b.Property("RequestedDate"); + + b.Property("RequestedUserId"); + + b.Property("SeriesType"); + + b.Property("Title"); + + b.HasKey("Id"); + + b.HasIndex("ParentRequestId"); + + b.HasIndex("RequestedUserId"); + + b.ToTable("ChildRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Value"); + + b.HasKey("Id"); + + b.ToTable("IssueCategory"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueComments", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Comment"); + + b.Property("Date"); + + b.Property("IssuesId"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("IssuesId"); + + b.HasIndex("UserId"); + + b.ToTable("IssueComments"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.Issues", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Description"); + + b.Property("IssueCategoryId"); + + b.Property("IssueId"); + + b.Property("ProviderId"); + + b.Property("RequestId"); + + b.Property("RequestType"); + + b.Property("ResovledDate"); + + b.Property("Status"); + + b.Property("Subject"); + + b.Property("Title"); + + b.Property("UserReportedId"); + + b.HasKey("Id"); + + b.HasIndex("IssueCategoryId"); + + b.HasIndex("IssueId"); + + b.HasIndex("UserReportedId"); + + b.ToTable("Issues"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.MovieRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Approved"); + + b.Property("Available"); + + b.Property("Background"); + + b.Property("Denied"); + + b.Property("DeniedReason"); + + b.Property("DigitalReleaseDate"); + + b.Property("ImdbId"); + + b.Property("IssueId"); + + b.Property("MarkedAsApproved"); + + b.Property("MarkedAsAvailable"); + + b.Property("MarkedAsDenied"); + + b.Property("Overview"); + + b.Property("PosterPath"); + + b.Property("QualityOverride"); + + b.Property("ReleaseDate"); + + b.Property("RequestType"); + + b.Property("RequestedDate"); + + b.Property("RequestedUserId"); + + b.Property("RootPathOverride"); + + b.Property("Status"); + + b.Property("TheMovieDbId"); + + b.Property("Title"); + + b.HasKey("Id"); + + b.HasIndex("RequestedUserId"); + + b.ToTable("MovieRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.RequestLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("EpisodeCount"); + + b.Property("RequestDate"); + + b.Property("RequestId"); + + b.Property("RequestType"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("RequestLog"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.TvRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Background"); + + b.Property("ImdbId"); + + b.Property("Overview"); + + b.Property("PosterPath"); + + b.Property("QualityOverride"); + + b.Property("ReleaseDate"); + + b.Property("RootFolder"); + + b.Property("Status"); + + b.Property("Title"); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.ToTable("TvRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.RequestSubscription", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("RequestId"); + + b.Property("RequestType"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("RequestSubscription"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.SickRageCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.ToTable("SickRageCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.SickRageEpisodeCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("EpisodeNumber"); + + b.Property("SeasonNumber"); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.ToTable("SickRageEpisodeCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.SonarrCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.ToTable("SonarrCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.SonarrEpisodeCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("EpisodeNumber"); + + b.Property("HasFile"); + + b.Property("SeasonNumber"); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.ToTable("SonarrEpisodeCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Tokens", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Token"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Tokens"); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.EpisodeRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AirDate"); + + b.Property("Approved"); + + b.Property("Available"); + + b.Property("EpisodeNumber"); + + b.Property("Requested"); + + b.Property("SeasonId"); + + b.Property("Title"); + + b.Property("Url"); + + b.HasKey("Id"); + + b.HasIndex("SeasonId"); + + b.ToTable("EpisodeRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.SeasonRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChildRequestId"); + + b.Property("SeasonNumber"); + + b.HasKey("Id"); + + b.HasIndex("ChildRequestId"); + + b.ToTable("SeasonRequests"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole") + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole") + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Ombi.Store.Entities.OmbiUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Ombi.Store.Entities.EmbyEpisode", b => + { + b.HasOne("Ombi.Store.Entities.EmbyContent", "Series") + .WithMany("Episodes") + .HasForeignKey("ParentId") + .HasPrincipalKey("EmbyId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.NotificationUserId", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany("NotificationUserIds") + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexEpisode", b => + { + b.HasOne("Ombi.Store.Entities.PlexServerContent", "Series") + .WithMany("Episodes") + .HasForeignKey("GrandparentKey") + .HasPrincipalKey("Key") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexSeasonsContent", b => + { + b.HasOne("Ombi.Store.Entities.PlexServerContent") + .WithMany("Seasons") + .HasForeignKey("PlexServerContentId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.AlbumRequest", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "RequestedUser") + .WithMany() + .HasForeignKey("RequestedUserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.ChildRequests", b => + { + b.HasOne("Ombi.Store.Entities.Requests.TvRequests", "ParentRequest") + .WithMany("ChildRequests") + .HasForeignKey("ParentRequestId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Ombi.Store.Entities.OmbiUser", "RequestedUser") + .WithMany() + .HasForeignKey("RequestedUserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueComments", b => + { + b.HasOne("Ombi.Store.Entities.Requests.Issues", "Issues") + .WithMany("Comments") + .HasForeignKey("IssuesId"); + + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.Issues", b => + { + b.HasOne("Ombi.Store.Entities.Requests.IssueCategory", "IssueCategory") + .WithMany() + .HasForeignKey("IssueCategoryId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Ombi.Store.Entities.Requests.ChildRequests") + .WithMany("Issues") + .HasForeignKey("IssueId"); + + b.HasOne("Ombi.Store.Entities.Requests.MovieRequests") + .WithMany("Issues") + .HasForeignKey("IssueId"); + + b.HasOne("Ombi.Store.Entities.OmbiUser", "UserReported") + .WithMany() + .HasForeignKey("UserReportedId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.MovieRequests", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "RequestedUser") + .WithMany() + .HasForeignKey("RequestedUserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.RequestLog", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.RequestSubscription", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Tokens", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.EpisodeRequests", b => + { + b.HasOne("Ombi.Store.Repository.Requests.SeasonRequests", "Season") + .WithMany("Episodes") + .HasForeignKey("SeasonId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.SeasonRequests", b => + { + b.HasOne("Ombi.Store.Entities.Requests.ChildRequests", "ChildRequest") + .WithMany("SeasonRequests") + .HasForeignKey("ChildRequestId") + .OnDelete(DeleteBehavior.Cascade); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Ombi.Store/Migrations/20180824152254_MusicRequests.cs b/src/Ombi.Store/Migrations/20180824152254_MusicRequests.cs new file mode 100644 index 000000000..4810a39eb --- /dev/null +++ b/src/Ombi.Store/Migrations/20180824152254_MusicRequests.cs @@ -0,0 +1,67 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Ombi.Store.Migrations +{ + public partial class MusicRequests : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "MusicRequestLimit", + table: "AspNetUsers", + nullable: true); + + migrationBuilder.CreateTable( + name: "AlbumRequests", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Title = table.Column(nullable: true), + Approved = table.Column(nullable: false), + MarkedAsApproved = table.Column(nullable: false), + RequestedDate = table.Column(nullable: false), + Available = table.Column(nullable: false), + MarkedAsAvailable = table.Column(nullable: true), + RequestedUserId = table.Column(nullable: true), + Denied = table.Column(nullable: true), + MarkedAsDenied = table.Column(nullable: false), + DeniedReason = table.Column(nullable: true), + RequestType = table.Column(nullable: false), + ForeignAlbumId = table.Column(nullable: true), + ForeignArtistId = table.Column(nullable: true), + Disk = table.Column(nullable: true), + Cover = table.Column(nullable: true), + Rating = table.Column(nullable: false), + ReleaseDate = table.Column(nullable: false), + ArtistName = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AlbumRequests", x => x.Id); + table.ForeignKey( + name: "FK_AlbumRequests_AspNetUsers_RequestedUserId", + column: x => x.RequestedUserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateIndex( + name: "IX_AlbumRequests_RequestedUserId", + table: "AlbumRequests", + column: "RequestedUserId"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "AlbumRequests"); + + migrationBuilder.DropColumn( + name: "MusicRequestLimit", + table: "AspNetUsers"); + } + } +} diff --git a/src/Ombi.Store/Migrations/20180824211553_LidarrSyncJobs.Designer.cs b/src/Ombi.Store/Migrations/20180824211553_LidarrSyncJobs.Designer.cs new file mode 100644 index 000000000..c97886525 --- /dev/null +++ b/src/Ombi.Store/Migrations/20180824211553_LidarrSyncJobs.Designer.cs @@ -0,0 +1,1087 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Ombi.Store.Context; + +namespace Ombi.Store.Migrations +{ + [DbContext(typeof(OmbiContext))] + [Migration("20180824211553_LidarrSyncJobs")] + partial class LidarrSyncJobs + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.1.1-rtm-30846"); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken(); + + b.Property("Name") + .HasMaxLength(256); + + b.Property("NormalizedName") + .HasMaxLength(256); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasName("RoleNameIndex"); + + b.ToTable("AspNetRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ClaimType"); + + b.Property("ClaimValue"); + + b.Property("RoleId") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ClaimType"); + + b.Property("ClaimValue"); + + b.Property("UserId") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider"); + + b.Property("ProviderKey"); + + b.Property("ProviderDisplayName"); + + b.Property("UserId") + .IsRequired(); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId"); + + b.Property("RoleId"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId"); + + b.Property("LoginProvider"); + + b.Property("Name"); + + b.Property("Value"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.ApplicationConfiguration", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Type"); + + b.Property("Value"); + + b.HasKey("Id"); + + b.ToTable("ApplicationConfiguration"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Audit", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AuditArea"); + + b.Property("AuditType"); + + b.Property("DateTime"); + + b.Property("Description"); + + b.Property("User"); + + b.HasKey("Id"); + + b.ToTable("Audit"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.CouchPotatoCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("TheMovieDbId"); + + b.HasKey("Id"); + + b.ToTable("CouchPotatoCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.EmbyContent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("EmbyId") + .IsRequired(); + + b.Property("ImdbId"); + + b.Property("ProviderId"); + + b.Property("TheMovieDbId"); + + b.Property("Title"); + + b.Property("TvDbId"); + + b.Property("Type"); + + b.Property("Url"); + + b.HasKey("Id"); + + b.ToTable("EmbyContent"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.EmbyEpisode", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("EmbyId"); + + b.Property("EpisodeNumber"); + + b.Property("ImdbId"); + + b.Property("ParentId"); + + b.Property("ProviderId"); + + b.Property("SeasonNumber"); + + b.Property("TheMovieDbId"); + + b.Property("Title"); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.HasIndex("ParentId"); + + b.ToTable("EmbyEpisode"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.GlobalSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Content"); + + b.Property("SettingsName"); + + b.HasKey("Id"); + + b.ToTable("GlobalSettings"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.LidarrAlbumCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ArtistId"); + + b.Property("ForeignAlbumId"); + + b.Property("Monitored"); + + b.Property("PercentOfTracks"); + + b.Property("ReleaseDate"); + + b.Property("Title"); + + b.Property("TrackCount"); + + b.HasKey("Id"); + + b.ToTable("LidarrAlbumCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.LidarrArtistCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ArtistId"); + + b.Property("ArtistName"); + + b.Property("ForeignArtistId"); + + b.Property("Monitored"); + + b.HasKey("Id"); + + b.ToTable("LidarrArtistCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.NotificationTemplates", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Agent"); + + b.Property("Enabled"); + + b.Property("Message"); + + b.Property("NotificationType"); + + b.Property("Subject"); + + b.HasKey("Id"); + + b.ToTable("NotificationTemplates"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.NotificationUserId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("PlayerId"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("NotificationUserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.OmbiUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AccessFailedCount"); + + b.Property("Alias"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken(); + + b.Property("Email") + .HasMaxLength(256); + + b.Property("EmailConfirmed"); + + b.Property("EmbyConnectUserId"); + + b.Property("EpisodeRequestLimit"); + + b.Property("LastLoggedIn"); + + b.Property("LockoutEnabled"); + + b.Property("LockoutEnd"); + + b.Property("MovieRequestLimit"); + + b.Property("MusicRequestLimit"); + + b.Property("NormalizedEmail") + .HasMaxLength(256); + + b.Property("NormalizedUserName") + .HasMaxLength(256); + + b.Property("PasswordHash"); + + b.Property("PhoneNumber"); + + b.Property("PhoneNumberConfirmed"); + + b.Property("ProviderUserId"); + + b.Property("SecurityStamp"); + + b.Property("TwoFactorEnabled"); + + b.Property("UserAccessToken"); + + b.Property("UserName") + .HasMaxLength(256); + + b.Property("UserType"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasName("UserNameIndex"); + + b.ToTable("AspNetUsers"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexEpisode", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("EpisodeNumber"); + + b.Property("GrandparentKey"); + + b.Property("Key"); + + b.Property("ParentKey"); + + b.Property("SeasonNumber"); + + b.Property("Title"); + + b.HasKey("Id"); + + b.HasIndex("GrandparentKey"); + + b.ToTable("PlexEpisode"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexSeasonsContent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ParentKey"); + + b.Property("PlexContentId"); + + b.Property("PlexServerContentId"); + + b.Property("SeasonKey"); + + b.Property("SeasonNumber"); + + b.HasKey("Id"); + + b.HasIndex("PlexServerContentId"); + + b.ToTable("PlexSeasonsContent"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexServerContent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("ImdbId"); + + b.Property("Key"); + + b.Property("Quality"); + + b.Property("ReleaseYear"); + + b.Property("TheMovieDbId"); + + b.Property("Title"); + + b.Property("TvDbId"); + + b.Property("Type"); + + b.Property("Url"); + + b.HasKey("Id"); + + b.ToTable("PlexServerContent"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.RadarrCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("HasFile"); + + b.Property("TheMovieDbId"); + + b.HasKey("Id"); + + b.ToTable("RadarrCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.RecentlyAddedLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("ContentId"); + + b.Property("ContentType"); + + b.Property("EpisodeNumber"); + + b.Property("SeasonNumber"); + + b.Property("Type"); + + b.HasKey("Id"); + + b.ToTable("RecentlyAddedLog"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.AlbumRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Approved"); + + b.Property("ArtistName"); + + b.Property("Available"); + + b.Property("Cover"); + + b.Property("Denied"); + + b.Property("DeniedReason"); + + b.Property("Disk"); + + b.Property("ForeignAlbumId"); + + b.Property("ForeignArtistId"); + + b.Property("MarkedAsApproved"); + + b.Property("MarkedAsAvailable"); + + b.Property("MarkedAsDenied"); + + b.Property("Rating"); + + b.Property("ReleaseDate"); + + b.Property("RequestType"); + + b.Property("RequestedDate"); + + b.Property("RequestedUserId"); + + b.Property("Title"); + + b.HasKey("Id"); + + b.HasIndex("RequestedUserId"); + + b.ToTable("AlbumRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.ChildRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Approved"); + + b.Property("Available"); + + b.Property("Denied"); + + b.Property("DeniedReason"); + + b.Property("IssueId"); + + b.Property("MarkedAsApproved"); + + b.Property("MarkedAsAvailable"); + + b.Property("MarkedAsDenied"); + + b.Property("ParentRequestId"); + + b.Property("RequestType"); + + b.Property("RequestedDate"); + + b.Property("RequestedUserId"); + + b.Property("SeriesType"); + + b.Property("Title"); + + b.HasKey("Id"); + + b.HasIndex("ParentRequestId"); + + b.HasIndex("RequestedUserId"); + + b.ToTable("ChildRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Value"); + + b.HasKey("Id"); + + b.ToTable("IssueCategory"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueComments", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Comment"); + + b.Property("Date"); + + b.Property("IssuesId"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("IssuesId"); + + b.HasIndex("UserId"); + + b.ToTable("IssueComments"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.Issues", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Description"); + + b.Property("IssueCategoryId"); + + b.Property("IssueId"); + + b.Property("ProviderId"); + + b.Property("RequestId"); + + b.Property("RequestType"); + + b.Property("ResovledDate"); + + b.Property("Status"); + + b.Property("Subject"); + + b.Property("Title"); + + b.Property("UserReportedId"); + + b.HasKey("Id"); + + b.HasIndex("IssueCategoryId"); + + b.HasIndex("IssueId"); + + b.HasIndex("UserReportedId"); + + b.ToTable("Issues"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.MovieRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Approved"); + + b.Property("Available"); + + b.Property("Background"); + + b.Property("Denied"); + + b.Property("DeniedReason"); + + b.Property("DigitalReleaseDate"); + + b.Property("ImdbId"); + + b.Property("IssueId"); + + b.Property("MarkedAsApproved"); + + b.Property("MarkedAsAvailable"); + + b.Property("MarkedAsDenied"); + + b.Property("Overview"); + + b.Property("PosterPath"); + + b.Property("QualityOverride"); + + b.Property("ReleaseDate"); + + b.Property("RequestType"); + + b.Property("RequestedDate"); + + b.Property("RequestedUserId"); + + b.Property("RootPathOverride"); + + b.Property("Status"); + + b.Property("TheMovieDbId"); + + b.Property("Title"); + + b.HasKey("Id"); + + b.HasIndex("RequestedUserId"); + + b.ToTable("MovieRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.RequestLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("EpisodeCount"); + + b.Property("RequestDate"); + + b.Property("RequestId"); + + b.Property("RequestType"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("RequestLog"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.TvRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Background"); + + b.Property("ImdbId"); + + b.Property("Overview"); + + b.Property("PosterPath"); + + b.Property("QualityOverride"); + + b.Property("ReleaseDate"); + + b.Property("RootFolder"); + + b.Property("Status"); + + b.Property("Title"); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.ToTable("TvRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.RequestSubscription", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("RequestId"); + + b.Property("RequestType"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("RequestSubscription"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.SickRageCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.ToTable("SickRageCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.SickRageEpisodeCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("EpisodeNumber"); + + b.Property("SeasonNumber"); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.ToTable("SickRageEpisodeCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.SonarrCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.ToTable("SonarrCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.SonarrEpisodeCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("EpisodeNumber"); + + b.Property("HasFile"); + + b.Property("SeasonNumber"); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.ToTable("SonarrEpisodeCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Tokens", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Token"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Tokens"); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.EpisodeRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AirDate"); + + b.Property("Approved"); + + b.Property("Available"); + + b.Property("EpisodeNumber"); + + b.Property("Requested"); + + b.Property("SeasonId"); + + b.Property("Title"); + + b.Property("Url"); + + b.HasKey("Id"); + + b.HasIndex("SeasonId"); + + b.ToTable("EpisodeRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.SeasonRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChildRequestId"); + + b.Property("SeasonNumber"); + + b.HasKey("Id"); + + b.HasIndex("ChildRequestId"); + + b.ToTable("SeasonRequests"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole") + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole") + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Ombi.Store.Entities.OmbiUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Ombi.Store.Entities.EmbyEpisode", b => + { + b.HasOne("Ombi.Store.Entities.EmbyContent", "Series") + .WithMany("Episodes") + .HasForeignKey("ParentId") + .HasPrincipalKey("EmbyId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.NotificationUserId", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany("NotificationUserIds") + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexEpisode", b => + { + b.HasOne("Ombi.Store.Entities.PlexServerContent", "Series") + .WithMany("Episodes") + .HasForeignKey("GrandparentKey") + .HasPrincipalKey("Key") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexSeasonsContent", b => + { + b.HasOne("Ombi.Store.Entities.PlexServerContent") + .WithMany("Seasons") + .HasForeignKey("PlexServerContentId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.AlbumRequest", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "RequestedUser") + .WithMany() + .HasForeignKey("RequestedUserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.ChildRequests", b => + { + b.HasOne("Ombi.Store.Entities.Requests.TvRequests", "ParentRequest") + .WithMany("ChildRequests") + .HasForeignKey("ParentRequestId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Ombi.Store.Entities.OmbiUser", "RequestedUser") + .WithMany() + .HasForeignKey("RequestedUserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueComments", b => + { + b.HasOne("Ombi.Store.Entities.Requests.Issues", "Issues") + .WithMany("Comments") + .HasForeignKey("IssuesId"); + + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.Issues", b => + { + b.HasOne("Ombi.Store.Entities.Requests.IssueCategory", "IssueCategory") + .WithMany() + .HasForeignKey("IssueCategoryId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Ombi.Store.Entities.Requests.ChildRequests") + .WithMany("Issues") + .HasForeignKey("IssueId"); + + b.HasOne("Ombi.Store.Entities.Requests.MovieRequests") + .WithMany("Issues") + .HasForeignKey("IssueId"); + + b.HasOne("Ombi.Store.Entities.OmbiUser", "UserReported") + .WithMany() + .HasForeignKey("UserReportedId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.MovieRequests", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "RequestedUser") + .WithMany() + .HasForeignKey("RequestedUserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.RequestLog", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.RequestSubscription", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Tokens", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.EpisodeRequests", b => + { + b.HasOne("Ombi.Store.Repository.Requests.SeasonRequests", "Season") + .WithMany("Episodes") + .HasForeignKey("SeasonId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.SeasonRequests", b => + { + b.HasOne("Ombi.Store.Entities.Requests.ChildRequests", "ChildRequest") + .WithMany("SeasonRequests") + .HasForeignKey("ChildRequestId") + .OnDelete(DeleteBehavior.Cascade); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Ombi.Store/Migrations/20180824211553_LidarrSyncJobs.cs b/src/Ombi.Store/Migrations/20180824211553_LidarrSyncJobs.cs new file mode 100644 index 000000000..2b843d3e2 --- /dev/null +++ b/src/Ombi.Store/Migrations/20180824211553_LidarrSyncJobs.cs @@ -0,0 +1,55 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Ombi.Store.Migrations +{ + public partial class LidarrSyncJobs : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "LidarrAlbumCache", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + ArtistId = table.Column(nullable: false), + ForeignAlbumId = table.Column(nullable: true), + TrackCount = table.Column(nullable: false), + ReleaseDate = table.Column(nullable: false), + Monitored = table.Column(nullable: false), + Title = table.Column(nullable: true), + PercentOfTracks = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_LidarrAlbumCache", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "LidarrArtistCache", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + ArtistId = table.Column(nullable: false), + ArtistName = table.Column(nullable: true), + ForeignArtistId = table.Column(nullable: true), + Monitored = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_LidarrArtistCache", x => x.Id); + }); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "LidarrAlbumCache"); + + migrationBuilder.DropTable( + name: "LidarrArtistCache"); + } + } +} diff --git a/src/Ombi.Store/Migrations/20180828083219_MusicIssues.Designer.cs b/src/Ombi.Store/Migrations/20180828083219_MusicIssues.Designer.cs new file mode 100644 index 000000000..52f00c840 --- /dev/null +++ b/src/Ombi.Store/Migrations/20180828083219_MusicIssues.Designer.cs @@ -0,0 +1,1091 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Ombi.Store.Context; + +namespace Ombi.Store.Migrations +{ + [DbContext(typeof(OmbiContext))] + [Migration("20180828083219_MusicIssues")] + partial class MusicIssues + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.1.1-rtm-30846"); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken(); + + b.Property("Name") + .HasMaxLength(256); + + b.Property("NormalizedName") + .HasMaxLength(256); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasName("RoleNameIndex"); + + b.ToTable("AspNetRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ClaimType"); + + b.Property("ClaimValue"); + + b.Property("RoleId") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ClaimType"); + + b.Property("ClaimValue"); + + b.Property("UserId") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider"); + + b.Property("ProviderKey"); + + b.Property("ProviderDisplayName"); + + b.Property("UserId") + .IsRequired(); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId"); + + b.Property("RoleId"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId"); + + b.Property("LoginProvider"); + + b.Property("Name"); + + b.Property("Value"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.ApplicationConfiguration", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Type"); + + b.Property("Value"); + + b.HasKey("Id"); + + b.ToTable("ApplicationConfiguration"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Audit", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AuditArea"); + + b.Property("AuditType"); + + b.Property("DateTime"); + + b.Property("Description"); + + b.Property("User"); + + b.HasKey("Id"); + + b.ToTable("Audit"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.CouchPotatoCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("TheMovieDbId"); + + b.HasKey("Id"); + + b.ToTable("CouchPotatoCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.EmbyContent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("EmbyId") + .IsRequired(); + + b.Property("ImdbId"); + + b.Property("ProviderId"); + + b.Property("TheMovieDbId"); + + b.Property("Title"); + + b.Property("TvDbId"); + + b.Property("Type"); + + b.Property("Url"); + + b.HasKey("Id"); + + b.ToTable("EmbyContent"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.EmbyEpisode", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("EmbyId"); + + b.Property("EpisodeNumber"); + + b.Property("ImdbId"); + + b.Property("ParentId"); + + b.Property("ProviderId"); + + b.Property("SeasonNumber"); + + b.Property("TheMovieDbId"); + + b.Property("Title"); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.HasIndex("ParentId"); + + b.ToTable("EmbyEpisode"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.GlobalSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Content"); + + b.Property("SettingsName"); + + b.HasKey("Id"); + + b.ToTable("GlobalSettings"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.LidarrAlbumCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("ArtistId"); + + b.Property("ForeignAlbumId"); + + b.Property("Monitored"); + + b.Property("PercentOfTracks"); + + b.Property("ReleaseDate"); + + b.Property("Title"); + + b.Property("TrackCount"); + + b.HasKey("Id"); + + b.ToTable("LidarrAlbumCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.LidarrArtistCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ArtistId"); + + b.Property("ArtistName"); + + b.Property("ForeignArtistId"); + + b.Property("Monitored"); + + b.HasKey("Id"); + + b.ToTable("LidarrArtistCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.NotificationTemplates", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Agent"); + + b.Property("Enabled"); + + b.Property("Message"); + + b.Property("NotificationType"); + + b.Property("Subject"); + + b.HasKey("Id"); + + b.ToTable("NotificationTemplates"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.NotificationUserId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("PlayerId"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("NotificationUserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.OmbiUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AccessFailedCount"); + + b.Property("Alias"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken(); + + b.Property("Email") + .HasMaxLength(256); + + b.Property("EmailConfirmed"); + + b.Property("EmbyConnectUserId"); + + b.Property("EpisodeRequestLimit"); + + b.Property("LastLoggedIn"); + + b.Property("LockoutEnabled"); + + b.Property("LockoutEnd"); + + b.Property("MovieRequestLimit"); + + b.Property("MusicRequestLimit"); + + b.Property("NormalizedEmail") + .HasMaxLength(256); + + b.Property("NormalizedUserName") + .HasMaxLength(256); + + b.Property("PasswordHash"); + + b.Property("PhoneNumber"); + + b.Property("PhoneNumberConfirmed"); + + b.Property("ProviderUserId"); + + b.Property("SecurityStamp"); + + b.Property("TwoFactorEnabled"); + + b.Property("UserAccessToken"); + + b.Property("UserName") + .HasMaxLength(256); + + b.Property("UserType"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasName("UserNameIndex"); + + b.ToTable("AspNetUsers"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexEpisode", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("EpisodeNumber"); + + b.Property("GrandparentKey"); + + b.Property("Key"); + + b.Property("ParentKey"); + + b.Property("SeasonNumber"); + + b.Property("Title"); + + b.HasKey("Id"); + + b.HasIndex("GrandparentKey"); + + b.ToTable("PlexEpisode"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexSeasonsContent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ParentKey"); + + b.Property("PlexContentId"); + + b.Property("PlexServerContentId"); + + b.Property("SeasonKey"); + + b.Property("SeasonNumber"); + + b.HasKey("Id"); + + b.HasIndex("PlexServerContentId"); + + b.ToTable("PlexSeasonsContent"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexServerContent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("ImdbId"); + + b.Property("Key"); + + b.Property("Quality"); + + b.Property("ReleaseYear"); + + b.Property("TheMovieDbId"); + + b.Property("Title"); + + b.Property("TvDbId"); + + b.Property("Type"); + + b.Property("Url"); + + b.HasKey("Id"); + + b.ToTable("PlexServerContent"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.RadarrCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("HasFile"); + + b.Property("TheMovieDbId"); + + b.HasKey("Id"); + + b.ToTable("RadarrCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.RecentlyAddedLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("AlbumId"); + + b.Property("ContentId"); + + b.Property("ContentType"); + + b.Property("EpisodeNumber"); + + b.Property("SeasonNumber"); + + b.Property("Type"); + + b.HasKey("Id"); + + b.ToTable("RecentlyAddedLog"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.AlbumRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Approved"); + + b.Property("ArtistName"); + + b.Property("Available"); + + b.Property("Cover"); + + b.Property("Denied"); + + b.Property("DeniedReason"); + + b.Property("Disk"); + + b.Property("ForeignAlbumId"); + + b.Property("ForeignArtistId"); + + b.Property("MarkedAsApproved"); + + b.Property("MarkedAsAvailable"); + + b.Property("MarkedAsDenied"); + + b.Property("Rating"); + + b.Property("ReleaseDate"); + + b.Property("RequestType"); + + b.Property("RequestedDate"); + + b.Property("RequestedUserId"); + + b.Property("Title"); + + b.HasKey("Id"); + + b.HasIndex("RequestedUserId"); + + b.ToTable("AlbumRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.ChildRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Approved"); + + b.Property("Available"); + + b.Property("Denied"); + + b.Property("DeniedReason"); + + b.Property("IssueId"); + + b.Property("MarkedAsApproved"); + + b.Property("MarkedAsAvailable"); + + b.Property("MarkedAsDenied"); + + b.Property("ParentRequestId"); + + b.Property("RequestType"); + + b.Property("RequestedDate"); + + b.Property("RequestedUserId"); + + b.Property("SeriesType"); + + b.Property("Title"); + + b.HasKey("Id"); + + b.HasIndex("ParentRequestId"); + + b.HasIndex("RequestedUserId"); + + b.ToTable("ChildRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Value"); + + b.HasKey("Id"); + + b.ToTable("IssueCategory"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueComments", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Comment"); + + b.Property("Date"); + + b.Property("IssuesId"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("IssuesId"); + + b.HasIndex("UserId"); + + b.ToTable("IssueComments"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.Issues", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Description"); + + b.Property("IssueCategoryId"); + + b.Property("IssueId"); + + b.Property("ProviderId"); + + b.Property("RequestId"); + + b.Property("RequestType"); + + b.Property("ResovledDate"); + + b.Property("Status"); + + b.Property("Subject"); + + b.Property("Title"); + + b.Property("UserReportedId"); + + b.HasKey("Id"); + + b.HasIndex("IssueCategoryId"); + + b.HasIndex("IssueId"); + + b.HasIndex("UserReportedId"); + + b.ToTable("Issues"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.MovieRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Approved"); + + b.Property("Available"); + + b.Property("Background"); + + b.Property("Denied"); + + b.Property("DeniedReason"); + + b.Property("DigitalReleaseDate"); + + b.Property("ImdbId"); + + b.Property("IssueId"); + + b.Property("MarkedAsApproved"); + + b.Property("MarkedAsAvailable"); + + b.Property("MarkedAsDenied"); + + b.Property("Overview"); + + b.Property("PosterPath"); + + b.Property("QualityOverride"); + + b.Property("ReleaseDate"); + + b.Property("RequestType"); + + b.Property("RequestedDate"); + + b.Property("RequestedUserId"); + + b.Property("RootPathOverride"); + + b.Property("Status"); + + b.Property("TheMovieDbId"); + + b.Property("Title"); + + b.HasKey("Id"); + + b.HasIndex("RequestedUserId"); + + b.ToTable("MovieRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.RequestLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("EpisodeCount"); + + b.Property("RequestDate"); + + b.Property("RequestId"); + + b.Property("RequestType"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("RequestLog"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.TvRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Background"); + + b.Property("ImdbId"); + + b.Property("Overview"); + + b.Property("PosterPath"); + + b.Property("QualityOverride"); + + b.Property("ReleaseDate"); + + b.Property("RootFolder"); + + b.Property("Status"); + + b.Property("Title"); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.ToTable("TvRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.RequestSubscription", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("RequestId"); + + b.Property("RequestType"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("RequestSubscription"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.SickRageCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.ToTable("SickRageCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.SickRageEpisodeCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("EpisodeNumber"); + + b.Property("SeasonNumber"); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.ToTable("SickRageEpisodeCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.SonarrCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.ToTable("SonarrCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.SonarrEpisodeCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("EpisodeNumber"); + + b.Property("HasFile"); + + b.Property("SeasonNumber"); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.ToTable("SonarrEpisodeCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Tokens", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Token"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Tokens"); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.EpisodeRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AirDate"); + + b.Property("Approved"); + + b.Property("Available"); + + b.Property("EpisodeNumber"); + + b.Property("Requested"); + + b.Property("SeasonId"); + + b.Property("Title"); + + b.Property("Url"); + + b.HasKey("Id"); + + b.HasIndex("SeasonId"); + + b.ToTable("EpisodeRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.SeasonRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChildRequestId"); + + b.Property("SeasonNumber"); + + b.HasKey("Id"); + + b.HasIndex("ChildRequestId"); + + b.ToTable("SeasonRequests"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole") + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole") + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Ombi.Store.Entities.OmbiUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Ombi.Store.Entities.EmbyEpisode", b => + { + b.HasOne("Ombi.Store.Entities.EmbyContent", "Series") + .WithMany("Episodes") + .HasForeignKey("ParentId") + .HasPrincipalKey("EmbyId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.NotificationUserId", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany("NotificationUserIds") + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexEpisode", b => + { + b.HasOne("Ombi.Store.Entities.PlexServerContent", "Series") + .WithMany("Episodes") + .HasForeignKey("GrandparentKey") + .HasPrincipalKey("Key") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexSeasonsContent", b => + { + b.HasOne("Ombi.Store.Entities.PlexServerContent") + .WithMany("Seasons") + .HasForeignKey("PlexServerContentId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.AlbumRequest", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "RequestedUser") + .WithMany() + .HasForeignKey("RequestedUserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.ChildRequests", b => + { + b.HasOne("Ombi.Store.Entities.Requests.TvRequests", "ParentRequest") + .WithMany("ChildRequests") + .HasForeignKey("ParentRequestId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Ombi.Store.Entities.OmbiUser", "RequestedUser") + .WithMany() + .HasForeignKey("RequestedUserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueComments", b => + { + b.HasOne("Ombi.Store.Entities.Requests.Issues", "Issues") + .WithMany("Comments") + .HasForeignKey("IssuesId"); + + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.Issues", b => + { + b.HasOne("Ombi.Store.Entities.Requests.IssueCategory", "IssueCategory") + .WithMany() + .HasForeignKey("IssueCategoryId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Ombi.Store.Entities.Requests.ChildRequests") + .WithMany("Issues") + .HasForeignKey("IssueId"); + + b.HasOne("Ombi.Store.Entities.Requests.MovieRequests") + .WithMany("Issues") + .HasForeignKey("IssueId"); + + b.HasOne("Ombi.Store.Entities.OmbiUser", "UserReported") + .WithMany() + .HasForeignKey("UserReportedId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.MovieRequests", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "RequestedUser") + .WithMany() + .HasForeignKey("RequestedUserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.RequestLog", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.RequestSubscription", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Tokens", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.EpisodeRequests", b => + { + b.HasOne("Ombi.Store.Repository.Requests.SeasonRequests", "Season") + .WithMany("Episodes") + .HasForeignKey("SeasonId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.SeasonRequests", b => + { + b.HasOne("Ombi.Store.Entities.Requests.ChildRequests", "ChildRequest") + .WithMany("SeasonRequests") + .HasForeignKey("ChildRequestId") + .OnDelete(DeleteBehavior.Cascade); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Ombi.Store/Migrations/20180828083219_MusicIssues.cs b/src/Ombi.Store/Migrations/20180828083219_MusicIssues.cs new file mode 100644 index 000000000..94a06ff18 --- /dev/null +++ b/src/Ombi.Store/Migrations/20180828083219_MusicIssues.cs @@ -0,0 +1,33 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Ombi.Store.Migrations +{ + public partial class MusicIssues : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "AlbumId", + table: "RecentlyAddedLog", + nullable: true); + + migrationBuilder.AddColumn( + name: "AddedAt", + table: "LidarrAlbumCache", + nullable: false, + defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified)); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "AlbumId", + table: "RecentlyAddedLog"); + + migrationBuilder.DropColumn( + name: "AddedAt", + table: "LidarrAlbumCache"); + } + } +} diff --git a/src/Ombi.Store/Migrations/20180919073124_UserNotificationPreferences.Designer.cs b/src/Ombi.Store/Migrations/20180919073124_UserNotificationPreferences.Designer.cs new file mode 100644 index 000000000..d61ea31ba --- /dev/null +++ b/src/Ombi.Store/Migrations/20180919073124_UserNotificationPreferences.Designer.cs @@ -0,0 +1,1118 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Ombi.Store.Context; + +namespace Ombi.Store.Migrations +{ + [DbContext(typeof(OmbiContext))] + [Migration("20180919073124_UserNotificationPreferences")] + partial class UserNotificationPreferences + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.1.3-rtm-32065"); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken(); + + b.Property("Name") + .HasMaxLength(256); + + b.Property("NormalizedName") + .HasMaxLength(256); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasName("RoleNameIndex"); + + b.ToTable("AspNetRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ClaimType"); + + b.Property("ClaimValue"); + + b.Property("RoleId") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ClaimType"); + + b.Property("ClaimValue"); + + b.Property("UserId") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider"); + + b.Property("ProviderKey"); + + b.Property("ProviderDisplayName"); + + b.Property("UserId") + .IsRequired(); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId"); + + b.Property("RoleId"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId"); + + b.Property("LoginProvider"); + + b.Property("Name"); + + b.Property("Value"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.ApplicationConfiguration", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Type"); + + b.Property("Value"); + + b.HasKey("Id"); + + b.ToTable("ApplicationConfiguration"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Audit", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AuditArea"); + + b.Property("AuditType"); + + b.Property("DateTime"); + + b.Property("Description"); + + b.Property("User"); + + b.HasKey("Id"); + + b.ToTable("Audit"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.CouchPotatoCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("TheMovieDbId"); + + b.HasKey("Id"); + + b.ToTable("CouchPotatoCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.EmbyContent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("EmbyId") + .IsRequired(); + + b.Property("ImdbId"); + + b.Property("ProviderId"); + + b.Property("TheMovieDbId"); + + b.Property("Title"); + + b.Property("TvDbId"); + + b.Property("Type"); + + b.Property("Url"); + + b.HasKey("Id"); + + b.ToTable("EmbyContent"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.EmbyEpisode", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("EmbyId"); + + b.Property("EpisodeNumber"); + + b.Property("ImdbId"); + + b.Property("ParentId"); + + b.Property("ProviderId"); + + b.Property("SeasonNumber"); + + b.Property("TheMovieDbId"); + + b.Property("Title"); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.HasIndex("ParentId"); + + b.ToTable("EmbyEpisode"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.GlobalSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Content"); + + b.Property("SettingsName"); + + b.HasKey("Id"); + + b.ToTable("GlobalSettings"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.LidarrAlbumCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("ArtistId"); + + b.Property("ForeignAlbumId"); + + b.Property("Monitored"); + + b.Property("PercentOfTracks"); + + b.Property("ReleaseDate"); + + b.Property("Title"); + + b.Property("TrackCount"); + + b.HasKey("Id"); + + b.ToTable("LidarrAlbumCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.LidarrArtistCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ArtistId"); + + b.Property("ArtistName"); + + b.Property("ForeignArtistId"); + + b.Property("Monitored"); + + b.HasKey("Id"); + + b.ToTable("LidarrArtistCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.NotificationTemplates", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Agent"); + + b.Property("Enabled"); + + b.Property("Message"); + + b.Property("NotificationType"); + + b.Property("Subject"); + + b.HasKey("Id"); + + b.ToTable("NotificationTemplates"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.NotificationUserId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("PlayerId"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("NotificationUserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.OmbiUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AccessFailedCount"); + + b.Property("Alias"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken(); + + b.Property("Email") + .HasMaxLength(256); + + b.Property("EmailConfirmed"); + + b.Property("EmbyConnectUserId"); + + b.Property("EpisodeRequestLimit"); + + b.Property("LastLoggedIn"); + + b.Property("LockoutEnabled"); + + b.Property("LockoutEnd"); + + b.Property("MovieRequestLimit"); + + b.Property("MusicRequestLimit"); + + b.Property("NormalizedEmail") + .HasMaxLength(256); + + b.Property("NormalizedUserName") + .HasMaxLength(256); + + b.Property("PasswordHash"); + + b.Property("PhoneNumber"); + + b.Property("PhoneNumberConfirmed"); + + b.Property("ProviderUserId"); + + b.Property("SecurityStamp"); + + b.Property("TwoFactorEnabled"); + + b.Property("UserAccessToken"); + + b.Property("UserName") + .HasMaxLength(256); + + b.Property("UserType"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasName("UserNameIndex"); + + b.ToTable("AspNetUsers"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexEpisode", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("EpisodeNumber"); + + b.Property("GrandparentKey"); + + b.Property("Key"); + + b.Property("ParentKey"); + + b.Property("SeasonNumber"); + + b.Property("Title"); + + b.HasKey("Id"); + + b.HasIndex("GrandparentKey"); + + b.ToTable("PlexEpisode"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexSeasonsContent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ParentKey"); + + b.Property("PlexContentId"); + + b.Property("PlexServerContentId"); + + b.Property("SeasonKey"); + + b.Property("SeasonNumber"); + + b.HasKey("Id"); + + b.HasIndex("PlexServerContentId"); + + b.ToTable("PlexSeasonsContent"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexServerContent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("ImdbId"); + + b.Property("Key"); + + b.Property("Quality"); + + b.Property("ReleaseYear"); + + b.Property("TheMovieDbId"); + + b.Property("Title"); + + b.Property("TvDbId"); + + b.Property("Type"); + + b.Property("Url"); + + b.HasKey("Id"); + + b.ToTable("PlexServerContent"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.RadarrCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("HasFile"); + + b.Property("TheMovieDbId"); + + b.HasKey("Id"); + + b.ToTable("RadarrCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.RecentlyAddedLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("AlbumId"); + + b.Property("ContentId"); + + b.Property("ContentType"); + + b.Property("EpisodeNumber"); + + b.Property("SeasonNumber"); + + b.Property("Type"); + + b.HasKey("Id"); + + b.ToTable("RecentlyAddedLog"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.AlbumRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Approved"); + + b.Property("ArtistName"); + + b.Property("Available"); + + b.Property("Cover"); + + b.Property("Denied"); + + b.Property("DeniedReason"); + + b.Property("Disk"); + + b.Property("ForeignAlbumId"); + + b.Property("ForeignArtistId"); + + b.Property("MarkedAsApproved"); + + b.Property("MarkedAsAvailable"); + + b.Property("MarkedAsDenied"); + + b.Property("Rating"); + + b.Property("ReleaseDate"); + + b.Property("RequestType"); + + b.Property("RequestedDate"); + + b.Property("RequestedUserId"); + + b.Property("Title"); + + b.HasKey("Id"); + + b.HasIndex("RequestedUserId"); + + b.ToTable("AlbumRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.ChildRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Approved"); + + b.Property("Available"); + + b.Property("Denied"); + + b.Property("DeniedReason"); + + b.Property("IssueId"); + + b.Property("MarkedAsApproved"); + + b.Property("MarkedAsAvailable"); + + b.Property("MarkedAsDenied"); + + b.Property("ParentRequestId"); + + b.Property("RequestType"); + + b.Property("RequestedDate"); + + b.Property("RequestedUserId"); + + b.Property("SeriesType"); + + b.Property("Title"); + + b.HasKey("Id"); + + b.HasIndex("ParentRequestId"); + + b.HasIndex("RequestedUserId"); + + b.ToTable("ChildRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Value"); + + b.HasKey("Id"); + + b.ToTable("IssueCategory"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueComments", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Comment"); + + b.Property("Date"); + + b.Property("IssuesId"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("IssuesId"); + + b.HasIndex("UserId"); + + b.ToTable("IssueComments"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.Issues", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Description"); + + b.Property("IssueCategoryId"); + + b.Property("IssueId"); + + b.Property("ProviderId"); + + b.Property("RequestId"); + + b.Property("RequestType"); + + b.Property("ResovledDate"); + + b.Property("Status"); + + b.Property("Subject"); + + b.Property("Title"); + + b.Property("UserReportedId"); + + b.HasKey("Id"); + + b.HasIndex("IssueCategoryId"); + + b.HasIndex("IssueId"); + + b.HasIndex("UserReportedId"); + + b.ToTable("Issues"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.MovieRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Approved"); + + b.Property("Available"); + + b.Property("Background"); + + b.Property("Denied"); + + b.Property("DeniedReason"); + + b.Property("DigitalReleaseDate"); + + b.Property("ImdbId"); + + b.Property("IssueId"); + + b.Property("MarkedAsApproved"); + + b.Property("MarkedAsAvailable"); + + b.Property("MarkedAsDenied"); + + b.Property("Overview"); + + b.Property("PosterPath"); + + b.Property("QualityOverride"); + + b.Property("ReleaseDate"); + + b.Property("RequestType"); + + b.Property("RequestedDate"); + + b.Property("RequestedUserId"); + + b.Property("RootPathOverride"); + + b.Property("Status"); + + b.Property("TheMovieDbId"); + + b.Property("Title"); + + b.HasKey("Id"); + + b.HasIndex("RequestedUserId"); + + b.ToTable("MovieRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.RequestLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("EpisodeCount"); + + b.Property("RequestDate"); + + b.Property("RequestId"); + + b.Property("RequestType"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("RequestLog"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.TvRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Background"); + + b.Property("ImdbId"); + + b.Property("Overview"); + + b.Property("PosterPath"); + + b.Property("QualityOverride"); + + b.Property("ReleaseDate"); + + b.Property("RootFolder"); + + b.Property("Status"); + + b.Property("Title"); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.ToTable("TvRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.RequestSubscription", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("RequestId"); + + b.Property("RequestType"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("RequestSubscription"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.SickRageCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.ToTable("SickRageCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.SickRageEpisodeCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("EpisodeNumber"); + + b.Property("SeasonNumber"); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.ToTable("SickRageEpisodeCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.SonarrCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.ToTable("SonarrCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.SonarrEpisodeCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("EpisodeNumber"); + + b.Property("HasFile"); + + b.Property("SeasonNumber"); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.ToTable("SonarrEpisodeCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Tokens", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Token"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Tokens"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.UserNotificationPreferences", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Agent"); + + b.Property("Enabled"); + + b.Property("UserId"); + + b.Property("Value"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("UserNotificationPreferences"); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.EpisodeRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AirDate"); + + b.Property("Approved"); + + b.Property("Available"); + + b.Property("EpisodeNumber"); + + b.Property("Requested"); + + b.Property("SeasonId"); + + b.Property("Title"); + + b.Property("Url"); + + b.HasKey("Id"); + + b.HasIndex("SeasonId"); + + b.ToTable("EpisodeRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.SeasonRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChildRequestId"); + + b.Property("SeasonNumber"); + + b.HasKey("Id"); + + b.HasIndex("ChildRequestId"); + + b.ToTable("SeasonRequests"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole") + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole") + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Ombi.Store.Entities.OmbiUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Ombi.Store.Entities.EmbyEpisode", b => + { + b.HasOne("Ombi.Store.Entities.EmbyContent", "Series") + .WithMany("Episodes") + .HasForeignKey("ParentId") + .HasPrincipalKey("EmbyId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.NotificationUserId", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany("NotificationUserIds") + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexEpisode", b => + { + b.HasOne("Ombi.Store.Entities.PlexServerContent", "Series") + .WithMany("Episodes") + .HasForeignKey("GrandparentKey") + .HasPrincipalKey("Key") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexSeasonsContent", b => + { + b.HasOne("Ombi.Store.Entities.PlexServerContent") + .WithMany("Seasons") + .HasForeignKey("PlexServerContentId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.AlbumRequest", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "RequestedUser") + .WithMany() + .HasForeignKey("RequestedUserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.ChildRequests", b => + { + b.HasOne("Ombi.Store.Entities.Requests.TvRequests", "ParentRequest") + .WithMany("ChildRequests") + .HasForeignKey("ParentRequestId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Ombi.Store.Entities.OmbiUser", "RequestedUser") + .WithMany() + .HasForeignKey("RequestedUserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueComments", b => + { + b.HasOne("Ombi.Store.Entities.Requests.Issues", "Issues") + .WithMany("Comments") + .HasForeignKey("IssuesId"); + + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.Issues", b => + { + b.HasOne("Ombi.Store.Entities.Requests.IssueCategory", "IssueCategory") + .WithMany() + .HasForeignKey("IssueCategoryId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Ombi.Store.Entities.Requests.ChildRequests") + .WithMany("Issues") + .HasForeignKey("IssueId"); + + b.HasOne("Ombi.Store.Entities.Requests.MovieRequests") + .WithMany("Issues") + .HasForeignKey("IssueId"); + + b.HasOne("Ombi.Store.Entities.OmbiUser", "UserReported") + .WithMany() + .HasForeignKey("UserReportedId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.MovieRequests", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "RequestedUser") + .WithMany() + .HasForeignKey("RequestedUserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.RequestLog", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.RequestSubscription", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Tokens", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.UserNotificationPreferences", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany("UserNotificationPreferences") + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.EpisodeRequests", b => + { + b.HasOne("Ombi.Store.Repository.Requests.SeasonRequests", "Season") + .WithMany("Episodes") + .HasForeignKey("SeasonId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.SeasonRequests", b => + { + b.HasOne("Ombi.Store.Entities.Requests.ChildRequests", "ChildRequest") + .WithMany("SeasonRequests") + .HasForeignKey("ChildRequestId") + .OnDelete(DeleteBehavior.Cascade); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Ombi.Store/Migrations/20180919073124_UserNotificationPreferences.cs b/src/Ombi.Store/Migrations/20180919073124_UserNotificationPreferences.cs new file mode 100644 index 000000000..adb062af7 --- /dev/null +++ b/src/Ombi.Store/Migrations/20180919073124_UserNotificationPreferences.cs @@ -0,0 +1,43 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Ombi.Store.Migrations +{ + public partial class UserNotificationPreferences : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "UserNotificationPreferences", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + UserId = table.Column(nullable: true), + Agent = table.Column(nullable: false), + Enabled = table.Column(nullable: false), + Value = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_UserNotificationPreferences", x => x.Id); + table.ForeignKey( + name: "FK_UserNotificationPreferences_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateIndex( + name: "IX_UserNotificationPreferences_UserId", + table: "UserNotificationPreferences", + column: "UserId"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "UserNotificationPreferences"); + } + } +} diff --git a/src/Ombi.Store/Migrations/20180920201101_UserQualityProfiles.Designer.cs b/src/Ombi.Store/Migrations/20180920201101_UserQualityProfiles.Designer.cs new file mode 100644 index 000000000..c9e18ef3a --- /dev/null +++ b/src/Ombi.Store/Migrations/20180920201101_UserQualityProfiles.Designer.cs @@ -0,0 +1,1151 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Ombi.Store.Context; + +namespace Ombi.Store.Migrations +{ + [DbContext(typeof(OmbiContext))] + [Migration("20180920201101_UserQualityProfiles")] + partial class UserQualityProfiles + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.1.3-rtm-32065"); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken(); + + b.Property("Name") + .HasMaxLength(256); + + b.Property("NormalizedName") + .HasMaxLength(256); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasName("RoleNameIndex"); + + b.ToTable("AspNetRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ClaimType"); + + b.Property("ClaimValue"); + + b.Property("RoleId") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ClaimType"); + + b.Property("ClaimValue"); + + b.Property("UserId") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider"); + + b.Property("ProviderKey"); + + b.Property("ProviderDisplayName"); + + b.Property("UserId") + .IsRequired(); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId"); + + b.Property("RoleId"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId"); + + b.Property("LoginProvider"); + + b.Property("Name"); + + b.Property("Value"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.ApplicationConfiguration", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Type"); + + b.Property("Value"); + + b.HasKey("Id"); + + b.ToTable("ApplicationConfiguration"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Audit", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AuditArea"); + + b.Property("AuditType"); + + b.Property("DateTime"); + + b.Property("Description"); + + b.Property("User"); + + b.HasKey("Id"); + + b.ToTable("Audit"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.CouchPotatoCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("TheMovieDbId"); + + b.HasKey("Id"); + + b.ToTable("CouchPotatoCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.EmbyContent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("EmbyId") + .IsRequired(); + + b.Property("ImdbId"); + + b.Property("ProviderId"); + + b.Property("TheMovieDbId"); + + b.Property("Title"); + + b.Property("TvDbId"); + + b.Property("Type"); + + b.Property("Url"); + + b.HasKey("Id"); + + b.ToTable("EmbyContent"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.EmbyEpisode", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("EmbyId"); + + b.Property("EpisodeNumber"); + + b.Property("ImdbId"); + + b.Property("ParentId"); + + b.Property("ProviderId"); + + b.Property("SeasonNumber"); + + b.Property("TheMovieDbId"); + + b.Property("Title"); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.HasIndex("ParentId"); + + b.ToTable("EmbyEpisode"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.GlobalSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Content"); + + b.Property("SettingsName"); + + b.HasKey("Id"); + + b.ToTable("GlobalSettings"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.LidarrAlbumCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("ArtistId"); + + b.Property("ForeignAlbumId"); + + b.Property("Monitored"); + + b.Property("PercentOfTracks"); + + b.Property("ReleaseDate"); + + b.Property("Title"); + + b.Property("TrackCount"); + + b.HasKey("Id"); + + b.ToTable("LidarrAlbumCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.LidarrArtistCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ArtistId"); + + b.Property("ArtistName"); + + b.Property("ForeignArtistId"); + + b.Property("Monitored"); + + b.HasKey("Id"); + + b.ToTable("LidarrArtistCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.NotificationTemplates", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Agent"); + + b.Property("Enabled"); + + b.Property("Message"); + + b.Property("NotificationType"); + + b.Property("Subject"); + + b.HasKey("Id"); + + b.ToTable("NotificationTemplates"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.NotificationUserId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("PlayerId"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("NotificationUserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.OmbiUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AccessFailedCount"); + + b.Property("Alias"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken(); + + b.Property("Email") + .HasMaxLength(256); + + b.Property("EmailConfirmed"); + + b.Property("EmbyConnectUserId"); + + b.Property("EpisodeRequestLimit"); + + b.Property("LastLoggedIn"); + + b.Property("LockoutEnabled"); + + b.Property("LockoutEnd"); + + b.Property("MovieRequestLimit"); + + b.Property("MusicRequestLimit"); + + b.Property("NormalizedEmail") + .HasMaxLength(256); + + b.Property("NormalizedUserName") + .HasMaxLength(256); + + b.Property("PasswordHash"); + + b.Property("PhoneNumber"); + + b.Property("PhoneNumberConfirmed"); + + b.Property("ProviderUserId"); + + b.Property("SecurityStamp"); + + b.Property("TwoFactorEnabled"); + + b.Property("UserAccessToken"); + + b.Property("UserName") + .HasMaxLength(256); + + b.Property("UserType"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasName("UserNameIndex"); + + b.ToTable("AspNetUsers"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexEpisode", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("EpisodeNumber"); + + b.Property("GrandparentKey"); + + b.Property("Key"); + + b.Property("ParentKey"); + + b.Property("SeasonNumber"); + + b.Property("Title"); + + b.HasKey("Id"); + + b.HasIndex("GrandparentKey"); + + b.ToTable("PlexEpisode"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexSeasonsContent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ParentKey"); + + b.Property("PlexContentId"); + + b.Property("PlexServerContentId"); + + b.Property("SeasonKey"); + + b.Property("SeasonNumber"); + + b.HasKey("Id"); + + b.HasIndex("PlexServerContentId"); + + b.ToTable("PlexSeasonsContent"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexServerContent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("ImdbId"); + + b.Property("Key"); + + b.Property("Quality"); + + b.Property("ReleaseYear"); + + b.Property("TheMovieDbId"); + + b.Property("Title"); + + b.Property("TvDbId"); + + b.Property("Type"); + + b.Property("Url"); + + b.HasKey("Id"); + + b.ToTable("PlexServerContent"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.RadarrCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("HasFile"); + + b.Property("TheMovieDbId"); + + b.HasKey("Id"); + + b.ToTable("RadarrCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.RecentlyAddedLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("AlbumId"); + + b.Property("ContentId"); + + b.Property("ContentType"); + + b.Property("EpisodeNumber"); + + b.Property("SeasonNumber"); + + b.Property("Type"); + + b.HasKey("Id"); + + b.ToTable("RecentlyAddedLog"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.AlbumRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Approved"); + + b.Property("ArtistName"); + + b.Property("Available"); + + b.Property("Cover"); + + b.Property("Denied"); + + b.Property("DeniedReason"); + + b.Property("Disk"); + + b.Property("ForeignAlbumId"); + + b.Property("ForeignArtistId"); + + b.Property("MarkedAsApproved"); + + b.Property("MarkedAsAvailable"); + + b.Property("MarkedAsDenied"); + + b.Property("Rating"); + + b.Property("ReleaseDate"); + + b.Property("RequestType"); + + b.Property("RequestedDate"); + + b.Property("RequestedUserId"); + + b.Property("Title"); + + b.HasKey("Id"); + + b.HasIndex("RequestedUserId"); + + b.ToTable("AlbumRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.ChildRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Approved"); + + b.Property("Available"); + + b.Property("Denied"); + + b.Property("DeniedReason"); + + b.Property("IssueId"); + + b.Property("MarkedAsApproved"); + + b.Property("MarkedAsAvailable"); + + b.Property("MarkedAsDenied"); + + b.Property("ParentRequestId"); + + b.Property("RequestType"); + + b.Property("RequestedDate"); + + b.Property("RequestedUserId"); + + b.Property("SeriesType"); + + b.Property("Title"); + + b.HasKey("Id"); + + b.HasIndex("ParentRequestId"); + + b.HasIndex("RequestedUserId"); + + b.ToTable("ChildRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Value"); + + b.HasKey("Id"); + + b.ToTable("IssueCategory"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueComments", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Comment"); + + b.Property("Date"); + + b.Property("IssuesId"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("IssuesId"); + + b.HasIndex("UserId"); + + b.ToTable("IssueComments"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.Issues", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Description"); + + b.Property("IssueCategoryId"); + + b.Property("IssueId"); + + b.Property("ProviderId"); + + b.Property("RequestId"); + + b.Property("RequestType"); + + b.Property("ResovledDate"); + + b.Property("Status"); + + b.Property("Subject"); + + b.Property("Title"); + + b.Property("UserReportedId"); + + b.HasKey("Id"); + + b.HasIndex("IssueCategoryId"); + + b.HasIndex("IssueId"); + + b.HasIndex("UserReportedId"); + + b.ToTable("Issues"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.MovieRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Approved"); + + b.Property("Available"); + + b.Property("Background"); + + b.Property("Denied"); + + b.Property("DeniedReason"); + + b.Property("DigitalReleaseDate"); + + b.Property("ImdbId"); + + b.Property("IssueId"); + + b.Property("MarkedAsApproved"); + + b.Property("MarkedAsAvailable"); + + b.Property("MarkedAsDenied"); + + b.Property("Overview"); + + b.Property("PosterPath"); + + b.Property("QualityOverride"); + + b.Property("ReleaseDate"); + + b.Property("RequestType"); + + b.Property("RequestedDate"); + + b.Property("RequestedUserId"); + + b.Property("RootPathOverride"); + + b.Property("Status"); + + b.Property("TheMovieDbId"); + + b.Property("Title"); + + b.HasKey("Id"); + + b.HasIndex("RequestedUserId"); + + b.ToTable("MovieRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.RequestLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("EpisodeCount"); + + b.Property("RequestDate"); + + b.Property("RequestId"); + + b.Property("RequestType"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("RequestLog"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.TvRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Background"); + + b.Property("ImdbId"); + + b.Property("Overview"); + + b.Property("PosterPath"); + + b.Property("QualityOverride"); + + b.Property("ReleaseDate"); + + b.Property("RootFolder"); + + b.Property("Status"); + + b.Property("Title"); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.ToTable("TvRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.RequestSubscription", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("RequestId"); + + b.Property("RequestType"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("RequestSubscription"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.SickRageCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.ToTable("SickRageCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.SickRageEpisodeCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("EpisodeNumber"); + + b.Property("SeasonNumber"); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.ToTable("SickRageEpisodeCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.SonarrCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.ToTable("SonarrCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.SonarrEpisodeCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("EpisodeNumber"); + + b.Property("HasFile"); + + b.Property("SeasonNumber"); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.ToTable("SonarrEpisodeCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Tokens", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Token"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Tokens"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.UserNotificationPreferences", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Agent"); + + b.Property("Enabled"); + + b.Property("UserId"); + + b.Property("Value"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("UserNotificationPreferences"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.UserQualityProfiles", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("RadarrQualityProfile"); + + b.Property("RadarrRootPath"); + + b.Property("SonarrQualityProfile"); + + b.Property("SonarrQualityProfileAnime"); + + b.Property("SonarrRootPath"); + + b.Property("SonarrRootPathAnime"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("UserQualityProfiles"); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.EpisodeRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AirDate"); + + b.Property("Approved"); + + b.Property("Available"); + + b.Property("EpisodeNumber"); + + b.Property("Requested"); + + b.Property("SeasonId"); + + b.Property("Title"); + + b.Property("Url"); + + b.HasKey("Id"); + + b.HasIndex("SeasonId"); + + b.ToTable("EpisodeRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.SeasonRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChildRequestId"); + + b.Property("SeasonNumber"); + + b.HasKey("Id"); + + b.HasIndex("ChildRequestId"); + + b.ToTable("SeasonRequests"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole") + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole") + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Ombi.Store.Entities.OmbiUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Ombi.Store.Entities.EmbyEpisode", b => + { + b.HasOne("Ombi.Store.Entities.EmbyContent", "Series") + .WithMany("Episodes") + .HasForeignKey("ParentId") + .HasPrincipalKey("EmbyId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.NotificationUserId", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany("NotificationUserIds") + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexEpisode", b => + { + b.HasOne("Ombi.Store.Entities.PlexServerContent", "Series") + .WithMany("Episodes") + .HasForeignKey("GrandparentKey") + .HasPrincipalKey("Key") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexSeasonsContent", b => + { + b.HasOne("Ombi.Store.Entities.PlexServerContent") + .WithMany("Seasons") + .HasForeignKey("PlexServerContentId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.AlbumRequest", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "RequestedUser") + .WithMany() + .HasForeignKey("RequestedUserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.ChildRequests", b => + { + b.HasOne("Ombi.Store.Entities.Requests.TvRequests", "ParentRequest") + .WithMany("ChildRequests") + .HasForeignKey("ParentRequestId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Ombi.Store.Entities.OmbiUser", "RequestedUser") + .WithMany() + .HasForeignKey("RequestedUserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueComments", b => + { + b.HasOne("Ombi.Store.Entities.Requests.Issues", "Issues") + .WithMany("Comments") + .HasForeignKey("IssuesId"); + + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.Issues", b => + { + b.HasOne("Ombi.Store.Entities.Requests.IssueCategory", "IssueCategory") + .WithMany() + .HasForeignKey("IssueCategoryId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Ombi.Store.Entities.Requests.ChildRequests") + .WithMany("Issues") + .HasForeignKey("IssueId"); + + b.HasOne("Ombi.Store.Entities.Requests.MovieRequests") + .WithMany("Issues") + .HasForeignKey("IssueId"); + + b.HasOne("Ombi.Store.Entities.OmbiUser", "UserReported") + .WithMany() + .HasForeignKey("UserReportedId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.MovieRequests", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "RequestedUser") + .WithMany() + .HasForeignKey("RequestedUserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.RequestLog", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.RequestSubscription", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Tokens", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.UserNotificationPreferences", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany("UserNotificationPreferences") + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.UserQualityProfiles", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.EpisodeRequests", b => + { + b.HasOne("Ombi.Store.Repository.Requests.SeasonRequests", "Season") + .WithMany("Episodes") + .HasForeignKey("SeasonId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.SeasonRequests", b => + { + b.HasOne("Ombi.Store.Entities.Requests.ChildRequests", "ChildRequest") + .WithMany("SeasonRequests") + .HasForeignKey("ChildRequestId") + .OnDelete(DeleteBehavior.Cascade); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Ombi.Store/Migrations/20180920201101_UserQualityProfiles.cs b/src/Ombi.Store/Migrations/20180920201101_UserQualityProfiles.cs new file mode 100644 index 000000000..bbcc2e79e --- /dev/null +++ b/src/Ombi.Store/Migrations/20180920201101_UserQualityProfiles.cs @@ -0,0 +1,46 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Ombi.Store.Migrations +{ + public partial class UserQualityProfiles : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "UserQualityProfiles", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + UserId = table.Column(nullable: true), + SonarrQualityProfileAnime = table.Column(nullable: false), + SonarrRootPathAnime = table.Column(nullable: false), + SonarrRootPath = table.Column(nullable: false), + SonarrQualityProfile = table.Column(nullable: false), + RadarrRootPath = table.Column(nullable: false), + RadarrQualityProfile = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_UserQualityProfiles", x => x.Id); + table.ForeignKey( + name: "FK_UserQualityProfiles_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateIndex( + name: "IX_UserQualityProfiles_UserId", + table: "UserQualityProfiles", + column: "UserId"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "UserQualityProfiles"); + } + } +} diff --git a/src/Ombi.Store/Migrations/20180928201334_Votes.Designer.cs b/src/Ombi.Store/Migrations/20180928201334_Votes.Designer.cs new file mode 100644 index 000000000..5d9b47c26 --- /dev/null +++ b/src/Ombi.Store/Migrations/20180928201334_Votes.Designer.cs @@ -0,0 +1,1182 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Ombi.Store.Context; + +namespace Ombi.Store.Migrations +{ + [DbContext(typeof(OmbiContext))] + [Migration("20180928201334_Votes")] + partial class Votes + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.1.3-rtm-32065"); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken(); + + b.Property("Name") + .HasMaxLength(256); + + b.Property("NormalizedName") + .HasMaxLength(256); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasName("RoleNameIndex"); + + b.ToTable("AspNetRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ClaimType"); + + b.Property("ClaimValue"); + + b.Property("RoleId") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ClaimType"); + + b.Property("ClaimValue"); + + b.Property("UserId") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider"); + + b.Property("ProviderKey"); + + b.Property("ProviderDisplayName"); + + b.Property("UserId") + .IsRequired(); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId"); + + b.Property("RoleId"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId"); + + b.Property("LoginProvider"); + + b.Property("Name"); + + b.Property("Value"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.ApplicationConfiguration", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Type"); + + b.Property("Value"); + + b.HasKey("Id"); + + b.ToTable("ApplicationConfiguration"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Audit", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AuditArea"); + + b.Property("AuditType"); + + b.Property("DateTime"); + + b.Property("Description"); + + b.Property("User"); + + b.HasKey("Id"); + + b.ToTable("Audit"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.CouchPotatoCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("TheMovieDbId"); + + b.HasKey("Id"); + + b.ToTable("CouchPotatoCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.EmbyContent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("EmbyId") + .IsRequired(); + + b.Property("ImdbId"); + + b.Property("ProviderId"); + + b.Property("TheMovieDbId"); + + b.Property("Title"); + + b.Property("TvDbId"); + + b.Property("Type"); + + b.Property("Url"); + + b.HasKey("Id"); + + b.ToTable("EmbyContent"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.EmbyEpisode", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("EmbyId"); + + b.Property("EpisodeNumber"); + + b.Property("ImdbId"); + + b.Property("ParentId"); + + b.Property("ProviderId"); + + b.Property("SeasonNumber"); + + b.Property("TheMovieDbId"); + + b.Property("Title"); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.HasIndex("ParentId"); + + b.ToTable("EmbyEpisode"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.GlobalSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Content"); + + b.Property("SettingsName"); + + b.HasKey("Id"); + + b.ToTable("GlobalSettings"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.LidarrAlbumCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("ArtistId"); + + b.Property("ForeignAlbumId"); + + b.Property("Monitored"); + + b.Property("PercentOfTracks"); + + b.Property("ReleaseDate"); + + b.Property("Title"); + + b.Property("TrackCount"); + + b.HasKey("Id"); + + b.ToTable("LidarrAlbumCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.LidarrArtistCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ArtistId"); + + b.Property("ArtistName"); + + b.Property("ForeignArtistId"); + + b.Property("Monitored"); + + b.HasKey("Id"); + + b.ToTable("LidarrArtistCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.NotificationTemplates", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Agent"); + + b.Property("Enabled"); + + b.Property("Message"); + + b.Property("NotificationType"); + + b.Property("Subject"); + + b.HasKey("Id"); + + b.ToTable("NotificationTemplates"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.NotificationUserId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("PlayerId"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("NotificationUserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.OmbiUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AccessFailedCount"); + + b.Property("Alias"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken(); + + b.Property("Email") + .HasMaxLength(256); + + b.Property("EmailConfirmed"); + + b.Property("EmbyConnectUserId"); + + b.Property("EpisodeRequestLimit"); + + b.Property("LastLoggedIn"); + + b.Property("LockoutEnabled"); + + b.Property("LockoutEnd"); + + b.Property("MovieRequestLimit"); + + b.Property("MusicRequestLimit"); + + b.Property("NormalizedEmail") + .HasMaxLength(256); + + b.Property("NormalizedUserName") + .HasMaxLength(256); + + b.Property("PasswordHash"); + + b.Property("PhoneNumber"); + + b.Property("PhoneNumberConfirmed"); + + b.Property("ProviderUserId"); + + b.Property("SecurityStamp"); + + b.Property("TwoFactorEnabled"); + + b.Property("UserAccessToken"); + + b.Property("UserName") + .HasMaxLength(256); + + b.Property("UserType"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasName("UserNameIndex"); + + b.ToTable("AspNetUsers"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexEpisode", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("EpisodeNumber"); + + b.Property("GrandparentKey"); + + b.Property("Key"); + + b.Property("ParentKey"); + + b.Property("SeasonNumber"); + + b.Property("Title"); + + b.HasKey("Id"); + + b.HasIndex("GrandparentKey"); + + b.ToTable("PlexEpisode"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexSeasonsContent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ParentKey"); + + b.Property("PlexContentId"); + + b.Property("PlexServerContentId"); + + b.Property("SeasonKey"); + + b.Property("SeasonNumber"); + + b.HasKey("Id"); + + b.HasIndex("PlexServerContentId"); + + b.ToTable("PlexSeasonsContent"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexServerContent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("ImdbId"); + + b.Property("Key"); + + b.Property("Quality"); + + b.Property("ReleaseYear"); + + b.Property("TheMovieDbId"); + + b.Property("Title"); + + b.Property("TvDbId"); + + b.Property("Type"); + + b.Property("Url"); + + b.HasKey("Id"); + + b.ToTable("PlexServerContent"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.RadarrCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("HasFile"); + + b.Property("TheMovieDbId"); + + b.HasKey("Id"); + + b.ToTable("RadarrCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.RecentlyAddedLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("AlbumId"); + + b.Property("ContentId"); + + b.Property("ContentType"); + + b.Property("EpisodeNumber"); + + b.Property("SeasonNumber"); + + b.Property("Type"); + + b.HasKey("Id"); + + b.ToTable("RecentlyAddedLog"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.AlbumRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Approved"); + + b.Property("ArtistName"); + + b.Property("Available"); + + b.Property("Cover"); + + b.Property("Denied"); + + b.Property("DeniedReason"); + + b.Property("Disk"); + + b.Property("ForeignAlbumId"); + + b.Property("ForeignArtistId"); + + b.Property("MarkedAsApproved"); + + b.Property("MarkedAsAvailable"); + + b.Property("MarkedAsDenied"); + + b.Property("Rating"); + + b.Property("ReleaseDate"); + + b.Property("RequestType"); + + b.Property("RequestedDate"); + + b.Property("RequestedUserId"); + + b.Property("Title"); + + b.HasKey("Id"); + + b.HasIndex("RequestedUserId"); + + b.ToTable("AlbumRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.ChildRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Approved"); + + b.Property("Available"); + + b.Property("Denied"); + + b.Property("DeniedReason"); + + b.Property("IssueId"); + + b.Property("MarkedAsApproved"); + + b.Property("MarkedAsAvailable"); + + b.Property("MarkedAsDenied"); + + b.Property("ParentRequestId"); + + b.Property("RequestType"); + + b.Property("RequestedDate"); + + b.Property("RequestedUserId"); + + b.Property("SeriesType"); + + b.Property("Title"); + + b.HasKey("Id"); + + b.HasIndex("ParentRequestId"); + + b.HasIndex("RequestedUserId"); + + b.ToTable("ChildRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Value"); + + b.HasKey("Id"); + + b.ToTable("IssueCategory"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueComments", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Comment"); + + b.Property("Date"); + + b.Property("IssuesId"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("IssuesId"); + + b.HasIndex("UserId"); + + b.ToTable("IssueComments"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.Issues", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Description"); + + b.Property("IssueCategoryId"); + + b.Property("IssueId"); + + b.Property("ProviderId"); + + b.Property("RequestId"); + + b.Property("RequestType"); + + b.Property("ResovledDate"); + + b.Property("Status"); + + b.Property("Subject"); + + b.Property("Title"); + + b.Property("UserReportedId"); + + b.HasKey("Id"); + + b.HasIndex("IssueCategoryId"); + + b.HasIndex("IssueId"); + + b.HasIndex("UserReportedId"); + + b.ToTable("Issues"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.MovieRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Approved"); + + b.Property("Available"); + + b.Property("Background"); + + b.Property("Denied"); + + b.Property("DeniedReason"); + + b.Property("DigitalReleaseDate"); + + b.Property("ImdbId"); + + b.Property("IssueId"); + + b.Property("MarkedAsApproved"); + + b.Property("MarkedAsAvailable"); + + b.Property("MarkedAsDenied"); + + b.Property("Overview"); + + b.Property("PosterPath"); + + b.Property("QualityOverride"); + + b.Property("ReleaseDate"); + + b.Property("RequestType"); + + b.Property("RequestedDate"); + + b.Property("RequestedUserId"); + + b.Property("RootPathOverride"); + + b.Property("Status"); + + b.Property("TheMovieDbId"); + + b.Property("Title"); + + b.HasKey("Id"); + + b.HasIndex("RequestedUserId"); + + b.ToTable("MovieRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.RequestLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("EpisodeCount"); + + b.Property("RequestDate"); + + b.Property("RequestId"); + + b.Property("RequestType"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("RequestLog"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.TvRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Background"); + + b.Property("ImdbId"); + + b.Property("Overview"); + + b.Property("PosterPath"); + + b.Property("QualityOverride"); + + b.Property("ReleaseDate"); + + b.Property("RootFolder"); + + b.Property("Status"); + + b.Property("Title"); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.ToTable("TvRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.RequestSubscription", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("RequestId"); + + b.Property("RequestType"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("RequestSubscription"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.SickRageCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.ToTable("SickRageCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.SickRageEpisodeCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("EpisodeNumber"); + + b.Property("SeasonNumber"); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.ToTable("SickRageEpisodeCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.SonarrCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.ToTable("SonarrCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.SonarrEpisodeCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("EpisodeNumber"); + + b.Property("HasFile"); + + b.Property("SeasonNumber"); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.ToTable("SonarrEpisodeCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Tokens", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Token"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Tokens"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.UserNotificationPreferences", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Agent"); + + b.Property("Enabled"); + + b.Property("UserId"); + + b.Property("Value"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("UserNotificationPreferences"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.UserQualityProfiles", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("RadarrQualityProfile"); + + b.Property("RadarrRootPath"); + + b.Property("SonarrQualityProfile"); + + b.Property("SonarrQualityProfileAnime"); + + b.Property("SonarrRootPath"); + + b.Property("SonarrRootPathAnime"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("UserQualityProfiles"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Votes", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Date"); + + b.Property("Deleted"); + + b.Property("RequestId"); + + b.Property("RequestType"); + + b.Property("UserId"); + + b.Property("VoteType"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Votes"); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.EpisodeRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AirDate"); + + b.Property("Approved"); + + b.Property("Available"); + + b.Property("EpisodeNumber"); + + b.Property("Requested"); + + b.Property("SeasonId"); + + b.Property("Title"); + + b.Property("Url"); + + b.HasKey("Id"); + + b.HasIndex("SeasonId"); + + b.ToTable("EpisodeRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.SeasonRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChildRequestId"); + + b.Property("SeasonNumber"); + + b.HasKey("Id"); + + b.HasIndex("ChildRequestId"); + + b.ToTable("SeasonRequests"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole") + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole") + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Ombi.Store.Entities.OmbiUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Ombi.Store.Entities.EmbyEpisode", b => + { + b.HasOne("Ombi.Store.Entities.EmbyContent", "Series") + .WithMany("Episodes") + .HasForeignKey("ParentId") + .HasPrincipalKey("EmbyId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.NotificationUserId", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany("NotificationUserIds") + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexEpisode", b => + { + b.HasOne("Ombi.Store.Entities.PlexServerContent", "Series") + .WithMany("Episodes") + .HasForeignKey("GrandparentKey") + .HasPrincipalKey("Key") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexSeasonsContent", b => + { + b.HasOne("Ombi.Store.Entities.PlexServerContent") + .WithMany("Seasons") + .HasForeignKey("PlexServerContentId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.AlbumRequest", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "RequestedUser") + .WithMany() + .HasForeignKey("RequestedUserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.ChildRequests", b => + { + b.HasOne("Ombi.Store.Entities.Requests.TvRequests", "ParentRequest") + .WithMany("ChildRequests") + .HasForeignKey("ParentRequestId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Ombi.Store.Entities.OmbiUser", "RequestedUser") + .WithMany() + .HasForeignKey("RequestedUserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueComments", b => + { + b.HasOne("Ombi.Store.Entities.Requests.Issues", "Issues") + .WithMany("Comments") + .HasForeignKey("IssuesId"); + + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.Issues", b => + { + b.HasOne("Ombi.Store.Entities.Requests.IssueCategory", "IssueCategory") + .WithMany() + .HasForeignKey("IssueCategoryId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Ombi.Store.Entities.Requests.ChildRequests") + .WithMany("Issues") + .HasForeignKey("IssueId"); + + b.HasOne("Ombi.Store.Entities.Requests.MovieRequests") + .WithMany("Issues") + .HasForeignKey("IssueId"); + + b.HasOne("Ombi.Store.Entities.OmbiUser", "UserReported") + .WithMany() + .HasForeignKey("UserReportedId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.MovieRequests", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "RequestedUser") + .WithMany() + .HasForeignKey("RequestedUserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.RequestLog", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.RequestSubscription", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Tokens", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.UserNotificationPreferences", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany("UserNotificationPreferences") + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.UserQualityProfiles", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Votes", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.EpisodeRequests", b => + { + b.HasOne("Ombi.Store.Repository.Requests.SeasonRequests", "Season") + .WithMany("Episodes") + .HasForeignKey("SeasonId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.SeasonRequests", b => + { + b.HasOne("Ombi.Store.Entities.Requests.ChildRequests", "ChildRequest") + .WithMany("SeasonRequests") + .HasForeignKey("ChildRequestId") + .OnDelete(DeleteBehavior.Cascade); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Ombi.Store/Migrations/20180928201334_Votes.cs b/src/Ombi.Store/Migrations/20180928201334_Votes.cs new file mode 100644 index 000000000..1c64a19aa --- /dev/null +++ b/src/Ombi.Store/Migrations/20180928201334_Votes.cs @@ -0,0 +1,46 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Ombi.Store.Migrations +{ + public partial class Votes : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "Votes", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + RequestId = table.Column(nullable: false), + VoteType = table.Column(nullable: false), + RequestType = table.Column(nullable: false), + UserId = table.Column(nullable: true), + Date = table.Column(nullable: false), + Deleted = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Votes", x => x.Id); + table.ForeignKey( + name: "FK_Votes_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateIndex( + name: "IX_Votes_UserId", + table: "Votes", + column: "UserId"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "Votes"); + } + } +} diff --git a/src/Ombi.Store/Migrations/20181204084915_RequestQueue.Designer.cs b/src/Ombi.Store/Migrations/20181204084915_RequestQueue.Designer.cs new file mode 100644 index 000000000..468abb2c7 --- /dev/null +++ b/src/Ombi.Store/Migrations/20181204084915_RequestQueue.Designer.cs @@ -0,0 +1,1204 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Ombi.Store.Context; + +namespace Ombi.Store.Migrations +{ + [DbContext(typeof(OmbiContext))] + [Migration("20181204084915_RequestQueue")] + partial class RequestQueue + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.1.4-rtm-31024"); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken(); + + b.Property("Name") + .HasMaxLength(256); + + b.Property("NormalizedName") + .HasMaxLength(256); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasName("RoleNameIndex"); + + b.ToTable("AspNetRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ClaimType"); + + b.Property("ClaimValue"); + + b.Property("RoleId") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ClaimType"); + + b.Property("ClaimValue"); + + b.Property("UserId") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider"); + + b.Property("ProviderKey"); + + b.Property("ProviderDisplayName"); + + b.Property("UserId") + .IsRequired(); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId"); + + b.Property("RoleId"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId"); + + b.Property("LoginProvider"); + + b.Property("Name"); + + b.Property("Value"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.ApplicationConfiguration", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Type"); + + b.Property("Value"); + + b.HasKey("Id"); + + b.ToTable("ApplicationConfiguration"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Audit", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AuditArea"); + + b.Property("AuditType"); + + b.Property("DateTime"); + + b.Property("Description"); + + b.Property("User"); + + b.HasKey("Id"); + + b.ToTable("Audit"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.CouchPotatoCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("TheMovieDbId"); + + b.HasKey("Id"); + + b.ToTable("CouchPotatoCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.EmbyContent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("EmbyId") + .IsRequired(); + + b.Property("ImdbId"); + + b.Property("ProviderId"); + + b.Property("TheMovieDbId"); + + b.Property("Title"); + + b.Property("TvDbId"); + + b.Property("Type"); + + b.Property("Url"); + + b.HasKey("Id"); + + b.ToTable("EmbyContent"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.EmbyEpisode", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("EmbyId"); + + b.Property("EpisodeNumber"); + + b.Property("ImdbId"); + + b.Property("ParentId"); + + b.Property("ProviderId"); + + b.Property("SeasonNumber"); + + b.Property("TheMovieDbId"); + + b.Property("Title"); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.HasIndex("ParentId"); + + b.ToTable("EmbyEpisode"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.GlobalSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Content"); + + b.Property("SettingsName"); + + b.HasKey("Id"); + + b.ToTable("GlobalSettings"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.LidarrAlbumCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("ArtistId"); + + b.Property("ForeignAlbumId"); + + b.Property("Monitored"); + + b.Property("PercentOfTracks"); + + b.Property("ReleaseDate"); + + b.Property("Title"); + + b.Property("TrackCount"); + + b.HasKey("Id"); + + b.ToTable("LidarrAlbumCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.LidarrArtistCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ArtistId"); + + b.Property("ArtistName"); + + b.Property("ForeignArtistId"); + + b.Property("Monitored"); + + b.HasKey("Id"); + + b.ToTable("LidarrArtistCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.NotificationTemplates", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Agent"); + + b.Property("Enabled"); + + b.Property("Message"); + + b.Property("NotificationType"); + + b.Property("Subject"); + + b.HasKey("Id"); + + b.ToTable("NotificationTemplates"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.NotificationUserId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("PlayerId"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("NotificationUserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.OmbiUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AccessFailedCount"); + + b.Property("Alias"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken(); + + b.Property("Email") + .HasMaxLength(256); + + b.Property("EmailConfirmed"); + + b.Property("EmbyConnectUserId"); + + b.Property("EpisodeRequestLimit"); + + b.Property("LastLoggedIn"); + + b.Property("LockoutEnabled"); + + b.Property("LockoutEnd"); + + b.Property("MovieRequestLimit"); + + b.Property("MusicRequestLimit"); + + b.Property("NormalizedEmail") + .HasMaxLength(256); + + b.Property("NormalizedUserName") + .HasMaxLength(256); + + b.Property("PasswordHash"); + + b.Property("PhoneNumber"); + + b.Property("PhoneNumberConfirmed"); + + b.Property("ProviderUserId"); + + b.Property("SecurityStamp"); + + b.Property("TwoFactorEnabled"); + + b.Property("UserAccessToken"); + + b.Property("UserName") + .HasMaxLength(256); + + b.Property("UserType"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasName("UserNameIndex"); + + b.ToTable("AspNetUsers"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexEpisode", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("EpisodeNumber"); + + b.Property("GrandparentKey"); + + b.Property("Key"); + + b.Property("ParentKey"); + + b.Property("SeasonNumber"); + + b.Property("Title"); + + b.HasKey("Id"); + + b.HasIndex("GrandparentKey"); + + b.ToTable("PlexEpisode"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexSeasonsContent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ParentKey"); + + b.Property("PlexContentId"); + + b.Property("PlexServerContentId"); + + b.Property("SeasonKey"); + + b.Property("SeasonNumber"); + + b.HasKey("Id"); + + b.HasIndex("PlexServerContentId"); + + b.ToTable("PlexSeasonsContent"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexServerContent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("ImdbId"); + + b.Property("Key"); + + b.Property("Quality"); + + b.Property("ReleaseYear"); + + b.Property("TheMovieDbId"); + + b.Property("Title"); + + b.Property("TvDbId"); + + b.Property("Type"); + + b.Property("Url"); + + b.HasKey("Id"); + + b.ToTable("PlexServerContent"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.RadarrCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("HasFile"); + + b.Property("TheMovieDbId"); + + b.HasKey("Id"); + + b.ToTable("RadarrCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.RecentlyAddedLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("AlbumId"); + + b.Property("ContentId"); + + b.Property("ContentType"); + + b.Property("EpisodeNumber"); + + b.Property("SeasonNumber"); + + b.Property("Type"); + + b.HasKey("Id"); + + b.ToTable("RecentlyAddedLog"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.RequestQueue", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Completed"); + + b.Property("Dts"); + + b.Property("Error"); + + b.Property("RequestId"); + + b.Property("RetryCount"); + + b.Property("Type"); + + b.HasKey("Id"); + + b.ToTable("RequestQueue"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.AlbumRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Approved"); + + b.Property("ArtistName"); + + b.Property("Available"); + + b.Property("Cover"); + + b.Property("Denied"); + + b.Property("DeniedReason"); + + b.Property("Disk"); + + b.Property("ForeignAlbumId"); + + b.Property("ForeignArtistId"); + + b.Property("MarkedAsApproved"); + + b.Property("MarkedAsAvailable"); + + b.Property("MarkedAsDenied"); + + b.Property("Rating"); + + b.Property("ReleaseDate"); + + b.Property("RequestType"); + + b.Property("RequestedDate"); + + b.Property("RequestedUserId"); + + b.Property("Title"); + + b.HasKey("Id"); + + b.HasIndex("RequestedUserId"); + + b.ToTable("AlbumRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.ChildRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Approved"); + + b.Property("Available"); + + b.Property("Denied"); + + b.Property("DeniedReason"); + + b.Property("IssueId"); + + b.Property("MarkedAsApproved"); + + b.Property("MarkedAsAvailable"); + + b.Property("MarkedAsDenied"); + + b.Property("ParentRequestId"); + + b.Property("RequestType"); + + b.Property("RequestedDate"); + + b.Property("RequestedUserId"); + + b.Property("SeriesType"); + + b.Property("Title"); + + b.HasKey("Id"); + + b.HasIndex("ParentRequestId"); + + b.HasIndex("RequestedUserId"); + + b.ToTable("ChildRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Value"); + + b.HasKey("Id"); + + b.ToTable("IssueCategory"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueComments", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Comment"); + + b.Property("Date"); + + b.Property("IssuesId"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("IssuesId"); + + b.HasIndex("UserId"); + + b.ToTable("IssueComments"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.Issues", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Description"); + + b.Property("IssueCategoryId"); + + b.Property("IssueId"); + + b.Property("ProviderId"); + + b.Property("RequestId"); + + b.Property("RequestType"); + + b.Property("ResovledDate"); + + b.Property("Status"); + + b.Property("Subject"); + + b.Property("Title"); + + b.Property("UserReportedId"); + + b.HasKey("Id"); + + b.HasIndex("IssueCategoryId"); + + b.HasIndex("IssueId"); + + b.HasIndex("UserReportedId"); + + b.ToTable("Issues"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.MovieRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Approved"); + + b.Property("Available"); + + b.Property("Background"); + + b.Property("Denied"); + + b.Property("DeniedReason"); + + b.Property("DigitalReleaseDate"); + + b.Property("ImdbId"); + + b.Property("IssueId"); + + b.Property("MarkedAsApproved"); + + b.Property("MarkedAsAvailable"); + + b.Property("MarkedAsDenied"); + + b.Property("Overview"); + + b.Property("PosterPath"); + + b.Property("QualityOverride"); + + b.Property("ReleaseDate"); + + b.Property("RequestType"); + + b.Property("RequestedDate"); + + b.Property("RequestedUserId"); + + b.Property("RootPathOverride"); + + b.Property("Status"); + + b.Property("TheMovieDbId"); + + b.Property("Title"); + + b.HasKey("Id"); + + b.HasIndex("RequestedUserId"); + + b.ToTable("MovieRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.RequestLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("EpisodeCount"); + + b.Property("RequestDate"); + + b.Property("RequestId"); + + b.Property("RequestType"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("RequestLog"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.TvRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Background"); + + b.Property("ImdbId"); + + b.Property("Overview"); + + b.Property("PosterPath"); + + b.Property("QualityOverride"); + + b.Property("ReleaseDate"); + + b.Property("RootFolder"); + + b.Property("Status"); + + b.Property("Title"); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.ToTable("TvRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.RequestSubscription", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("RequestId"); + + b.Property("RequestType"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("RequestSubscription"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.SickRageCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.ToTable("SickRageCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.SickRageEpisodeCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("EpisodeNumber"); + + b.Property("SeasonNumber"); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.ToTable("SickRageEpisodeCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.SonarrCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.ToTable("SonarrCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.SonarrEpisodeCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("EpisodeNumber"); + + b.Property("HasFile"); + + b.Property("SeasonNumber"); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.ToTable("SonarrEpisodeCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Tokens", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Token"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Tokens"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.UserNotificationPreferences", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Agent"); + + b.Property("Enabled"); + + b.Property("UserId"); + + b.Property("Value"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("UserNotificationPreferences"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.UserQualityProfiles", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("RadarrQualityProfile"); + + b.Property("RadarrRootPath"); + + b.Property("SonarrQualityProfile"); + + b.Property("SonarrQualityProfileAnime"); + + b.Property("SonarrRootPath"); + + b.Property("SonarrRootPathAnime"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("UserQualityProfiles"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Votes", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Date"); + + b.Property("Deleted"); + + b.Property("RequestId"); + + b.Property("RequestType"); + + b.Property("UserId"); + + b.Property("VoteType"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Votes"); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.EpisodeRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AirDate"); + + b.Property("Approved"); + + b.Property("Available"); + + b.Property("EpisodeNumber"); + + b.Property("Requested"); + + b.Property("SeasonId"); + + b.Property("Title"); + + b.Property("Url"); + + b.HasKey("Id"); + + b.HasIndex("SeasonId"); + + b.ToTable("EpisodeRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.SeasonRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChildRequestId"); + + b.Property("SeasonNumber"); + + b.HasKey("Id"); + + b.HasIndex("ChildRequestId"); + + b.ToTable("SeasonRequests"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole") + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole") + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Ombi.Store.Entities.OmbiUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Ombi.Store.Entities.EmbyEpisode", b => + { + b.HasOne("Ombi.Store.Entities.EmbyContent", "Series") + .WithMany("Episodes") + .HasForeignKey("ParentId") + .HasPrincipalKey("EmbyId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.NotificationUserId", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany("NotificationUserIds") + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexEpisode", b => + { + b.HasOne("Ombi.Store.Entities.PlexServerContent", "Series") + .WithMany("Episodes") + .HasForeignKey("GrandparentKey") + .HasPrincipalKey("Key") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexSeasonsContent", b => + { + b.HasOne("Ombi.Store.Entities.PlexServerContent") + .WithMany("Seasons") + .HasForeignKey("PlexServerContentId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.AlbumRequest", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "RequestedUser") + .WithMany() + .HasForeignKey("RequestedUserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.ChildRequests", b => + { + b.HasOne("Ombi.Store.Entities.Requests.TvRequests", "ParentRequest") + .WithMany("ChildRequests") + .HasForeignKey("ParentRequestId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Ombi.Store.Entities.OmbiUser", "RequestedUser") + .WithMany() + .HasForeignKey("RequestedUserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueComments", b => + { + b.HasOne("Ombi.Store.Entities.Requests.Issues", "Issues") + .WithMany("Comments") + .HasForeignKey("IssuesId"); + + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.Issues", b => + { + b.HasOne("Ombi.Store.Entities.Requests.IssueCategory", "IssueCategory") + .WithMany() + .HasForeignKey("IssueCategoryId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Ombi.Store.Entities.Requests.ChildRequests") + .WithMany("Issues") + .HasForeignKey("IssueId"); + + b.HasOne("Ombi.Store.Entities.Requests.MovieRequests") + .WithMany("Issues") + .HasForeignKey("IssueId"); + + b.HasOne("Ombi.Store.Entities.OmbiUser", "UserReported") + .WithMany() + .HasForeignKey("UserReportedId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.MovieRequests", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "RequestedUser") + .WithMany() + .HasForeignKey("RequestedUserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.RequestLog", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.RequestSubscription", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Tokens", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.UserNotificationPreferences", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany("UserNotificationPreferences") + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.UserQualityProfiles", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Votes", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.EpisodeRequests", b => + { + b.HasOne("Ombi.Store.Repository.Requests.SeasonRequests", "Season") + .WithMany("Episodes") + .HasForeignKey("SeasonId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.SeasonRequests", b => + { + b.HasOne("Ombi.Store.Entities.Requests.ChildRequests", "ChildRequest") + .WithMany("SeasonRequests") + .HasForeignKey("ChildRequestId") + .OnDelete(DeleteBehavior.Cascade); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Ombi.Store/Migrations/20181204084915_RequestQueue.cs b/src/Ombi.Store/Migrations/20181204084915_RequestQueue.cs new file mode 100644 index 000000000..820ce50af --- /dev/null +++ b/src/Ombi.Store/Migrations/20181204084915_RequestQueue.cs @@ -0,0 +1,35 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Ombi.Store.Migrations +{ + public partial class RequestQueue : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "RequestQueue", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + RequestId = table.Column(nullable: false), + Type = table.Column(nullable: false), + Dts = table.Column(nullable: false), + Error = table.Column(nullable: true), + Completed = table.Column(nullable: true), + RetryCount = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_RequestQueue", x => x.Id); + }); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "RequestQueue"); + } + } +} diff --git a/src/Ombi.Store/Migrations/20190104203305_LanguageCode.Designer.cs b/src/Ombi.Store/Migrations/20190104203305_LanguageCode.Designer.cs new file mode 100644 index 000000000..301622b41 --- /dev/null +++ b/src/Ombi.Store/Migrations/20190104203305_LanguageCode.Designer.cs @@ -0,0 +1,1206 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Ombi.Store.Context; + +namespace Ombi.Store.Migrations +{ + [DbContext(typeof(OmbiContext))] + [Migration("20190104203305_LanguageCode")] + partial class LanguageCode + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.2.0-rtm-35687"); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken(); + + b.Property("Name") + .HasMaxLength(256); + + b.Property("NormalizedName") + .HasMaxLength(256); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasName("RoleNameIndex"); + + b.ToTable("AspNetRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ClaimType"); + + b.Property("ClaimValue"); + + b.Property("RoleId") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ClaimType"); + + b.Property("ClaimValue"); + + b.Property("UserId") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider"); + + b.Property("ProviderKey"); + + b.Property("ProviderDisplayName"); + + b.Property("UserId") + .IsRequired(); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId"); + + b.Property("RoleId"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId"); + + b.Property("LoginProvider"); + + b.Property("Name"); + + b.Property("Value"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.ApplicationConfiguration", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Type"); + + b.Property("Value"); + + b.HasKey("Id"); + + b.ToTable("ApplicationConfiguration"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Audit", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AuditArea"); + + b.Property("AuditType"); + + b.Property("DateTime"); + + b.Property("Description"); + + b.Property("User"); + + b.HasKey("Id"); + + b.ToTable("Audit"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.CouchPotatoCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("TheMovieDbId"); + + b.HasKey("Id"); + + b.ToTable("CouchPotatoCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.EmbyContent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("EmbyId") + .IsRequired(); + + b.Property("ImdbId"); + + b.Property("ProviderId"); + + b.Property("TheMovieDbId"); + + b.Property("Title"); + + b.Property("TvDbId"); + + b.Property("Type"); + + b.Property("Url"); + + b.HasKey("Id"); + + b.ToTable("EmbyContent"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.EmbyEpisode", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("EmbyId"); + + b.Property("EpisodeNumber"); + + b.Property("ImdbId"); + + b.Property("ParentId"); + + b.Property("ProviderId"); + + b.Property("SeasonNumber"); + + b.Property("TheMovieDbId"); + + b.Property("Title"); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.HasIndex("ParentId"); + + b.ToTable("EmbyEpisode"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.GlobalSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Content"); + + b.Property("SettingsName"); + + b.HasKey("Id"); + + b.ToTable("GlobalSettings"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.LidarrAlbumCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("ArtistId"); + + b.Property("ForeignAlbumId"); + + b.Property("Monitored"); + + b.Property("PercentOfTracks"); + + b.Property("ReleaseDate"); + + b.Property("Title"); + + b.Property("TrackCount"); + + b.HasKey("Id"); + + b.ToTable("LidarrAlbumCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.LidarrArtistCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ArtistId"); + + b.Property("ArtistName"); + + b.Property("ForeignArtistId"); + + b.Property("Monitored"); + + b.HasKey("Id"); + + b.ToTable("LidarrArtistCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.NotificationTemplates", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Agent"); + + b.Property("Enabled"); + + b.Property("Message"); + + b.Property("NotificationType"); + + b.Property("Subject"); + + b.HasKey("Id"); + + b.ToTable("NotificationTemplates"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.NotificationUserId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("PlayerId"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("NotificationUserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.OmbiUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AccessFailedCount"); + + b.Property("Alias"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken(); + + b.Property("Email") + .HasMaxLength(256); + + b.Property("EmailConfirmed"); + + b.Property("EmbyConnectUserId"); + + b.Property("EpisodeRequestLimit"); + + b.Property("LastLoggedIn"); + + b.Property("LockoutEnabled"); + + b.Property("LockoutEnd"); + + b.Property("MovieRequestLimit"); + + b.Property("MusicRequestLimit"); + + b.Property("NormalizedEmail") + .HasMaxLength(256); + + b.Property("NormalizedUserName") + .HasMaxLength(256); + + b.Property("PasswordHash"); + + b.Property("PhoneNumber"); + + b.Property("PhoneNumberConfirmed"); + + b.Property("ProviderUserId"); + + b.Property("SecurityStamp"); + + b.Property("TwoFactorEnabled"); + + b.Property("UserAccessToken"); + + b.Property("UserName") + .HasMaxLength(256); + + b.Property("UserType"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasName("UserNameIndex"); + + b.ToTable("AspNetUsers"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexEpisode", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("EpisodeNumber"); + + b.Property("GrandparentKey"); + + b.Property("Key"); + + b.Property("ParentKey"); + + b.Property("SeasonNumber"); + + b.Property("Title"); + + b.HasKey("Id"); + + b.HasIndex("GrandparentKey"); + + b.ToTable("PlexEpisode"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexSeasonsContent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ParentKey"); + + b.Property("PlexContentId"); + + b.Property("PlexServerContentId"); + + b.Property("SeasonKey"); + + b.Property("SeasonNumber"); + + b.HasKey("Id"); + + b.HasIndex("PlexServerContentId"); + + b.ToTable("PlexSeasonsContent"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexServerContent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("ImdbId"); + + b.Property("Key"); + + b.Property("Quality"); + + b.Property("ReleaseYear"); + + b.Property("TheMovieDbId"); + + b.Property("Title"); + + b.Property("TvDbId"); + + b.Property("Type"); + + b.Property("Url"); + + b.HasKey("Id"); + + b.ToTable("PlexServerContent"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.RadarrCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("HasFile"); + + b.Property("TheMovieDbId"); + + b.HasKey("Id"); + + b.ToTable("RadarrCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.RecentlyAddedLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("AlbumId"); + + b.Property("ContentId"); + + b.Property("ContentType"); + + b.Property("EpisodeNumber"); + + b.Property("SeasonNumber"); + + b.Property("Type"); + + b.HasKey("Id"); + + b.ToTable("RecentlyAddedLog"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.RequestQueue", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Completed"); + + b.Property("Dts"); + + b.Property("Error"); + + b.Property("RequestId"); + + b.Property("RetryCount"); + + b.Property("Type"); + + b.HasKey("Id"); + + b.ToTable("RequestQueue"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.RequestSubscription", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("RequestId"); + + b.Property("RequestType"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("RequestSubscription"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.AlbumRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Approved"); + + b.Property("ArtistName"); + + b.Property("Available"); + + b.Property("Cover"); + + b.Property("Denied"); + + b.Property("DeniedReason"); + + b.Property("Disk"); + + b.Property("ForeignAlbumId"); + + b.Property("ForeignArtistId"); + + b.Property("MarkedAsApproved"); + + b.Property("MarkedAsAvailable"); + + b.Property("MarkedAsDenied"); + + b.Property("Rating"); + + b.Property("ReleaseDate"); + + b.Property("RequestType"); + + b.Property("RequestedDate"); + + b.Property("RequestedUserId"); + + b.Property("Title"); + + b.HasKey("Id"); + + b.HasIndex("RequestedUserId"); + + b.ToTable("AlbumRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.ChildRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Approved"); + + b.Property("Available"); + + b.Property("Denied"); + + b.Property("DeniedReason"); + + b.Property("IssueId"); + + b.Property("MarkedAsApproved"); + + b.Property("MarkedAsAvailable"); + + b.Property("MarkedAsDenied"); + + b.Property("ParentRequestId"); + + b.Property("RequestType"); + + b.Property("RequestedDate"); + + b.Property("RequestedUserId"); + + b.Property("SeriesType"); + + b.Property("Title"); + + b.HasKey("Id"); + + b.HasIndex("ParentRequestId"); + + b.HasIndex("RequestedUserId"); + + b.ToTable("ChildRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Value"); + + b.HasKey("Id"); + + b.ToTable("IssueCategory"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueComments", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Comment"); + + b.Property("Date"); + + b.Property("IssuesId"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("IssuesId"); + + b.HasIndex("UserId"); + + b.ToTable("IssueComments"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.Issues", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Description"); + + b.Property("IssueCategoryId"); + + b.Property("IssueId"); + + b.Property("ProviderId"); + + b.Property("RequestId"); + + b.Property("RequestType"); + + b.Property("ResovledDate"); + + b.Property("Status"); + + b.Property("Subject"); + + b.Property("Title"); + + b.Property("UserReportedId"); + + b.HasKey("Id"); + + b.HasIndex("IssueCategoryId"); + + b.HasIndex("IssueId"); + + b.HasIndex("UserReportedId"); + + b.ToTable("Issues"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.MovieRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Approved"); + + b.Property("Available"); + + b.Property("Background"); + + b.Property("Denied"); + + b.Property("DeniedReason"); + + b.Property("DigitalReleaseDate"); + + b.Property("ImdbId"); + + b.Property("IssueId"); + + b.Property("LangCode"); + + b.Property("MarkedAsApproved"); + + b.Property("MarkedAsAvailable"); + + b.Property("MarkedAsDenied"); + + b.Property("Overview"); + + b.Property("PosterPath"); + + b.Property("QualityOverride"); + + b.Property("ReleaseDate"); + + b.Property("RequestType"); + + b.Property("RequestedDate"); + + b.Property("RequestedUserId"); + + b.Property("RootPathOverride"); + + b.Property("Status"); + + b.Property("TheMovieDbId"); + + b.Property("Title"); + + b.HasKey("Id"); + + b.HasIndex("RequestedUserId"); + + b.ToTable("MovieRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.RequestLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("EpisodeCount"); + + b.Property("RequestDate"); + + b.Property("RequestId"); + + b.Property("RequestType"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("RequestLog"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.TvRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Background"); + + b.Property("ImdbId"); + + b.Property("Overview"); + + b.Property("PosterPath"); + + b.Property("QualityOverride"); + + b.Property("ReleaseDate"); + + b.Property("RootFolder"); + + b.Property("Status"); + + b.Property("Title"); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.ToTable("TvRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.SickRageCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.ToTable("SickRageCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.SickRageEpisodeCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("EpisodeNumber"); + + b.Property("SeasonNumber"); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.ToTable("SickRageEpisodeCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.SonarrCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.ToTable("SonarrCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.SonarrEpisodeCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("EpisodeNumber"); + + b.Property("HasFile"); + + b.Property("SeasonNumber"); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.ToTable("SonarrEpisodeCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Tokens", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Token"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Tokens"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.UserNotificationPreferences", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Agent"); + + b.Property("Enabled"); + + b.Property("UserId"); + + b.Property("Value"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("UserNotificationPreferences"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.UserQualityProfiles", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("RadarrQualityProfile"); + + b.Property("RadarrRootPath"); + + b.Property("SonarrQualityProfile"); + + b.Property("SonarrQualityProfileAnime"); + + b.Property("SonarrRootPath"); + + b.Property("SonarrRootPathAnime"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("UserQualityProfiles"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Votes", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Date"); + + b.Property("Deleted"); + + b.Property("RequestId"); + + b.Property("RequestType"); + + b.Property("UserId"); + + b.Property("VoteType"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Votes"); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.EpisodeRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AirDate"); + + b.Property("Approved"); + + b.Property("Available"); + + b.Property("EpisodeNumber"); + + b.Property("Requested"); + + b.Property("SeasonId"); + + b.Property("Title"); + + b.Property("Url"); + + b.HasKey("Id"); + + b.HasIndex("SeasonId"); + + b.ToTable("EpisodeRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.SeasonRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChildRequestId"); + + b.Property("SeasonNumber"); + + b.HasKey("Id"); + + b.HasIndex("ChildRequestId"); + + b.ToTable("SeasonRequests"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole") + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole") + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Ombi.Store.Entities.OmbiUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Ombi.Store.Entities.EmbyEpisode", b => + { + b.HasOne("Ombi.Store.Entities.EmbyContent", "Series") + .WithMany("Episodes") + .HasForeignKey("ParentId") + .HasPrincipalKey("EmbyId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.NotificationUserId", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany("NotificationUserIds") + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexEpisode", b => + { + b.HasOne("Ombi.Store.Entities.PlexServerContent", "Series") + .WithMany("Episodes") + .HasForeignKey("GrandparentKey") + .HasPrincipalKey("Key") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexSeasonsContent", b => + { + b.HasOne("Ombi.Store.Entities.PlexServerContent") + .WithMany("Seasons") + .HasForeignKey("PlexServerContentId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.RequestSubscription", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.AlbumRequest", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "RequestedUser") + .WithMany() + .HasForeignKey("RequestedUserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.ChildRequests", b => + { + b.HasOne("Ombi.Store.Entities.Requests.TvRequests", "ParentRequest") + .WithMany("ChildRequests") + .HasForeignKey("ParentRequestId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Ombi.Store.Entities.OmbiUser", "RequestedUser") + .WithMany() + .HasForeignKey("RequestedUserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueComments", b => + { + b.HasOne("Ombi.Store.Entities.Requests.Issues", "Issues") + .WithMany("Comments") + .HasForeignKey("IssuesId"); + + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.Issues", b => + { + b.HasOne("Ombi.Store.Entities.Requests.IssueCategory", "IssueCategory") + .WithMany() + .HasForeignKey("IssueCategoryId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Ombi.Store.Entities.Requests.ChildRequests") + .WithMany("Issues") + .HasForeignKey("IssueId"); + + b.HasOne("Ombi.Store.Entities.Requests.MovieRequests") + .WithMany("Issues") + .HasForeignKey("IssueId"); + + b.HasOne("Ombi.Store.Entities.OmbiUser", "UserReported") + .WithMany() + .HasForeignKey("UserReportedId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.MovieRequests", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "RequestedUser") + .WithMany() + .HasForeignKey("RequestedUserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.RequestLog", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Tokens", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.UserNotificationPreferences", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany("UserNotificationPreferences") + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.UserQualityProfiles", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Votes", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.EpisodeRequests", b => + { + b.HasOne("Ombi.Store.Repository.Requests.SeasonRequests", "Season") + .WithMany("Episodes") + .HasForeignKey("SeasonId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.SeasonRequests", b => + { + b.HasOne("Ombi.Store.Entities.Requests.ChildRequests", "ChildRequest") + .WithMany("SeasonRequests") + .HasForeignKey("ChildRequestId") + .OnDelete(DeleteBehavior.Cascade); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Ombi.Store/Migrations/20190104203305_LanguageCode.cs b/src/Ombi.Store/Migrations/20190104203305_LanguageCode.cs new file mode 100644 index 000000000..7372dc0d6 --- /dev/null +++ b/src/Ombi.Store/Migrations/20190104203305_LanguageCode.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Ombi.Store.Migrations +{ + public partial class LanguageCode : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "LangCode", + table: "MovieRequests", + nullable: true); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "LangCode", + table: "MovieRequests"); + } + } +} diff --git a/src/Ombi.Store/Migrations/20190116212601_RequestedByAlias.Designer.cs b/src/Ombi.Store/Migrations/20190116212601_RequestedByAlias.Designer.cs new file mode 100644 index 000000000..45197bdfb --- /dev/null +++ b/src/Ombi.Store/Migrations/20190116212601_RequestedByAlias.Designer.cs @@ -0,0 +1,1212 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Ombi.Store.Context; + +namespace Ombi.Store.Migrations +{ + [DbContext(typeof(OmbiContext))] + [Migration("20190116212601_RequestedByAlias")] + partial class RequestedByAlias + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.2.1-servicing-10028"); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken(); + + b.Property("Name") + .HasMaxLength(256); + + b.Property("NormalizedName") + .HasMaxLength(256); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasName("RoleNameIndex"); + + b.ToTable("AspNetRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ClaimType"); + + b.Property("ClaimValue"); + + b.Property("RoleId") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ClaimType"); + + b.Property("ClaimValue"); + + b.Property("UserId") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider"); + + b.Property("ProviderKey"); + + b.Property("ProviderDisplayName"); + + b.Property("UserId") + .IsRequired(); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId"); + + b.Property("RoleId"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId"); + + b.Property("LoginProvider"); + + b.Property("Name"); + + b.Property("Value"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.ApplicationConfiguration", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Type"); + + b.Property("Value"); + + b.HasKey("Id"); + + b.ToTable("ApplicationConfiguration"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Audit", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AuditArea"); + + b.Property("AuditType"); + + b.Property("DateTime"); + + b.Property("Description"); + + b.Property("User"); + + b.HasKey("Id"); + + b.ToTable("Audit"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.CouchPotatoCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("TheMovieDbId"); + + b.HasKey("Id"); + + b.ToTable("CouchPotatoCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.EmbyContent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("EmbyId") + .IsRequired(); + + b.Property("ImdbId"); + + b.Property("ProviderId"); + + b.Property("TheMovieDbId"); + + b.Property("Title"); + + b.Property("TvDbId"); + + b.Property("Type"); + + b.Property("Url"); + + b.HasKey("Id"); + + b.ToTable("EmbyContent"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.EmbyEpisode", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("EmbyId"); + + b.Property("EpisodeNumber"); + + b.Property("ImdbId"); + + b.Property("ParentId"); + + b.Property("ProviderId"); + + b.Property("SeasonNumber"); + + b.Property("TheMovieDbId"); + + b.Property("Title"); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.HasIndex("ParentId"); + + b.ToTable("EmbyEpisode"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.GlobalSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Content"); + + b.Property("SettingsName"); + + b.HasKey("Id"); + + b.ToTable("GlobalSettings"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.LidarrAlbumCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("ArtistId"); + + b.Property("ForeignAlbumId"); + + b.Property("Monitored"); + + b.Property("PercentOfTracks"); + + b.Property("ReleaseDate"); + + b.Property("Title"); + + b.Property("TrackCount"); + + b.HasKey("Id"); + + b.ToTable("LidarrAlbumCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.LidarrArtistCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ArtistId"); + + b.Property("ArtistName"); + + b.Property("ForeignArtistId"); + + b.Property("Monitored"); + + b.HasKey("Id"); + + b.ToTable("LidarrArtistCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.NotificationTemplates", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Agent"); + + b.Property("Enabled"); + + b.Property("Message"); + + b.Property("NotificationType"); + + b.Property("Subject"); + + b.HasKey("Id"); + + b.ToTable("NotificationTemplates"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.NotificationUserId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("PlayerId"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("NotificationUserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.OmbiUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AccessFailedCount"); + + b.Property("Alias"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken(); + + b.Property("Email") + .HasMaxLength(256); + + b.Property("EmailConfirmed"); + + b.Property("EmbyConnectUserId"); + + b.Property("EpisodeRequestLimit"); + + b.Property("LastLoggedIn"); + + b.Property("LockoutEnabled"); + + b.Property("LockoutEnd"); + + b.Property("MovieRequestLimit"); + + b.Property("MusicRequestLimit"); + + b.Property("NormalizedEmail") + .HasMaxLength(256); + + b.Property("NormalizedUserName") + .HasMaxLength(256); + + b.Property("PasswordHash"); + + b.Property("PhoneNumber"); + + b.Property("PhoneNumberConfirmed"); + + b.Property("ProviderUserId"); + + b.Property("SecurityStamp"); + + b.Property("TwoFactorEnabled"); + + b.Property("UserAccessToken"); + + b.Property("UserName") + .HasMaxLength(256); + + b.Property("UserType"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasName("UserNameIndex"); + + b.ToTable("AspNetUsers"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexEpisode", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("EpisodeNumber"); + + b.Property("GrandparentKey"); + + b.Property("Key"); + + b.Property("ParentKey"); + + b.Property("SeasonNumber"); + + b.Property("Title"); + + b.HasKey("Id"); + + b.HasIndex("GrandparentKey"); + + b.ToTable("PlexEpisode"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexSeasonsContent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ParentKey"); + + b.Property("PlexContentId"); + + b.Property("PlexServerContentId"); + + b.Property("SeasonKey"); + + b.Property("SeasonNumber"); + + b.HasKey("Id"); + + b.HasIndex("PlexServerContentId"); + + b.ToTable("PlexSeasonsContent"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexServerContent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("ImdbId"); + + b.Property("Key"); + + b.Property("Quality"); + + b.Property("ReleaseYear"); + + b.Property("TheMovieDbId"); + + b.Property("Title"); + + b.Property("TvDbId"); + + b.Property("Type"); + + b.Property("Url"); + + b.HasKey("Id"); + + b.ToTable("PlexServerContent"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.RadarrCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("HasFile"); + + b.Property("TheMovieDbId"); + + b.HasKey("Id"); + + b.ToTable("RadarrCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.RecentlyAddedLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("AlbumId"); + + b.Property("ContentId"); + + b.Property("ContentType"); + + b.Property("EpisodeNumber"); + + b.Property("SeasonNumber"); + + b.Property("Type"); + + b.HasKey("Id"); + + b.ToTable("RecentlyAddedLog"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.RequestQueue", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Completed"); + + b.Property("Dts"); + + b.Property("Error"); + + b.Property("RequestId"); + + b.Property("RetryCount"); + + b.Property("Type"); + + b.HasKey("Id"); + + b.ToTable("RequestQueue"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.RequestSubscription", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("RequestId"); + + b.Property("RequestType"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("RequestSubscription"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.AlbumRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Approved"); + + b.Property("ArtistName"); + + b.Property("Available"); + + b.Property("Cover"); + + b.Property("Denied"); + + b.Property("DeniedReason"); + + b.Property("Disk"); + + b.Property("ForeignAlbumId"); + + b.Property("ForeignArtistId"); + + b.Property("MarkedAsApproved"); + + b.Property("MarkedAsAvailable"); + + b.Property("MarkedAsDenied"); + + b.Property("Rating"); + + b.Property("ReleaseDate"); + + b.Property("RequestType"); + + b.Property("RequestedByAlias"); + + b.Property("RequestedDate"); + + b.Property("RequestedUserId"); + + b.Property("Title"); + + b.HasKey("Id"); + + b.HasIndex("RequestedUserId"); + + b.ToTable("AlbumRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.ChildRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Approved"); + + b.Property("Available"); + + b.Property("Denied"); + + b.Property("DeniedReason"); + + b.Property("IssueId"); + + b.Property("MarkedAsApproved"); + + b.Property("MarkedAsAvailable"); + + b.Property("MarkedAsDenied"); + + b.Property("ParentRequestId"); + + b.Property("RequestType"); + + b.Property("RequestedByAlias"); + + b.Property("RequestedDate"); + + b.Property("RequestedUserId"); + + b.Property("SeriesType"); + + b.Property("Title"); + + b.HasKey("Id"); + + b.HasIndex("ParentRequestId"); + + b.HasIndex("RequestedUserId"); + + b.ToTable("ChildRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Value"); + + b.HasKey("Id"); + + b.ToTable("IssueCategory"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueComments", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Comment"); + + b.Property("Date"); + + b.Property("IssuesId"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("IssuesId"); + + b.HasIndex("UserId"); + + b.ToTable("IssueComments"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.Issues", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Description"); + + b.Property("IssueCategoryId"); + + b.Property("IssueId"); + + b.Property("ProviderId"); + + b.Property("RequestId"); + + b.Property("RequestType"); + + b.Property("ResovledDate"); + + b.Property("Status"); + + b.Property("Subject"); + + b.Property("Title"); + + b.Property("UserReportedId"); + + b.HasKey("Id"); + + b.HasIndex("IssueCategoryId"); + + b.HasIndex("IssueId"); + + b.HasIndex("UserReportedId"); + + b.ToTable("Issues"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.MovieRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Approved"); + + b.Property("Available"); + + b.Property("Background"); + + b.Property("Denied"); + + b.Property("DeniedReason"); + + b.Property("DigitalReleaseDate"); + + b.Property("ImdbId"); + + b.Property("IssueId"); + + b.Property("LangCode"); + + b.Property("MarkedAsApproved"); + + b.Property("MarkedAsAvailable"); + + b.Property("MarkedAsDenied"); + + b.Property("Overview"); + + b.Property("PosterPath"); + + b.Property("QualityOverride"); + + b.Property("ReleaseDate"); + + b.Property("RequestType"); + + b.Property("RequestedByAlias"); + + b.Property("RequestedDate"); + + b.Property("RequestedUserId"); + + b.Property("RootPathOverride"); + + b.Property("Status"); + + b.Property("TheMovieDbId"); + + b.Property("Title"); + + b.HasKey("Id"); + + b.HasIndex("RequestedUserId"); + + b.ToTable("MovieRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.RequestLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("EpisodeCount"); + + b.Property("RequestDate"); + + b.Property("RequestId"); + + b.Property("RequestType"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("RequestLog"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.TvRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Background"); + + b.Property("ImdbId"); + + b.Property("Overview"); + + b.Property("PosterPath"); + + b.Property("QualityOverride"); + + b.Property("ReleaseDate"); + + b.Property("RootFolder"); + + b.Property("Status"); + + b.Property("Title"); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.ToTable("TvRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.SickRageCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.ToTable("SickRageCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.SickRageEpisodeCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("EpisodeNumber"); + + b.Property("SeasonNumber"); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.ToTable("SickRageEpisodeCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.SonarrCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.ToTable("SonarrCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.SonarrEpisodeCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("EpisodeNumber"); + + b.Property("HasFile"); + + b.Property("SeasonNumber"); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.ToTable("SonarrEpisodeCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Tokens", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Token"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Tokens"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.UserNotificationPreferences", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Agent"); + + b.Property("Enabled"); + + b.Property("UserId"); + + b.Property("Value"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("UserNotificationPreferences"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.UserQualityProfiles", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("RadarrQualityProfile"); + + b.Property("RadarrRootPath"); + + b.Property("SonarrQualityProfile"); + + b.Property("SonarrQualityProfileAnime"); + + b.Property("SonarrRootPath"); + + b.Property("SonarrRootPathAnime"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("UserQualityProfiles"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Votes", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Date"); + + b.Property("Deleted"); + + b.Property("RequestId"); + + b.Property("RequestType"); + + b.Property("UserId"); + + b.Property("VoteType"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Votes"); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.EpisodeRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AirDate"); + + b.Property("Approved"); + + b.Property("Available"); + + b.Property("EpisodeNumber"); + + b.Property("Requested"); + + b.Property("SeasonId"); + + b.Property("Title"); + + b.Property("Url"); + + b.HasKey("Id"); + + b.HasIndex("SeasonId"); + + b.ToTable("EpisodeRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.SeasonRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChildRequestId"); + + b.Property("SeasonNumber"); + + b.HasKey("Id"); + + b.HasIndex("ChildRequestId"); + + b.ToTable("SeasonRequests"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole") + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole") + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Ombi.Store.Entities.OmbiUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Ombi.Store.Entities.EmbyEpisode", b => + { + b.HasOne("Ombi.Store.Entities.EmbyContent", "Series") + .WithMany("Episodes") + .HasForeignKey("ParentId") + .HasPrincipalKey("EmbyId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.NotificationUserId", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany("NotificationUserIds") + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexEpisode", b => + { + b.HasOne("Ombi.Store.Entities.PlexServerContent", "Series") + .WithMany("Episodes") + .HasForeignKey("GrandparentKey") + .HasPrincipalKey("Key") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexSeasonsContent", b => + { + b.HasOne("Ombi.Store.Entities.PlexServerContent") + .WithMany("Seasons") + .HasForeignKey("PlexServerContentId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.RequestSubscription", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.AlbumRequest", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "RequestedUser") + .WithMany() + .HasForeignKey("RequestedUserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.ChildRequests", b => + { + b.HasOne("Ombi.Store.Entities.Requests.TvRequests", "ParentRequest") + .WithMany("ChildRequests") + .HasForeignKey("ParentRequestId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Ombi.Store.Entities.OmbiUser", "RequestedUser") + .WithMany() + .HasForeignKey("RequestedUserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueComments", b => + { + b.HasOne("Ombi.Store.Entities.Requests.Issues", "Issues") + .WithMany("Comments") + .HasForeignKey("IssuesId"); + + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.Issues", b => + { + b.HasOne("Ombi.Store.Entities.Requests.IssueCategory", "IssueCategory") + .WithMany() + .HasForeignKey("IssueCategoryId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Ombi.Store.Entities.Requests.ChildRequests") + .WithMany("Issues") + .HasForeignKey("IssueId"); + + b.HasOne("Ombi.Store.Entities.Requests.MovieRequests") + .WithMany("Issues") + .HasForeignKey("IssueId"); + + b.HasOne("Ombi.Store.Entities.OmbiUser", "UserReported") + .WithMany() + .HasForeignKey("UserReportedId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.MovieRequests", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "RequestedUser") + .WithMany() + .HasForeignKey("RequestedUserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.RequestLog", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Tokens", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.UserNotificationPreferences", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany("UserNotificationPreferences") + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.UserQualityProfiles", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Votes", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.EpisodeRequests", b => + { + b.HasOne("Ombi.Store.Repository.Requests.SeasonRequests", "Season") + .WithMany("Episodes") + .HasForeignKey("SeasonId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.SeasonRequests", b => + { + b.HasOne("Ombi.Store.Entities.Requests.ChildRequests", "ChildRequest") + .WithMany("SeasonRequests") + .HasForeignKey("ChildRequestId") + .OnDelete(DeleteBehavior.Cascade); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Ombi.Store/Migrations/20190116212601_RequestedByAlias.cs b/src/Ombi.Store/Migrations/20190116212601_RequestedByAlias.cs new file mode 100644 index 000000000..1e55c2f6e --- /dev/null +++ b/src/Ombi.Store/Migrations/20190116212601_RequestedByAlias.cs @@ -0,0 +1,40 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Ombi.Store.Migrations +{ + public partial class RequestedByAlias : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "RequestedByAlias", + table: "MovieRequests", + nullable: true); + + migrationBuilder.AddColumn( + name: "RequestedByAlias", + table: "ChildRequests", + nullable: true); + + migrationBuilder.AddColumn( + name: "RequestedByAlias", + table: "AlbumRequests", + nullable: true); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "RequestedByAlias", + table: "MovieRequests"); + + migrationBuilder.DropColumn( + name: "RequestedByAlias", + table: "ChildRequests"); + + migrationBuilder.DropColumn( + name: "RequestedByAlias", + table: "AlbumRequests"); + } + } +} diff --git a/src/Ombi.Store/Migrations/20190216224539_Roles.Designer.cs b/src/Ombi.Store/Migrations/20190216224539_Roles.Designer.cs new file mode 100644 index 000000000..f0aeb88bc --- /dev/null +++ b/src/Ombi.Store/Migrations/20190216224539_Roles.Designer.cs @@ -0,0 +1,1212 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Ombi.Store.Context; + +namespace Ombi.Store.Migrations +{ + [DbContext(typeof(OmbiContext))] + [Migration("20190216224539_Roles")] + partial class Roles + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.2.1-servicing-10028"); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken(); + + b.Property("Name") + .HasMaxLength(256); + + b.Property("NormalizedName") + .HasMaxLength(256); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasName("RoleNameIndex"); + + b.ToTable("AspNetRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ClaimType"); + + b.Property("ClaimValue"); + + b.Property("RoleId") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ClaimType"); + + b.Property("ClaimValue"); + + b.Property("UserId") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider"); + + b.Property("ProviderKey"); + + b.Property("ProviderDisplayName"); + + b.Property("UserId") + .IsRequired(); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId"); + + b.Property("RoleId"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId"); + + b.Property("LoginProvider"); + + b.Property("Name"); + + b.Property("Value"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.ApplicationConfiguration", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Type"); + + b.Property("Value"); + + b.HasKey("Id"); + + b.ToTable("ApplicationConfiguration"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Audit", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AuditArea"); + + b.Property("AuditType"); + + b.Property("DateTime"); + + b.Property("Description"); + + b.Property("User"); + + b.HasKey("Id"); + + b.ToTable("Audit"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.CouchPotatoCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("TheMovieDbId"); + + b.HasKey("Id"); + + b.ToTable("CouchPotatoCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.EmbyContent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("EmbyId") + .IsRequired(); + + b.Property("ImdbId"); + + b.Property("ProviderId"); + + b.Property("TheMovieDbId"); + + b.Property("Title"); + + b.Property("TvDbId"); + + b.Property("Type"); + + b.Property("Url"); + + b.HasKey("Id"); + + b.ToTable("EmbyContent"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.EmbyEpisode", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("EmbyId"); + + b.Property("EpisodeNumber"); + + b.Property("ImdbId"); + + b.Property("ParentId"); + + b.Property("ProviderId"); + + b.Property("SeasonNumber"); + + b.Property("TheMovieDbId"); + + b.Property("Title"); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.HasIndex("ParentId"); + + b.ToTable("EmbyEpisode"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.GlobalSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Content"); + + b.Property("SettingsName"); + + b.HasKey("Id"); + + b.ToTable("GlobalSettings"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.LidarrAlbumCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("ArtistId"); + + b.Property("ForeignAlbumId"); + + b.Property("Monitored"); + + b.Property("PercentOfTracks"); + + b.Property("ReleaseDate"); + + b.Property("Title"); + + b.Property("TrackCount"); + + b.HasKey("Id"); + + b.ToTable("LidarrAlbumCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.LidarrArtistCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ArtistId"); + + b.Property("ArtistName"); + + b.Property("ForeignArtistId"); + + b.Property("Monitored"); + + b.HasKey("Id"); + + b.ToTable("LidarrArtistCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.NotificationTemplates", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Agent"); + + b.Property("Enabled"); + + b.Property("Message"); + + b.Property("NotificationType"); + + b.Property("Subject"); + + b.HasKey("Id"); + + b.ToTable("NotificationTemplates"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.NotificationUserId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("PlayerId"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("NotificationUserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.OmbiUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AccessFailedCount"); + + b.Property("Alias"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken(); + + b.Property("Email") + .HasMaxLength(256); + + b.Property("EmailConfirmed"); + + b.Property("EmbyConnectUserId"); + + b.Property("EpisodeRequestLimit"); + + b.Property("LastLoggedIn"); + + b.Property("LockoutEnabled"); + + b.Property("LockoutEnd"); + + b.Property("MovieRequestLimit"); + + b.Property("MusicRequestLimit"); + + b.Property("NormalizedEmail") + .HasMaxLength(256); + + b.Property("NormalizedUserName") + .HasMaxLength(256); + + b.Property("PasswordHash"); + + b.Property("PhoneNumber"); + + b.Property("PhoneNumberConfirmed"); + + b.Property("ProviderUserId"); + + b.Property("SecurityStamp"); + + b.Property("TwoFactorEnabled"); + + b.Property("UserAccessToken"); + + b.Property("UserName") + .HasMaxLength(256); + + b.Property("UserType"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasName("UserNameIndex"); + + b.ToTable("AspNetUsers"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexEpisode", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("EpisodeNumber"); + + b.Property("GrandparentKey"); + + b.Property("Key"); + + b.Property("ParentKey"); + + b.Property("SeasonNumber"); + + b.Property("Title"); + + b.HasKey("Id"); + + b.HasIndex("GrandparentKey"); + + b.ToTable("PlexEpisode"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexSeasonsContent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ParentKey"); + + b.Property("PlexContentId"); + + b.Property("PlexServerContentId"); + + b.Property("SeasonKey"); + + b.Property("SeasonNumber"); + + b.HasKey("Id"); + + b.HasIndex("PlexServerContentId"); + + b.ToTable("PlexSeasonsContent"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexServerContent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("ImdbId"); + + b.Property("Key"); + + b.Property("Quality"); + + b.Property("ReleaseYear"); + + b.Property("TheMovieDbId"); + + b.Property("Title"); + + b.Property("TvDbId"); + + b.Property("Type"); + + b.Property("Url"); + + b.HasKey("Id"); + + b.ToTable("PlexServerContent"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.RadarrCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("HasFile"); + + b.Property("TheMovieDbId"); + + b.HasKey("Id"); + + b.ToTable("RadarrCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.RecentlyAddedLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("AlbumId"); + + b.Property("ContentId"); + + b.Property("ContentType"); + + b.Property("EpisodeNumber"); + + b.Property("SeasonNumber"); + + b.Property("Type"); + + b.HasKey("Id"); + + b.ToTable("RecentlyAddedLog"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.RequestQueue", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Completed"); + + b.Property("Dts"); + + b.Property("Error"); + + b.Property("RequestId"); + + b.Property("RetryCount"); + + b.Property("Type"); + + b.HasKey("Id"); + + b.ToTable("RequestQueue"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.RequestSubscription", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("RequestId"); + + b.Property("RequestType"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("RequestSubscription"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.AlbumRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Approved"); + + b.Property("ArtistName"); + + b.Property("Available"); + + b.Property("Cover"); + + b.Property("Denied"); + + b.Property("DeniedReason"); + + b.Property("Disk"); + + b.Property("ForeignAlbumId"); + + b.Property("ForeignArtistId"); + + b.Property("MarkedAsApproved"); + + b.Property("MarkedAsAvailable"); + + b.Property("MarkedAsDenied"); + + b.Property("Rating"); + + b.Property("ReleaseDate"); + + b.Property("RequestType"); + + b.Property("RequestedByAlias"); + + b.Property("RequestedDate"); + + b.Property("RequestedUserId"); + + b.Property("Title"); + + b.HasKey("Id"); + + b.HasIndex("RequestedUserId"); + + b.ToTable("AlbumRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.ChildRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Approved"); + + b.Property("Available"); + + b.Property("Denied"); + + b.Property("DeniedReason"); + + b.Property("IssueId"); + + b.Property("MarkedAsApproved"); + + b.Property("MarkedAsAvailable"); + + b.Property("MarkedAsDenied"); + + b.Property("ParentRequestId"); + + b.Property("RequestType"); + + b.Property("RequestedByAlias"); + + b.Property("RequestedDate"); + + b.Property("RequestedUserId"); + + b.Property("SeriesType"); + + b.Property("Title"); + + b.HasKey("Id"); + + b.HasIndex("ParentRequestId"); + + b.HasIndex("RequestedUserId"); + + b.ToTable("ChildRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Value"); + + b.HasKey("Id"); + + b.ToTable("IssueCategory"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueComments", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Comment"); + + b.Property("Date"); + + b.Property("IssuesId"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("IssuesId"); + + b.HasIndex("UserId"); + + b.ToTable("IssueComments"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.Issues", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Description"); + + b.Property("IssueCategoryId"); + + b.Property("IssueId"); + + b.Property("ProviderId"); + + b.Property("RequestId"); + + b.Property("RequestType"); + + b.Property("ResovledDate"); + + b.Property("Status"); + + b.Property("Subject"); + + b.Property("Title"); + + b.Property("UserReportedId"); + + b.HasKey("Id"); + + b.HasIndex("IssueCategoryId"); + + b.HasIndex("IssueId"); + + b.HasIndex("UserReportedId"); + + b.ToTable("Issues"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.MovieRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Approved"); + + b.Property("Available"); + + b.Property("Background"); + + b.Property("Denied"); + + b.Property("DeniedReason"); + + b.Property("DigitalReleaseDate"); + + b.Property("ImdbId"); + + b.Property("IssueId"); + + b.Property("LangCode"); + + b.Property("MarkedAsApproved"); + + b.Property("MarkedAsAvailable"); + + b.Property("MarkedAsDenied"); + + b.Property("Overview"); + + b.Property("PosterPath"); + + b.Property("QualityOverride"); + + b.Property("ReleaseDate"); + + b.Property("RequestType"); + + b.Property("RequestedByAlias"); + + b.Property("RequestedDate"); + + b.Property("RequestedUserId"); + + b.Property("RootPathOverride"); + + b.Property("Status"); + + b.Property("TheMovieDbId"); + + b.Property("Title"); + + b.HasKey("Id"); + + b.HasIndex("RequestedUserId"); + + b.ToTable("MovieRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.RequestLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("EpisodeCount"); + + b.Property("RequestDate"); + + b.Property("RequestId"); + + b.Property("RequestType"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("RequestLog"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.TvRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Background"); + + b.Property("ImdbId"); + + b.Property("Overview"); + + b.Property("PosterPath"); + + b.Property("QualityOverride"); + + b.Property("ReleaseDate"); + + b.Property("RootFolder"); + + b.Property("Status"); + + b.Property("Title"); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.ToTable("TvRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.SickRageCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.ToTable("SickRageCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.SickRageEpisodeCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("EpisodeNumber"); + + b.Property("SeasonNumber"); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.ToTable("SickRageEpisodeCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.SonarrCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.ToTable("SonarrCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.SonarrEpisodeCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("EpisodeNumber"); + + b.Property("HasFile"); + + b.Property("SeasonNumber"); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.ToTable("SonarrEpisodeCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Tokens", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Token"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Tokens"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.UserNotificationPreferences", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Agent"); + + b.Property("Enabled"); + + b.Property("UserId"); + + b.Property("Value"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("UserNotificationPreferences"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.UserQualityProfiles", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("RadarrQualityProfile"); + + b.Property("RadarrRootPath"); + + b.Property("SonarrQualityProfile"); + + b.Property("SonarrQualityProfileAnime"); + + b.Property("SonarrRootPath"); + + b.Property("SonarrRootPathAnime"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("UserQualityProfiles"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Votes", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Date"); + + b.Property("Deleted"); + + b.Property("RequestId"); + + b.Property("RequestType"); + + b.Property("UserId"); + + b.Property("VoteType"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Votes"); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.EpisodeRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AirDate"); + + b.Property("Approved"); + + b.Property("Available"); + + b.Property("EpisodeNumber"); + + b.Property("Requested"); + + b.Property("SeasonId"); + + b.Property("Title"); + + b.Property("Url"); + + b.HasKey("Id"); + + b.HasIndex("SeasonId"); + + b.ToTable("EpisodeRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.SeasonRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChildRequestId"); + + b.Property("SeasonNumber"); + + b.HasKey("Id"); + + b.HasIndex("ChildRequestId"); + + b.ToTable("SeasonRequests"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole") + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole") + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Ombi.Store.Entities.OmbiUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Ombi.Store.Entities.EmbyEpisode", b => + { + b.HasOne("Ombi.Store.Entities.EmbyContent", "Series") + .WithMany("Episodes") + .HasForeignKey("ParentId") + .HasPrincipalKey("EmbyId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.NotificationUserId", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany("NotificationUserIds") + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexEpisode", b => + { + b.HasOne("Ombi.Store.Entities.PlexServerContent", "Series") + .WithMany("Episodes") + .HasForeignKey("GrandparentKey") + .HasPrincipalKey("Key") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexSeasonsContent", b => + { + b.HasOne("Ombi.Store.Entities.PlexServerContent") + .WithMany("Seasons") + .HasForeignKey("PlexServerContentId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.RequestSubscription", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.AlbumRequest", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "RequestedUser") + .WithMany() + .HasForeignKey("RequestedUserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.ChildRequests", b => + { + b.HasOne("Ombi.Store.Entities.Requests.TvRequests", "ParentRequest") + .WithMany("ChildRequests") + .HasForeignKey("ParentRequestId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Ombi.Store.Entities.OmbiUser", "RequestedUser") + .WithMany() + .HasForeignKey("RequestedUserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueComments", b => + { + b.HasOne("Ombi.Store.Entities.Requests.Issues", "Issues") + .WithMany("Comments") + .HasForeignKey("IssuesId"); + + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.Issues", b => + { + b.HasOne("Ombi.Store.Entities.Requests.IssueCategory", "IssueCategory") + .WithMany() + .HasForeignKey("IssueCategoryId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Ombi.Store.Entities.Requests.ChildRequests") + .WithMany("Issues") + .HasForeignKey("IssueId"); + + b.HasOne("Ombi.Store.Entities.Requests.MovieRequests") + .WithMany("Issues") + .HasForeignKey("IssueId"); + + b.HasOne("Ombi.Store.Entities.OmbiUser", "UserReported") + .WithMany() + .HasForeignKey("UserReportedId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.MovieRequests", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "RequestedUser") + .WithMany() + .HasForeignKey("RequestedUserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.RequestLog", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Tokens", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.UserNotificationPreferences", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany("UserNotificationPreferences") + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.UserQualityProfiles", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Votes", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.EpisodeRequests", b => + { + b.HasOne("Ombi.Store.Repository.Requests.SeasonRequests", "Season") + .WithMany("Episodes") + .HasForeignKey("SeasonId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.SeasonRequests", b => + { + b.HasOne("Ombi.Store.Entities.Requests.ChildRequests", "ChildRequest") + .WithMany("SeasonRequests") + .HasForeignKey("ChildRequestId") + .OnDelete(DeleteBehavior.Cascade); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Ombi.Store/Migrations/20190216224539_Roles.cs b/src/Ombi.Store/Migrations/20190216224539_Roles.cs new file mode 100644 index 000000000..9c2f91c12 --- /dev/null +++ b/src/Ombi.Store/Migrations/20190216224539_Roles.cs @@ -0,0 +1,32 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; +using Ombi.Helpers; + +namespace Ombi.Store.Migrations +{ + public partial class Roles : Migration + { + protected override void Up(MigrationBuilder mb) + { + // Make sure we have the roles + InsertRole(mb, OmbiRoles.ReceivesNewsletter); + InsertRole(mb, OmbiRoles.RequestMusic); + InsertRole(mb, OmbiRoles.AutoApproveMusic); + InsertRole(mb, OmbiRoles.ManageOwnRequests); + InsertRole(mb, OmbiRoles.EditCustomPage); + } + + private void InsertRole(MigrationBuilder mb, string role) + { + mb.Sql($@" +INSERT INTO AspnetRoles(Id, ConcurrencyStamp, Name, NormalizedName) +SELECT '{Guid.NewGuid().ToString()}','{Guid.NewGuid().ToString()}','{role}', '{role.ToUpper()}' +WHERE NOT EXISTS(SELECT 1 FROM AspnetRoles WHERE Name = '{role}');"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + + } + } +} diff --git a/src/Ombi.Store/Migrations/20190216231519_TvRequestsTotalSeasons.Designer.cs b/src/Ombi.Store/Migrations/20190216231519_TvRequestsTotalSeasons.Designer.cs new file mode 100644 index 000000000..7341ff5fe --- /dev/null +++ b/src/Ombi.Store/Migrations/20190216231519_TvRequestsTotalSeasons.Designer.cs @@ -0,0 +1,1214 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Ombi.Store.Context; + +namespace Ombi.Store.Migrations +{ + [DbContext(typeof(OmbiContext))] + [Migration("20190216231519_TvRequestsTotalSeasons")] + partial class TvRequestsTotalSeasons + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.2.1-servicing-10028"); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken(); + + b.Property("Name") + .HasMaxLength(256); + + b.Property("NormalizedName") + .HasMaxLength(256); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasName("RoleNameIndex"); + + b.ToTable("AspNetRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ClaimType"); + + b.Property("ClaimValue"); + + b.Property("RoleId") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ClaimType"); + + b.Property("ClaimValue"); + + b.Property("UserId") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider"); + + b.Property("ProviderKey"); + + b.Property("ProviderDisplayName"); + + b.Property("UserId") + .IsRequired(); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId"); + + b.Property("RoleId"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId"); + + b.Property("LoginProvider"); + + b.Property("Name"); + + b.Property("Value"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.ApplicationConfiguration", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Type"); + + b.Property("Value"); + + b.HasKey("Id"); + + b.ToTable("ApplicationConfiguration"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Audit", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AuditArea"); + + b.Property("AuditType"); + + b.Property("DateTime"); + + b.Property("Description"); + + b.Property("User"); + + b.HasKey("Id"); + + b.ToTable("Audit"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.CouchPotatoCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("TheMovieDbId"); + + b.HasKey("Id"); + + b.ToTable("CouchPotatoCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.EmbyContent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("EmbyId") + .IsRequired(); + + b.Property("ImdbId"); + + b.Property("ProviderId"); + + b.Property("TheMovieDbId"); + + b.Property("Title"); + + b.Property("TvDbId"); + + b.Property("Type"); + + b.Property("Url"); + + b.HasKey("Id"); + + b.ToTable("EmbyContent"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.EmbyEpisode", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("EmbyId"); + + b.Property("EpisodeNumber"); + + b.Property("ImdbId"); + + b.Property("ParentId"); + + b.Property("ProviderId"); + + b.Property("SeasonNumber"); + + b.Property("TheMovieDbId"); + + b.Property("Title"); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.HasIndex("ParentId"); + + b.ToTable("EmbyEpisode"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.GlobalSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Content"); + + b.Property("SettingsName"); + + b.HasKey("Id"); + + b.ToTable("GlobalSettings"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.LidarrAlbumCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("ArtistId"); + + b.Property("ForeignAlbumId"); + + b.Property("Monitored"); + + b.Property("PercentOfTracks"); + + b.Property("ReleaseDate"); + + b.Property("Title"); + + b.Property("TrackCount"); + + b.HasKey("Id"); + + b.ToTable("LidarrAlbumCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.LidarrArtistCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ArtistId"); + + b.Property("ArtistName"); + + b.Property("ForeignArtistId"); + + b.Property("Monitored"); + + b.HasKey("Id"); + + b.ToTable("LidarrArtistCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.NotificationTemplates", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Agent"); + + b.Property("Enabled"); + + b.Property("Message"); + + b.Property("NotificationType"); + + b.Property("Subject"); + + b.HasKey("Id"); + + b.ToTable("NotificationTemplates"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.NotificationUserId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("PlayerId"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("NotificationUserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.OmbiUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AccessFailedCount"); + + b.Property("Alias"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken(); + + b.Property("Email") + .HasMaxLength(256); + + b.Property("EmailConfirmed"); + + b.Property("EmbyConnectUserId"); + + b.Property("EpisodeRequestLimit"); + + b.Property("LastLoggedIn"); + + b.Property("LockoutEnabled"); + + b.Property("LockoutEnd"); + + b.Property("MovieRequestLimit"); + + b.Property("MusicRequestLimit"); + + b.Property("NormalizedEmail") + .HasMaxLength(256); + + b.Property("NormalizedUserName") + .HasMaxLength(256); + + b.Property("PasswordHash"); + + b.Property("PhoneNumber"); + + b.Property("PhoneNumberConfirmed"); + + b.Property("ProviderUserId"); + + b.Property("SecurityStamp"); + + b.Property("TwoFactorEnabled"); + + b.Property("UserAccessToken"); + + b.Property("UserName") + .HasMaxLength(256); + + b.Property("UserType"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasName("UserNameIndex"); + + b.ToTable("AspNetUsers"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexEpisode", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("EpisodeNumber"); + + b.Property("GrandparentKey"); + + b.Property("Key"); + + b.Property("ParentKey"); + + b.Property("SeasonNumber"); + + b.Property("Title"); + + b.HasKey("Id"); + + b.HasIndex("GrandparentKey"); + + b.ToTable("PlexEpisode"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexSeasonsContent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ParentKey"); + + b.Property("PlexContentId"); + + b.Property("PlexServerContentId"); + + b.Property("SeasonKey"); + + b.Property("SeasonNumber"); + + b.HasKey("Id"); + + b.HasIndex("PlexServerContentId"); + + b.ToTable("PlexSeasonsContent"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexServerContent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("ImdbId"); + + b.Property("Key"); + + b.Property("Quality"); + + b.Property("ReleaseYear"); + + b.Property("TheMovieDbId"); + + b.Property("Title"); + + b.Property("TvDbId"); + + b.Property("Type"); + + b.Property("Url"); + + b.HasKey("Id"); + + b.ToTable("PlexServerContent"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.RadarrCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("HasFile"); + + b.Property("TheMovieDbId"); + + b.HasKey("Id"); + + b.ToTable("RadarrCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.RecentlyAddedLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("AlbumId"); + + b.Property("ContentId"); + + b.Property("ContentType"); + + b.Property("EpisodeNumber"); + + b.Property("SeasonNumber"); + + b.Property("Type"); + + b.HasKey("Id"); + + b.ToTable("RecentlyAddedLog"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.RequestQueue", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Completed"); + + b.Property("Dts"); + + b.Property("Error"); + + b.Property("RequestId"); + + b.Property("RetryCount"); + + b.Property("Type"); + + b.HasKey("Id"); + + b.ToTable("RequestQueue"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.RequestSubscription", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("RequestId"); + + b.Property("RequestType"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("RequestSubscription"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.AlbumRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Approved"); + + b.Property("ArtistName"); + + b.Property("Available"); + + b.Property("Cover"); + + b.Property("Denied"); + + b.Property("DeniedReason"); + + b.Property("Disk"); + + b.Property("ForeignAlbumId"); + + b.Property("ForeignArtistId"); + + b.Property("MarkedAsApproved"); + + b.Property("MarkedAsAvailable"); + + b.Property("MarkedAsDenied"); + + b.Property("Rating"); + + b.Property("ReleaseDate"); + + b.Property("RequestType"); + + b.Property("RequestedByAlias"); + + b.Property("RequestedDate"); + + b.Property("RequestedUserId"); + + b.Property("Title"); + + b.HasKey("Id"); + + b.HasIndex("RequestedUserId"); + + b.ToTable("AlbumRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.ChildRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Approved"); + + b.Property("Available"); + + b.Property("Denied"); + + b.Property("DeniedReason"); + + b.Property("IssueId"); + + b.Property("MarkedAsApproved"); + + b.Property("MarkedAsAvailable"); + + b.Property("MarkedAsDenied"); + + b.Property("ParentRequestId"); + + b.Property("RequestType"); + + b.Property("RequestedByAlias"); + + b.Property("RequestedDate"); + + b.Property("RequestedUserId"); + + b.Property("SeriesType"); + + b.Property("Title"); + + b.HasKey("Id"); + + b.HasIndex("ParentRequestId"); + + b.HasIndex("RequestedUserId"); + + b.ToTable("ChildRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Value"); + + b.HasKey("Id"); + + b.ToTable("IssueCategory"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueComments", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Comment"); + + b.Property("Date"); + + b.Property("IssuesId"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("IssuesId"); + + b.HasIndex("UserId"); + + b.ToTable("IssueComments"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.Issues", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Description"); + + b.Property("IssueCategoryId"); + + b.Property("IssueId"); + + b.Property("ProviderId"); + + b.Property("RequestId"); + + b.Property("RequestType"); + + b.Property("ResovledDate"); + + b.Property("Status"); + + b.Property("Subject"); + + b.Property("Title"); + + b.Property("UserReportedId"); + + b.HasKey("Id"); + + b.HasIndex("IssueCategoryId"); + + b.HasIndex("IssueId"); + + b.HasIndex("UserReportedId"); + + b.ToTable("Issues"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.MovieRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Approved"); + + b.Property("Available"); + + b.Property("Background"); + + b.Property("Denied"); + + b.Property("DeniedReason"); + + b.Property("DigitalReleaseDate"); + + b.Property("ImdbId"); + + b.Property("IssueId"); + + b.Property("LangCode"); + + b.Property("MarkedAsApproved"); + + b.Property("MarkedAsAvailable"); + + b.Property("MarkedAsDenied"); + + b.Property("Overview"); + + b.Property("PosterPath"); + + b.Property("QualityOverride"); + + b.Property("ReleaseDate"); + + b.Property("RequestType"); + + b.Property("RequestedByAlias"); + + b.Property("RequestedDate"); + + b.Property("RequestedUserId"); + + b.Property("RootPathOverride"); + + b.Property("Status"); + + b.Property("TheMovieDbId"); + + b.Property("Title"); + + b.HasKey("Id"); + + b.HasIndex("RequestedUserId"); + + b.ToTable("MovieRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.RequestLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("EpisodeCount"); + + b.Property("RequestDate"); + + b.Property("RequestId"); + + b.Property("RequestType"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("RequestLog"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.TvRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Background"); + + b.Property("ImdbId"); + + b.Property("Overview"); + + b.Property("PosterPath"); + + b.Property("QualityOverride"); + + b.Property("ReleaseDate"); + + b.Property("RootFolder"); + + b.Property("Status"); + + b.Property("Title"); + + b.Property("TotalSeasons"); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.ToTable("TvRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.SickRageCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.ToTable("SickRageCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.SickRageEpisodeCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("EpisodeNumber"); + + b.Property("SeasonNumber"); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.ToTable("SickRageEpisodeCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.SonarrCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.ToTable("SonarrCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.SonarrEpisodeCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("EpisodeNumber"); + + b.Property("HasFile"); + + b.Property("SeasonNumber"); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.ToTable("SonarrEpisodeCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Tokens", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Token"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Tokens"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.UserNotificationPreferences", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Agent"); + + b.Property("Enabled"); + + b.Property("UserId"); + + b.Property("Value"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("UserNotificationPreferences"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.UserQualityProfiles", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("RadarrQualityProfile"); + + b.Property("RadarrRootPath"); + + b.Property("SonarrQualityProfile"); + + b.Property("SonarrQualityProfileAnime"); + + b.Property("SonarrRootPath"); + + b.Property("SonarrRootPathAnime"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("UserQualityProfiles"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Votes", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Date"); + + b.Property("Deleted"); + + b.Property("RequestId"); + + b.Property("RequestType"); + + b.Property("UserId"); + + b.Property("VoteType"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Votes"); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.EpisodeRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AirDate"); + + b.Property("Approved"); + + b.Property("Available"); + + b.Property("EpisodeNumber"); + + b.Property("Requested"); + + b.Property("SeasonId"); + + b.Property("Title"); + + b.Property("Url"); + + b.HasKey("Id"); + + b.HasIndex("SeasonId"); + + b.ToTable("EpisodeRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.SeasonRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChildRequestId"); + + b.Property("SeasonNumber"); + + b.HasKey("Id"); + + b.HasIndex("ChildRequestId"); + + b.ToTable("SeasonRequests"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole") + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole") + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Ombi.Store.Entities.OmbiUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Ombi.Store.Entities.EmbyEpisode", b => + { + b.HasOne("Ombi.Store.Entities.EmbyContent", "Series") + .WithMany("Episodes") + .HasForeignKey("ParentId") + .HasPrincipalKey("EmbyId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.NotificationUserId", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany("NotificationUserIds") + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexEpisode", b => + { + b.HasOne("Ombi.Store.Entities.PlexServerContent", "Series") + .WithMany("Episodes") + .HasForeignKey("GrandparentKey") + .HasPrincipalKey("Key") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexSeasonsContent", b => + { + b.HasOne("Ombi.Store.Entities.PlexServerContent") + .WithMany("Seasons") + .HasForeignKey("PlexServerContentId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.RequestSubscription", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.AlbumRequest", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "RequestedUser") + .WithMany() + .HasForeignKey("RequestedUserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.ChildRequests", b => + { + b.HasOne("Ombi.Store.Entities.Requests.TvRequests", "ParentRequest") + .WithMany("ChildRequests") + .HasForeignKey("ParentRequestId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Ombi.Store.Entities.OmbiUser", "RequestedUser") + .WithMany() + .HasForeignKey("RequestedUserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueComments", b => + { + b.HasOne("Ombi.Store.Entities.Requests.Issues", "Issues") + .WithMany("Comments") + .HasForeignKey("IssuesId"); + + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.Issues", b => + { + b.HasOne("Ombi.Store.Entities.Requests.IssueCategory", "IssueCategory") + .WithMany() + .HasForeignKey("IssueCategoryId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Ombi.Store.Entities.Requests.ChildRequests") + .WithMany("Issues") + .HasForeignKey("IssueId"); + + b.HasOne("Ombi.Store.Entities.Requests.MovieRequests") + .WithMany("Issues") + .HasForeignKey("IssueId"); + + b.HasOne("Ombi.Store.Entities.OmbiUser", "UserReported") + .WithMany() + .HasForeignKey("UserReportedId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.MovieRequests", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "RequestedUser") + .WithMany() + .HasForeignKey("RequestedUserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.RequestLog", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Tokens", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.UserNotificationPreferences", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany("UserNotificationPreferences") + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.UserQualityProfiles", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Votes", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.EpisodeRequests", b => + { + b.HasOne("Ombi.Store.Repository.Requests.SeasonRequests", "Season") + .WithMany("Episodes") + .HasForeignKey("SeasonId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.SeasonRequests", b => + { + b.HasOne("Ombi.Store.Entities.Requests.ChildRequests", "ChildRequest") + .WithMany("SeasonRequests") + .HasForeignKey("ChildRequestId") + .OnDelete(DeleteBehavior.Cascade); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Ombi.Store/Migrations/20190216231519_TvRequestsTotalSeasons.cs b/src/Ombi.Store/Migrations/20190216231519_TvRequestsTotalSeasons.cs new file mode 100644 index 000000000..0bae50c15 --- /dev/null +++ b/src/Ombi.Store/Migrations/20190216231519_TvRequestsTotalSeasons.cs @@ -0,0 +1,23 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Ombi.Store.Migrations +{ + public partial class TvRequestsTotalSeasons : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "TotalSeasons", + table: "TvRequests", + nullable: false, + defaultValue: 0); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "TotalSeasons", + table: "TvRequests"); + } + } +} diff --git a/src/Ombi.Store/Migrations/20191016203035_RequestIdOnPlexContent.Designer.cs b/src/Ombi.Store/Migrations/20191016203035_RequestIdOnPlexContent.Designer.cs new file mode 100644 index 000000000..f0cd74378 --- /dev/null +++ b/src/Ombi.Store/Migrations/20191016203035_RequestIdOnPlexContent.Designer.cs @@ -0,0 +1,1216 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Ombi.Store.Context; + +namespace Ombi.Store.Migrations +{ + [DbContext(typeof(OmbiContext))] + [Migration("20191016203035_RequestIdOnPlexContent")] + partial class RequestIdOnPlexContent + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.2.2-servicing-10034"); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken(); + + b.Property("Name") + .HasMaxLength(256); + + b.Property("NormalizedName") + .HasMaxLength(256); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasName("RoleNameIndex"); + + b.ToTable("AspNetRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ClaimType"); + + b.Property("ClaimValue"); + + b.Property("RoleId") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ClaimType"); + + b.Property("ClaimValue"); + + b.Property("UserId") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider"); + + b.Property("ProviderKey"); + + b.Property("ProviderDisplayName"); + + b.Property("UserId") + .IsRequired(); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId"); + + b.Property("RoleId"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId"); + + b.Property("LoginProvider"); + + b.Property("Name"); + + b.Property("Value"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.ApplicationConfiguration", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Type"); + + b.Property("Value"); + + b.HasKey("Id"); + + b.ToTable("ApplicationConfiguration"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Audit", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AuditArea"); + + b.Property("AuditType"); + + b.Property("DateTime"); + + b.Property("Description"); + + b.Property("User"); + + b.HasKey("Id"); + + b.ToTable("Audit"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.CouchPotatoCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("TheMovieDbId"); + + b.HasKey("Id"); + + b.ToTable("CouchPotatoCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.EmbyContent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("EmbyId") + .IsRequired(); + + b.Property("ImdbId"); + + b.Property("ProviderId"); + + b.Property("TheMovieDbId"); + + b.Property("Title"); + + b.Property("TvDbId"); + + b.Property("Type"); + + b.Property("Url"); + + b.HasKey("Id"); + + b.ToTable("EmbyContent"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.EmbyEpisode", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("EmbyId"); + + b.Property("EpisodeNumber"); + + b.Property("ImdbId"); + + b.Property("ParentId"); + + b.Property("ProviderId"); + + b.Property("SeasonNumber"); + + b.Property("TheMovieDbId"); + + b.Property("Title"); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.HasIndex("ParentId"); + + b.ToTable("EmbyEpisode"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.GlobalSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Content"); + + b.Property("SettingsName"); + + b.HasKey("Id"); + + b.ToTable("GlobalSettings"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.LidarrAlbumCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("ArtistId"); + + b.Property("ForeignAlbumId"); + + b.Property("Monitored"); + + b.Property("PercentOfTracks"); + + b.Property("ReleaseDate"); + + b.Property("Title"); + + b.Property("TrackCount"); + + b.HasKey("Id"); + + b.ToTable("LidarrAlbumCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.LidarrArtistCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ArtistId"); + + b.Property("ArtistName"); + + b.Property("ForeignArtistId"); + + b.Property("Monitored"); + + b.HasKey("Id"); + + b.ToTable("LidarrArtistCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.NotificationTemplates", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Agent"); + + b.Property("Enabled"); + + b.Property("Message"); + + b.Property("NotificationType"); + + b.Property("Subject"); + + b.HasKey("Id"); + + b.ToTable("NotificationTemplates"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.NotificationUserId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("PlayerId"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("NotificationUserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.OmbiUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AccessFailedCount"); + + b.Property("Alias"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken(); + + b.Property("Email") + .HasMaxLength(256); + + b.Property("EmailConfirmed"); + + b.Property("EmbyConnectUserId"); + + b.Property("EpisodeRequestLimit"); + + b.Property("LastLoggedIn"); + + b.Property("LockoutEnabled"); + + b.Property("LockoutEnd"); + + b.Property("MovieRequestLimit"); + + b.Property("MusicRequestLimit"); + + b.Property("NormalizedEmail") + .HasMaxLength(256); + + b.Property("NormalizedUserName") + .HasMaxLength(256); + + b.Property("PasswordHash"); + + b.Property("PhoneNumber"); + + b.Property("PhoneNumberConfirmed"); + + b.Property("ProviderUserId"); + + b.Property("SecurityStamp"); + + b.Property("TwoFactorEnabled"); + + b.Property("UserAccessToken"); + + b.Property("UserName") + .HasMaxLength(256); + + b.Property("UserType"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasName("UserNameIndex"); + + b.ToTable("AspNetUsers"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexEpisode", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("EpisodeNumber"); + + b.Property("GrandparentKey"); + + b.Property("Key"); + + b.Property("ParentKey"); + + b.Property("SeasonNumber"); + + b.Property("Title"); + + b.HasKey("Id"); + + b.HasIndex("GrandparentKey"); + + b.ToTable("PlexEpisode"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexSeasonsContent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ParentKey"); + + b.Property("PlexContentId"); + + b.Property("PlexServerContentId"); + + b.Property("SeasonKey"); + + b.Property("SeasonNumber"); + + b.HasKey("Id"); + + b.HasIndex("PlexServerContentId"); + + b.ToTable("PlexSeasonsContent"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexServerContent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("ImdbId"); + + b.Property("Key"); + + b.Property("Quality"); + + b.Property("ReleaseYear"); + + b.Property("RequestId"); + + b.Property("TheMovieDbId"); + + b.Property("Title"); + + b.Property("TvDbId"); + + b.Property("Type"); + + b.Property("Url"); + + b.HasKey("Id"); + + b.ToTable("PlexServerContent"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.RadarrCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("HasFile"); + + b.Property("TheMovieDbId"); + + b.HasKey("Id"); + + b.ToTable("RadarrCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.RecentlyAddedLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("AlbumId"); + + b.Property("ContentId"); + + b.Property("ContentType"); + + b.Property("EpisodeNumber"); + + b.Property("SeasonNumber"); + + b.Property("Type"); + + b.HasKey("Id"); + + b.ToTable("RecentlyAddedLog"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.RequestQueue", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Completed"); + + b.Property("Dts"); + + b.Property("Error"); + + b.Property("RequestId"); + + b.Property("RetryCount"); + + b.Property("Type"); + + b.HasKey("Id"); + + b.ToTable("RequestQueue"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.RequestSubscription", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("RequestId"); + + b.Property("RequestType"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("RequestSubscription"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.AlbumRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Approved"); + + b.Property("ArtistName"); + + b.Property("Available"); + + b.Property("Cover"); + + b.Property("Denied"); + + b.Property("DeniedReason"); + + b.Property("Disk"); + + b.Property("ForeignAlbumId"); + + b.Property("ForeignArtistId"); + + b.Property("MarkedAsApproved"); + + b.Property("MarkedAsAvailable"); + + b.Property("MarkedAsDenied"); + + b.Property("Rating"); + + b.Property("ReleaseDate"); + + b.Property("RequestType"); + + b.Property("RequestedByAlias"); + + b.Property("RequestedDate"); + + b.Property("RequestedUserId"); + + b.Property("Title"); + + b.HasKey("Id"); + + b.HasIndex("RequestedUserId"); + + b.ToTable("AlbumRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.ChildRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Approved"); + + b.Property("Available"); + + b.Property("Denied"); + + b.Property("DeniedReason"); + + b.Property("IssueId"); + + b.Property("MarkedAsApproved"); + + b.Property("MarkedAsAvailable"); + + b.Property("MarkedAsDenied"); + + b.Property("ParentRequestId"); + + b.Property("RequestType"); + + b.Property("RequestedByAlias"); + + b.Property("RequestedDate"); + + b.Property("RequestedUserId"); + + b.Property("SeriesType"); + + b.Property("Title"); + + b.HasKey("Id"); + + b.HasIndex("ParentRequestId"); + + b.HasIndex("RequestedUserId"); + + b.ToTable("ChildRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Value"); + + b.HasKey("Id"); + + b.ToTable("IssueCategory"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueComments", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Comment"); + + b.Property("Date"); + + b.Property("IssuesId"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("IssuesId"); + + b.HasIndex("UserId"); + + b.ToTable("IssueComments"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.Issues", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Description"); + + b.Property("IssueCategoryId"); + + b.Property("IssueId"); + + b.Property("ProviderId"); + + b.Property("RequestId"); + + b.Property("RequestType"); + + b.Property("ResovledDate"); + + b.Property("Status"); + + b.Property("Subject"); + + b.Property("Title"); + + b.Property("UserReportedId"); + + b.HasKey("Id"); + + b.HasIndex("IssueCategoryId"); + + b.HasIndex("IssueId"); + + b.HasIndex("UserReportedId"); + + b.ToTable("Issues"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.MovieRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Approved"); + + b.Property("Available"); + + b.Property("Background"); + + b.Property("Denied"); + + b.Property("DeniedReason"); + + b.Property("DigitalReleaseDate"); + + b.Property("ImdbId"); + + b.Property("IssueId"); + + b.Property("LangCode"); + + b.Property("MarkedAsApproved"); + + b.Property("MarkedAsAvailable"); + + b.Property("MarkedAsDenied"); + + b.Property("Overview"); + + b.Property("PosterPath"); + + b.Property("QualityOverride"); + + b.Property("ReleaseDate"); + + b.Property("RequestType"); + + b.Property("RequestedByAlias"); + + b.Property("RequestedDate"); + + b.Property("RequestedUserId"); + + b.Property("RootPathOverride"); + + b.Property("Status"); + + b.Property("TheMovieDbId"); + + b.Property("Title"); + + b.HasKey("Id"); + + b.HasIndex("RequestedUserId"); + + b.ToTable("MovieRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.RequestLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("EpisodeCount"); + + b.Property("RequestDate"); + + b.Property("RequestId"); + + b.Property("RequestType"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("RequestLog"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.TvRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Background"); + + b.Property("ImdbId"); + + b.Property("Overview"); + + b.Property("PosterPath"); + + b.Property("QualityOverride"); + + b.Property("ReleaseDate"); + + b.Property("RootFolder"); + + b.Property("Status"); + + b.Property("Title"); + + b.Property("TotalSeasons"); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.ToTable("TvRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.SickRageCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.ToTable("SickRageCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.SickRageEpisodeCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("EpisodeNumber"); + + b.Property("SeasonNumber"); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.ToTable("SickRageEpisodeCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.SonarrCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.ToTable("SonarrCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.SonarrEpisodeCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("EpisodeNumber"); + + b.Property("HasFile"); + + b.Property("SeasonNumber"); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.ToTable("SonarrEpisodeCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Tokens", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Token"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Tokens"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.UserNotificationPreferences", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Agent"); + + b.Property("Enabled"); + + b.Property("UserId"); + + b.Property("Value"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("UserNotificationPreferences"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.UserQualityProfiles", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("RadarrQualityProfile"); + + b.Property("RadarrRootPath"); + + b.Property("SonarrQualityProfile"); + + b.Property("SonarrQualityProfileAnime"); + + b.Property("SonarrRootPath"); + + b.Property("SonarrRootPathAnime"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("UserQualityProfiles"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Votes", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Date"); + + b.Property("Deleted"); + + b.Property("RequestId"); + + b.Property("RequestType"); + + b.Property("UserId"); + + b.Property("VoteType"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Votes"); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.EpisodeRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AirDate"); + + b.Property("Approved"); + + b.Property("Available"); + + b.Property("EpisodeNumber"); + + b.Property("Requested"); + + b.Property("SeasonId"); + + b.Property("Title"); + + b.Property("Url"); + + b.HasKey("Id"); + + b.HasIndex("SeasonId"); + + b.ToTable("EpisodeRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.SeasonRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChildRequestId"); + + b.Property("SeasonNumber"); + + b.HasKey("Id"); + + b.HasIndex("ChildRequestId"); + + b.ToTable("SeasonRequests"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole") + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole") + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Ombi.Store.Entities.OmbiUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Ombi.Store.Entities.EmbyEpisode", b => + { + b.HasOne("Ombi.Store.Entities.EmbyContent", "Series") + .WithMany("Episodes") + .HasForeignKey("ParentId") + .HasPrincipalKey("EmbyId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.NotificationUserId", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany("NotificationUserIds") + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexEpisode", b => + { + b.HasOne("Ombi.Store.Entities.PlexServerContent", "Series") + .WithMany("Episodes") + .HasForeignKey("GrandparentKey") + .HasPrincipalKey("Key") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexSeasonsContent", b => + { + b.HasOne("Ombi.Store.Entities.PlexServerContent") + .WithMany("Seasons") + .HasForeignKey("PlexServerContentId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.RequestSubscription", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.AlbumRequest", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "RequestedUser") + .WithMany() + .HasForeignKey("RequestedUserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.ChildRequests", b => + { + b.HasOne("Ombi.Store.Entities.Requests.TvRequests", "ParentRequest") + .WithMany("ChildRequests") + .HasForeignKey("ParentRequestId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Ombi.Store.Entities.OmbiUser", "RequestedUser") + .WithMany() + .HasForeignKey("RequestedUserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueComments", b => + { + b.HasOne("Ombi.Store.Entities.Requests.Issues", "Issues") + .WithMany("Comments") + .HasForeignKey("IssuesId"); + + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.Issues", b => + { + b.HasOne("Ombi.Store.Entities.Requests.IssueCategory", "IssueCategory") + .WithMany() + .HasForeignKey("IssueCategoryId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Ombi.Store.Entities.Requests.ChildRequests") + .WithMany("Issues") + .HasForeignKey("IssueId"); + + b.HasOne("Ombi.Store.Entities.Requests.MovieRequests") + .WithMany("Issues") + .HasForeignKey("IssueId"); + + b.HasOne("Ombi.Store.Entities.OmbiUser", "UserReported") + .WithMany() + .HasForeignKey("UserReportedId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.MovieRequests", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "RequestedUser") + .WithMany() + .HasForeignKey("RequestedUserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.RequestLog", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Tokens", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.UserNotificationPreferences", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany("UserNotificationPreferences") + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.UserQualityProfiles", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Votes", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.EpisodeRequests", b => + { + b.HasOne("Ombi.Store.Repository.Requests.SeasonRequests", "Season") + .WithMany("Episodes") + .HasForeignKey("SeasonId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.SeasonRequests", b => + { + b.HasOne("Ombi.Store.Entities.Requests.ChildRequests", "ChildRequest") + .WithMany("SeasonRequests") + .HasForeignKey("ChildRequestId") + .OnDelete(DeleteBehavior.Cascade); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Ombi.Store/Migrations/20191016203035_RequestIdOnPlexContent.cs b/src/Ombi.Store/Migrations/20191016203035_RequestIdOnPlexContent.cs new file mode 100644 index 000000000..f197cdcdb --- /dev/null +++ b/src/Ombi.Store/Migrations/20191016203035_RequestIdOnPlexContent.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Ombi.Store.Migrations +{ + public partial class RequestIdOnPlexContent : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "RequestId", + table: "PlexServerContent", + nullable: true); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "RequestId", + table: "PlexServerContent"); + } + } +} diff --git a/src/Ombi.Store/Migrations/External/20181004134907_Inital.Designer.cs b/src/Ombi.Store/Migrations/External/20181004134907_Inital.Designer.cs new file mode 100644 index 000000000..776d3e082 --- /dev/null +++ b/src/Ombi.Store/Migrations/External/20181004134907_Inital.Designer.cs @@ -0,0 +1,312 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Ombi.Store.Context; + +namespace Ombi.Store.Migrations.External +{ + [DbContext(typeof(ExternalContext))] + [Migration("20181004134907_Inital")] + partial class Inital + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.1.3-rtm-32065"); + + modelBuilder.Entity("Ombi.Store.Entities.CouchPotatoCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("TheMovieDbId"); + + b.HasKey("Id"); + + b.ToTable("CouchPotatoCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.EmbyContent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("EmbyId") + .IsRequired(); + + b.Property("ImdbId"); + + b.Property("ProviderId"); + + b.Property("TheMovieDbId"); + + b.Property("Title"); + + b.Property("TvDbId"); + + b.Property("Type"); + + b.Property("Url"); + + b.HasKey("Id"); + + b.ToTable("EmbyContent"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.EmbyEpisode", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("EmbyId"); + + b.Property("EpisodeNumber"); + + b.Property("ImdbId"); + + b.Property("ParentId"); + + b.Property("ProviderId"); + + b.Property("SeasonNumber"); + + b.Property("TheMovieDbId"); + + b.Property("Title"); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.HasIndex("ParentId"); + + b.ToTable("EmbyEpisode"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.LidarrAlbumCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("ArtistId"); + + b.Property("ForeignAlbumId"); + + b.Property("Monitored"); + + b.Property("PercentOfTracks"); + + b.Property("ReleaseDate"); + + b.Property("Title"); + + b.Property("TrackCount"); + + b.HasKey("Id"); + + b.ToTable("LidarrAlbumCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.LidarrArtistCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ArtistId"); + + b.Property("ArtistName"); + + b.Property("ForeignArtistId"); + + b.Property("Monitored"); + + b.HasKey("Id"); + + b.ToTable("LidarrArtistCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexEpisode", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("EpisodeNumber"); + + b.Property("GrandparentKey"); + + b.Property("Key"); + + b.Property("ParentKey"); + + b.Property("SeasonNumber"); + + b.Property("Title"); + + b.HasKey("Id"); + + b.HasIndex("GrandparentKey"); + + b.ToTable("PlexEpisode"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexSeasonsContent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ParentKey"); + + b.Property("PlexContentId"); + + b.Property("PlexServerContentId"); + + b.Property("SeasonKey"); + + b.Property("SeasonNumber"); + + b.HasKey("Id"); + + b.HasIndex("PlexServerContentId"); + + b.ToTable("PlexSeasonsContent"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexServerContent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("ImdbId"); + + b.Property("Key"); + + b.Property("Quality"); + + b.Property("ReleaseYear"); + + b.Property("TheMovieDbId"); + + b.Property("Title"); + + b.Property("TvDbId"); + + b.Property("Type"); + + b.Property("Url"); + + b.HasKey("Id"); + + b.ToTable("PlexServerContent"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.RadarrCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("HasFile"); + + b.Property("TheMovieDbId"); + + b.HasKey("Id"); + + b.ToTable("RadarrCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.SickRageCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.ToTable("SickRageCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.SickRageEpisodeCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("EpisodeNumber"); + + b.Property("SeasonNumber"); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.ToTable("SickRageEpisodeCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.SonarrCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.ToTable("SonarrCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.SonarrEpisodeCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("EpisodeNumber"); + + b.Property("HasFile"); + + b.Property("SeasonNumber"); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.ToTable("SonarrEpisodeCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.EmbyEpisode", b => + { + b.HasOne("Ombi.Store.Entities.EmbyContent", "Series") + .WithMany("Episodes") + .HasForeignKey("ParentId") + .HasPrincipalKey("EmbyId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexEpisode", b => + { + b.HasOne("Ombi.Store.Entities.PlexServerContent", "Series") + .WithMany("Episodes") + .HasForeignKey("GrandparentKey") + .HasPrincipalKey("Key") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexSeasonsContent", b => + { + b.HasOne("Ombi.Store.Entities.PlexServerContent") + .WithMany("Seasons") + .HasForeignKey("PlexServerContentId"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Ombi.Store/Migrations/External/20181004134907_Inital.cs b/src/Ombi.Store/Migrations/External/20181004134907_Inital.cs new file mode 100644 index 000000000..bc4dc509e --- /dev/null +++ b/src/Ombi.Store/Migrations/External/20181004134907_Inital.cs @@ -0,0 +1,308 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Ombi.Store.Migrations.External +{ + public partial class Inital : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "CouchPotatoCache", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + TheMovieDbId = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_CouchPotatoCache", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "EmbyContent", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Title = table.Column(nullable: true), + ProviderId = table.Column(nullable: true), + EmbyId = table.Column(nullable: false), + Type = table.Column(nullable: false), + AddedAt = table.Column(nullable: false), + ImdbId = table.Column(nullable: true), + TheMovieDbId = table.Column(nullable: true), + TvDbId = table.Column(nullable: true), + Url = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_EmbyContent", x => x.Id); + table.UniqueConstraint("AK_EmbyContent_EmbyId", x => x.EmbyId); + }); + + migrationBuilder.CreateTable( + name: "LidarrAlbumCache", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + ArtistId = table.Column(nullable: false), + ForeignAlbumId = table.Column(nullable: true), + TrackCount = table.Column(nullable: false), + ReleaseDate = table.Column(nullable: false), + Monitored = table.Column(nullable: false), + Title = table.Column(nullable: true), + PercentOfTracks = table.Column(nullable: false), + AddedAt = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_LidarrAlbumCache", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "LidarrArtistCache", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + ArtistId = table.Column(nullable: false), + ArtistName = table.Column(nullable: true), + ForeignArtistId = table.Column(nullable: true), + Monitored = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_LidarrArtistCache", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "PlexServerContent", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Title = table.Column(nullable: true), + ReleaseYear = table.Column(nullable: true), + ImdbId = table.Column(nullable: true), + TvDbId = table.Column(nullable: true), + TheMovieDbId = table.Column(nullable: true), + Type = table.Column(nullable: false), + Url = table.Column(nullable: true), + Key = table.Column(nullable: false), + AddedAt = table.Column(nullable: false), + Quality = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_PlexServerContent", x => x.Id); + table.UniqueConstraint("AK_PlexServerContent_Key", x => x.Key); + }); + + migrationBuilder.CreateTable( + name: "RadarrCache", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + TheMovieDbId = table.Column(nullable: false), + HasFile = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_RadarrCache", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "SickRageCache", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + TvDbId = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_SickRageCache", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "SickRageEpisodeCache", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + SeasonNumber = table.Column(nullable: false), + EpisodeNumber = table.Column(nullable: false), + TvDbId = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_SickRageEpisodeCache", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "SonarrCache", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + TvDbId = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_SonarrCache", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "SonarrEpisodeCache", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + SeasonNumber = table.Column(nullable: false), + EpisodeNumber = table.Column(nullable: false), + TvDbId = table.Column(nullable: false), + HasFile = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_SonarrEpisodeCache", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "EmbyEpisode", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Title = table.Column(nullable: true), + EmbyId = table.Column(nullable: true), + EpisodeNumber = table.Column(nullable: false), + SeasonNumber = table.Column(nullable: false), + ParentId = table.Column(nullable: true), + ProviderId = table.Column(nullable: true), + AddedAt = table.Column(nullable: false), + TvDbId = table.Column(nullable: true), + ImdbId = table.Column(nullable: true), + TheMovieDbId = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_EmbyEpisode", x => x.Id); + table.ForeignKey( + name: "FK_EmbyEpisode_EmbyContent_ParentId", + column: x => x.ParentId, + principalTable: "EmbyContent", + principalColumn: "EmbyId", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "PlexEpisode", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + EpisodeNumber = table.Column(nullable: false), + SeasonNumber = table.Column(nullable: false), + Key = table.Column(nullable: false), + Title = table.Column(nullable: true), + ParentKey = table.Column(nullable: false), + GrandparentKey = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_PlexEpisode", x => x.Id); + table.ForeignKey( + name: "FK_PlexEpisode_PlexServerContent_GrandparentKey", + column: x => x.GrandparentKey, + principalTable: "PlexServerContent", + principalColumn: "Key", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "PlexSeasonsContent", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + PlexContentId = table.Column(nullable: false), + SeasonNumber = table.Column(nullable: false), + SeasonKey = table.Column(nullable: false), + ParentKey = table.Column(nullable: false), + PlexServerContentId = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_PlexSeasonsContent", x => x.Id); + table.ForeignKey( + name: "FK_PlexSeasonsContent_PlexServerContent_PlexServerContentId", + column: x => x.PlexServerContentId, + principalTable: "PlexServerContent", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateIndex( + name: "IX_EmbyEpisode_ParentId", + table: "EmbyEpisode", + column: "ParentId"); + + migrationBuilder.CreateIndex( + name: "IX_PlexEpisode_GrandparentKey", + table: "PlexEpisode", + column: "GrandparentKey"); + + migrationBuilder.CreateIndex( + name: "IX_PlexSeasonsContent_PlexServerContentId", + table: "PlexSeasonsContent", + column: "PlexServerContentId"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "CouchPotatoCache"); + + migrationBuilder.DropTable( + name: "EmbyEpisode"); + + migrationBuilder.DropTable( + name: "LidarrAlbumCache"); + + migrationBuilder.DropTable( + name: "LidarrArtistCache"); + + migrationBuilder.DropTable( + name: "PlexEpisode"); + + migrationBuilder.DropTable( + name: "PlexSeasonsContent"); + + migrationBuilder.DropTable( + name: "RadarrCache"); + + migrationBuilder.DropTable( + name: "SickRageCache"); + + migrationBuilder.DropTable( + name: "SickRageEpisodeCache"); + + migrationBuilder.DropTable( + name: "SonarrCache"); + + migrationBuilder.DropTable( + name: "SonarrEpisodeCache"); + + migrationBuilder.DropTable( + name: "EmbyContent"); + + migrationBuilder.DropTable( + name: "PlexServerContent"); + } + } +} diff --git a/src/Ombi.Store/Migrations/External/20191018225833_PlexContentId.Designer.cs b/src/Ombi.Store/Migrations/External/20191018225833_PlexContentId.Designer.cs new file mode 100644 index 000000000..cba04a14c --- /dev/null +++ b/src/Ombi.Store/Migrations/External/20191018225833_PlexContentId.Designer.cs @@ -0,0 +1,314 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Ombi.Store.Context; + +namespace Ombi.Store.Migrations.External +{ + [DbContext(typeof(ExternalContext))] + [Migration("20191018225833_PlexContentId")] + partial class PlexContentId + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.2.2-servicing-10034"); + + modelBuilder.Entity("Ombi.Store.Entities.CouchPotatoCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("TheMovieDbId"); + + b.HasKey("Id"); + + b.ToTable("CouchPotatoCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.EmbyContent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("EmbyId") + .IsRequired(); + + b.Property("ImdbId"); + + b.Property("ProviderId"); + + b.Property("TheMovieDbId"); + + b.Property("Title"); + + b.Property("TvDbId"); + + b.Property("Type"); + + b.Property("Url"); + + b.HasKey("Id"); + + b.ToTable("EmbyContent"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.EmbyEpisode", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("EmbyId"); + + b.Property("EpisodeNumber"); + + b.Property("ImdbId"); + + b.Property("ParentId"); + + b.Property("ProviderId"); + + b.Property("SeasonNumber"); + + b.Property("TheMovieDbId"); + + b.Property("Title"); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.HasIndex("ParentId"); + + b.ToTable("EmbyEpisode"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.LidarrAlbumCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("ArtistId"); + + b.Property("ForeignAlbumId"); + + b.Property("Monitored"); + + b.Property("PercentOfTracks"); + + b.Property("ReleaseDate"); + + b.Property("Title"); + + b.Property("TrackCount"); + + b.HasKey("Id"); + + b.ToTable("LidarrAlbumCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.LidarrArtistCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ArtistId"); + + b.Property("ArtistName"); + + b.Property("ForeignArtistId"); + + b.Property("Monitored"); + + b.HasKey("Id"); + + b.ToTable("LidarrArtistCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexEpisode", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("EpisodeNumber"); + + b.Property("GrandparentKey"); + + b.Property("Key"); + + b.Property("ParentKey"); + + b.Property("SeasonNumber"); + + b.Property("Title"); + + b.HasKey("Id"); + + b.HasIndex("GrandparentKey"); + + b.ToTable("PlexEpisode"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexSeasonsContent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ParentKey"); + + b.Property("PlexContentId"); + + b.Property("PlexServerContentId"); + + b.Property("SeasonKey"); + + b.Property("SeasonNumber"); + + b.HasKey("Id"); + + b.HasIndex("PlexServerContentId"); + + b.ToTable("PlexSeasonsContent"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexServerContent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("ImdbId"); + + b.Property("Key"); + + b.Property("Quality"); + + b.Property("ReleaseYear"); + + b.Property("RequestId"); + + b.Property("TheMovieDbId"); + + b.Property("Title"); + + b.Property("TvDbId"); + + b.Property("Type"); + + b.Property("Url"); + + b.HasKey("Id"); + + b.ToTable("PlexServerContent"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.RadarrCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("HasFile"); + + b.Property("TheMovieDbId"); + + b.HasKey("Id"); + + b.ToTable("RadarrCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.SickRageCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.ToTable("SickRageCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.SickRageEpisodeCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("EpisodeNumber"); + + b.Property("SeasonNumber"); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.ToTable("SickRageEpisodeCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.SonarrCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.ToTable("SonarrCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.SonarrEpisodeCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("EpisodeNumber"); + + b.Property("HasFile"); + + b.Property("SeasonNumber"); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.ToTable("SonarrEpisodeCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.EmbyEpisode", b => + { + b.HasOne("Ombi.Store.Entities.EmbyContent", "Series") + .WithMany("Episodes") + .HasForeignKey("ParentId") + .HasPrincipalKey("EmbyId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexEpisode", b => + { + b.HasOne("Ombi.Store.Entities.PlexServerContent", "Series") + .WithMany("Episodes") + .HasForeignKey("GrandparentKey") + .HasPrincipalKey("Key") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexSeasonsContent", b => + { + b.HasOne("Ombi.Store.Entities.PlexServerContent") + .WithMany("Seasons") + .HasForeignKey("PlexServerContentId"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Ombi.Store/Migrations/External/20191018225833_PlexContentId.cs b/src/Ombi.Store/Migrations/External/20191018225833_PlexContentId.cs new file mode 100644 index 000000000..5d5e33dce --- /dev/null +++ b/src/Ombi.Store/Migrations/External/20191018225833_PlexContentId.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Ombi.Store.Migrations.External +{ + public partial class PlexContentId : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "RequestId", + table: "PlexServerContent", + nullable: true); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "RequestId", + table: "PlexServerContent"); + } + } +} diff --git a/src/Ombi.Store/Migrations/External/ExternalContextModelSnapshot.cs b/src/Ombi.Store/Migrations/External/ExternalContextModelSnapshot.cs new file mode 100644 index 000000000..77ef6b080 --- /dev/null +++ b/src/Ombi.Store/Migrations/External/ExternalContextModelSnapshot.cs @@ -0,0 +1,312 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Ombi.Store.Context; + +namespace Ombi.Store.Migrations.External +{ + [DbContext(typeof(ExternalContext))] + partial class ExternalContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.2.2-servicing-10034"); + + modelBuilder.Entity("Ombi.Store.Entities.CouchPotatoCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("TheMovieDbId"); + + b.HasKey("Id"); + + b.ToTable("CouchPotatoCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.EmbyContent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("EmbyId") + .IsRequired(); + + b.Property("ImdbId"); + + b.Property("ProviderId"); + + b.Property("TheMovieDbId"); + + b.Property("Title"); + + b.Property("TvDbId"); + + b.Property("Type"); + + b.Property("Url"); + + b.HasKey("Id"); + + b.ToTable("EmbyContent"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.EmbyEpisode", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("EmbyId"); + + b.Property("EpisodeNumber"); + + b.Property("ImdbId"); + + b.Property("ParentId"); + + b.Property("ProviderId"); + + b.Property("SeasonNumber"); + + b.Property("TheMovieDbId"); + + b.Property("Title"); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.HasIndex("ParentId"); + + b.ToTable("EmbyEpisode"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.LidarrAlbumCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("ArtistId"); + + b.Property("ForeignAlbumId"); + + b.Property("Monitored"); + + b.Property("PercentOfTracks"); + + b.Property("ReleaseDate"); + + b.Property("Title"); + + b.Property("TrackCount"); + + b.HasKey("Id"); + + b.ToTable("LidarrAlbumCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.LidarrArtistCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ArtistId"); + + b.Property("ArtistName"); + + b.Property("ForeignArtistId"); + + b.Property("Monitored"); + + b.HasKey("Id"); + + b.ToTable("LidarrArtistCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexEpisode", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("EpisodeNumber"); + + b.Property("GrandparentKey"); + + b.Property("Key"); + + b.Property("ParentKey"); + + b.Property("SeasonNumber"); + + b.Property("Title"); + + b.HasKey("Id"); + + b.HasIndex("GrandparentKey"); + + b.ToTable("PlexEpisode"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexSeasonsContent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ParentKey"); + + b.Property("PlexContentId"); + + b.Property("PlexServerContentId"); + + b.Property("SeasonKey"); + + b.Property("SeasonNumber"); + + b.HasKey("Id"); + + b.HasIndex("PlexServerContentId"); + + b.ToTable("PlexSeasonsContent"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexServerContent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("ImdbId"); + + b.Property("Key"); + + b.Property("Quality"); + + b.Property("ReleaseYear"); + + b.Property("RequestId"); + + b.Property("TheMovieDbId"); + + b.Property("Title"); + + b.Property("TvDbId"); + + b.Property("Type"); + + b.Property("Url"); + + b.HasKey("Id"); + + b.ToTable("PlexServerContent"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.RadarrCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("HasFile"); + + b.Property("TheMovieDbId"); + + b.HasKey("Id"); + + b.ToTable("RadarrCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.SickRageCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.ToTable("SickRageCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.SickRageEpisodeCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("EpisodeNumber"); + + b.Property("SeasonNumber"); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.ToTable("SickRageEpisodeCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.SonarrCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.ToTable("SonarrCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.SonarrEpisodeCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("EpisodeNumber"); + + b.Property("HasFile"); + + b.Property("SeasonNumber"); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.ToTable("SonarrEpisodeCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.EmbyEpisode", b => + { + b.HasOne("Ombi.Store.Entities.EmbyContent", "Series") + .WithMany("Episodes") + .HasForeignKey("ParentId") + .HasPrincipalKey("EmbyId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexEpisode", b => + { + b.HasOne("Ombi.Store.Entities.PlexServerContent", "Series") + .WithMany("Episodes") + .HasForeignKey("GrandparentKey") + .HasPrincipalKey("Key") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexSeasonsContent", b => + { + b.HasOne("Ombi.Store.Entities.PlexServerContent") + .WithMany("Seasons") + .HasForeignKey("PlexServerContentId"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Ombi.Store/Migrations/OmbiContextModelSnapshot.cs b/src/Ombi.Store/Migrations/OmbiContextModelSnapshot.cs index ea624fadb..4be5b7a43 100644 --- a/src/Ombi.Store/Migrations/OmbiContextModelSnapshot.cs +++ b/src/Ombi.Store/Migrations/OmbiContextModelSnapshot.cs @@ -1,15 +1,9 @@ // +using System; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage; -using Microsoft.EntityFrameworkCore.Storage.Internal; -using Ombi.Helpers; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; using Ombi.Store.Context; -using Ombi.Store.Entities; -using Ombi.Store.Entities.Requests; -using System; namespace Ombi.Store.Migrations { @@ -20,7 +14,7 @@ namespace Ombi.Store.Migrations { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "2.0.3-rtm-10026"); + .HasAnnotation("ProductVersion", "2.2.2-servicing-10034"); modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => { @@ -250,6 +244,50 @@ namespace Ombi.Store.Migrations b.ToTable("GlobalSettings"); }); + modelBuilder.Entity("Ombi.Store.Entities.LidarrAlbumCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("ArtistId"); + + b.Property("ForeignAlbumId"); + + b.Property("Monitored"); + + b.Property("PercentOfTracks"); + + b.Property("ReleaseDate"); + + b.Property("Title"); + + b.Property("TrackCount"); + + b.HasKey("Id"); + + b.ToTable("LidarrAlbumCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.LidarrArtistCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ArtistId"); + + b.Property("ArtistName"); + + b.Property("ForeignArtistId"); + + b.Property("Monitored"); + + b.HasKey("Id"); + + b.ToTable("LidarrArtistCache"); + }); + modelBuilder.Entity("Ombi.Store.Entities.NotificationTemplates", b => { b.Property("Id") @@ -317,6 +355,8 @@ namespace Ombi.Store.Migrations b.Property("MovieRequestLimit"); + b.Property("MusicRequestLimit"); + b.Property("NormalizedEmail") .HasMaxLength(256); @@ -415,6 +455,8 @@ namespace Ombi.Store.Migrations b.Property("ReleaseYear"); + b.Property("RequestId"); + b.Property("TheMovieDbId"); b.Property("Title"); @@ -451,6 +493,8 @@ namespace Ombi.Store.Migrations b.Property("AddedAt"); + b.Property("AlbumId"); + b.Property("ContentId"); b.Property("ContentType"); @@ -466,6 +510,96 @@ namespace Ombi.Store.Migrations b.ToTable("RecentlyAddedLog"); }); + modelBuilder.Entity("Ombi.Store.Entities.RequestQueue", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Completed"); + + b.Property("Dts"); + + b.Property("Error"); + + b.Property("RequestId"); + + b.Property("RetryCount"); + + b.Property("Type"); + + b.HasKey("Id"); + + b.ToTable("RequestQueue"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.RequestSubscription", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("RequestId"); + + b.Property("RequestType"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("RequestSubscription"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.AlbumRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Approved"); + + b.Property("ArtistName"); + + b.Property("Available"); + + b.Property("Cover"); + + b.Property("Denied"); + + b.Property("DeniedReason"); + + b.Property("Disk"); + + b.Property("ForeignAlbumId"); + + b.Property("ForeignArtistId"); + + b.Property("MarkedAsApproved"); + + b.Property("MarkedAsAvailable"); + + b.Property("MarkedAsDenied"); + + b.Property("Rating"); + + b.Property("ReleaseDate"); + + b.Property("RequestType"); + + b.Property("RequestedByAlias"); + + b.Property("RequestedDate"); + + b.Property("RequestedUserId"); + + b.Property("Title"); + + b.HasKey("Id"); + + b.HasIndex("RequestedUserId"); + + b.ToTable("AlbumRequests"); + }); + modelBuilder.Entity("Ombi.Store.Entities.Requests.ChildRequests", b => { b.Property("Id") @@ -481,10 +615,18 @@ namespace Ombi.Store.Migrations b.Property("IssueId"); + b.Property("MarkedAsApproved"); + + b.Property("MarkedAsAvailable"); + + b.Property("MarkedAsDenied"); + b.Property("ParentRequestId"); b.Property("RequestType"); + b.Property("RequestedByAlias"); + b.Property("RequestedDate"); b.Property("RequestedUserId"); @@ -595,6 +737,14 @@ namespace Ombi.Store.Migrations b.Property("IssueId"); + b.Property("LangCode"); + + b.Property("MarkedAsApproved"); + + b.Property("MarkedAsAvailable"); + + b.Property("MarkedAsDenied"); + b.Property("Overview"); b.Property("PosterPath"); @@ -605,6 +755,8 @@ namespace Ombi.Store.Migrations b.Property("RequestType"); + b.Property("RequestedByAlias"); + b.Property("RequestedDate"); b.Property("RequestedUserId"); @@ -669,6 +821,8 @@ namespace Ombi.Store.Migrations b.Property("Title"); + b.Property("TotalSeasons"); + b.Property("TvDbId"); b.HasKey("Id"); @@ -676,24 +830,6 @@ namespace Ombi.Store.Migrations b.ToTable("TvRequests"); }); - modelBuilder.Entity("Ombi.Store.Entities.RequestSubscription", b => - { - b.Property("Id") - .ValueGeneratedOnAdd(); - - b.Property("RequestId"); - - b.Property("RequestType"); - - b.Property("UserId"); - - b.HasKey("Id"); - - b.HasIndex("UserId"); - - b.ToTable("RequestSubscription"); - }); - modelBuilder.Entity("Ombi.Store.Entities.SickRageCache", b => { b.Property("Id") @@ -768,6 +904,76 @@ namespace Ombi.Store.Migrations b.ToTable("Tokens"); }); + modelBuilder.Entity("Ombi.Store.Entities.UserNotificationPreferences", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Agent"); + + b.Property("Enabled"); + + b.Property("UserId"); + + b.Property("Value"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("UserNotificationPreferences"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.UserQualityProfiles", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("RadarrQualityProfile"); + + b.Property("RadarrRootPath"); + + b.Property("SonarrQualityProfile"); + + b.Property("SonarrQualityProfileAnime"); + + b.Property("SonarrRootPath"); + + b.Property("SonarrRootPathAnime"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("UserQualityProfiles"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Votes", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Date"); + + b.Property("Deleted"); + + b.Property("RequestId"); + + b.Property("RequestType"); + + b.Property("UserId"); + + b.Property("VoteType"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Votes"); + }); + modelBuilder.Entity("Ombi.Store.Repository.Requests.EpisodeRequests", b => { b.Property("Id") @@ -888,6 +1094,20 @@ namespace Ombi.Store.Migrations .HasForeignKey("PlexServerContentId"); }); + modelBuilder.Entity("Ombi.Store.Entities.RequestSubscription", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.AlbumRequest", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "RequestedUser") + .WithMany() + .HasForeignKey("RequestedUserId"); + }); + modelBuilder.Entity("Ombi.Store.Entities.Requests.ChildRequests", b => { b.HasOne("Ombi.Store.Entities.Requests.TvRequests", "ParentRequest") @@ -945,14 +1165,28 @@ namespace Ombi.Store.Migrations .HasForeignKey("UserId"); }); - modelBuilder.Entity("Ombi.Store.Entities.RequestSubscription", b => + modelBuilder.Entity("Ombi.Store.Entities.Tokens", b => { b.HasOne("Ombi.Store.Entities.OmbiUser", "User") .WithMany() .HasForeignKey("UserId"); }); - modelBuilder.Entity("Ombi.Store.Entities.Tokens", b => + modelBuilder.Entity("Ombi.Store.Entities.UserNotificationPreferences", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany("UserNotificationPreferences") + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.UserQualityProfiles", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Votes", b => { b.HasOne("Ombi.Store.Entities.OmbiUser", "User") .WithMany() diff --git a/src/Ombi.Store/Migrations/Settings/20181004132516_Inital.Designer.cs b/src/Ombi.Store/Migrations/Settings/20181004132516_Inital.Designer.cs new file mode 100644 index 000000000..60b9f9adc --- /dev/null +++ b/src/Ombi.Store/Migrations/Settings/20181004132516_Inital.Designer.cs @@ -0,0 +1,50 @@ +// +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Ombi.Store.Context; + +namespace Ombi.Store.Migrations.Settings +{ + [DbContext(typeof(SettingsContext))] + [Migration("20181004132516_Inital")] + partial class Inital + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.1.3-rtm-32065"); + + modelBuilder.Entity("Ombi.Store.Entities.ApplicationConfiguration", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Type"); + + b.Property("Value"); + + b.HasKey("Id"); + + b.ToTable("ApplicationConfiguration"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.GlobalSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Content"); + + b.Property("SettingsName"); + + b.HasKey("Id"); + + b.ToTable("GlobalSettings"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Ombi.Store/Migrations/Settings/20181004132516_Inital.cs b/src/Ombi.Store/Migrations/Settings/20181004132516_Inital.cs new file mode 100644 index 000000000..956e0a14a --- /dev/null +++ b/src/Ombi.Store/Migrations/Settings/20181004132516_Inital.cs @@ -0,0 +1,47 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Ombi.Store.Migrations.Settings +{ + public partial class Inital : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "ApplicationConfiguration", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Type = table.Column(nullable: false), + Value = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_ApplicationConfiguration", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "GlobalSettings", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Content = table.Column(nullable: true), + SettingsName = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_GlobalSettings", x => x.Id); + }); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "ApplicationConfiguration"); + + migrationBuilder.DropTable( + name: "GlobalSettings"); + } + } +} diff --git a/src/Ombi.Store/Migrations/Settings/20190416204533_ResetSchedules.Designer.cs b/src/Ombi.Store/Migrations/Settings/20190416204533_ResetSchedules.Designer.cs new file mode 100644 index 000000000..205a98b11 --- /dev/null +++ b/src/Ombi.Store/Migrations/Settings/20190416204533_ResetSchedules.Designer.cs @@ -0,0 +1,50 @@ +// +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Ombi.Store.Context; + +namespace Ombi.Store.Migrations.Settings +{ + [DbContext(typeof(SettingsContext))] + [Migration("20190416204533_ResetSchedules")] + partial class ResetSchedules + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.2.2-servicing-10034"); + + modelBuilder.Entity("Ombi.Store.Entities.ApplicationConfiguration", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Type"); + + b.Property("Value"); + + b.HasKey("Id"); + + b.ToTable("ApplicationConfiguration"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.GlobalSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Content"); + + b.Property("SettingsName"); + + b.HasKey("Id"); + + b.ToTable("GlobalSettings"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Ombi.Store/Migrations/Settings/20190416204533_ResetSchedules.cs b/src/Ombi.Store/Migrations/Settings/20190416204533_ResetSchedules.cs new file mode 100644 index 000000000..0a3b9312c --- /dev/null +++ b/src/Ombi.Store/Migrations/Settings/20190416204533_ResetSchedules.cs @@ -0,0 +1,20 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Ombi.Store.Migrations.Settings +{ + public partial class ResetSchedules : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.Sql(@" + DELETE FROM GlobalSettings + WHERE SettingsName = 'JobSettings' +"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + + } + } +} diff --git a/src/Ombi.Store/Migrations/Settings/SettingsContextModelSnapshot.cs b/src/Ombi.Store/Migrations/Settings/SettingsContextModelSnapshot.cs new file mode 100644 index 000000000..7a605792d --- /dev/null +++ b/src/Ombi.Store/Migrations/Settings/SettingsContextModelSnapshot.cs @@ -0,0 +1,48 @@ +// +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Ombi.Store.Context; + +namespace Ombi.Store.Migrations.Settings +{ + [DbContext(typeof(SettingsContext))] + partial class SettingsContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.2.2-servicing-10034"); + + modelBuilder.Entity("Ombi.Store.Entities.ApplicationConfiguration", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Type"); + + b.Property("Value"); + + b.HasKey("Id"); + + b.ToTable("ApplicationConfiguration"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.GlobalSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Content"); + + b.Property("SettingsName"); + + b.HasKey("Id"); + + b.ToTable("GlobalSettings"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Ombi.Store/Ombi.Store.csproj b/src/Ombi.Store/Ombi.Store.csproj index 2ceb78424..dd13d5ea7 100644 --- a/src/Ombi.Store/Ombi.Store.csproj +++ b/src/Ombi.Store/Ombi.Store.csproj @@ -10,11 +10,13 @@ - - - - - + + + + + + + diff --git a/src/Ombi.Store/Repository/ApplicationConfigRepository.cs b/src/Ombi.Store/Repository/ApplicationConfigRepository.cs index 3b4476454..31ec3313c 100644 --- a/src/Ombi.Store/Repository/ApplicationConfigRepository.cs +++ b/src/Ombi.Store/Repository/ApplicationConfigRepository.cs @@ -8,16 +8,20 @@ namespace Ombi.Store.Repository { public class ApplicationConfigRepository : IApplicationConfigRepository { - public ApplicationConfigRepository(IOmbiContext ctx) + public ApplicationConfigRepository(ISettingsContext ctx) { Ctx = ctx; } - private IOmbiContext Ctx { get; } + private ISettingsContext Ctx { get; } - public async Task Get(ConfigurationTypes type) + public Task GetAsync(ConfigurationTypes type) { - return await Ctx.ApplicationConfigurations.FirstOrDefaultAsync(x => x.Type == type); + return Ctx.ApplicationConfigurations.FirstOrDefaultAsync(x => x.Type == type); + } + public ApplicationConfiguration Get(ConfigurationTypes type) + { + return Ctx.ApplicationConfigurations.FirstOrDefault(x => x.Type == type); } } } \ No newline at end of file diff --git a/src/Ombi.Store/Repository/AuditRepository.cs b/src/Ombi.Store/Repository/AuditRepository.cs index a6fc42c3f..858cd0eaf 100644 --- a/src/Ombi.Store/Repository/AuditRepository.cs +++ b/src/Ombi.Store/Repository/AuditRepository.cs @@ -24,16 +24,20 @@ namespace Ombi.Store.Repository public async Task Record(AuditType type, AuditArea area, string description, string user) { - await Ctx.Audit.AddAsync(new Audit + using (var tran = await Ctx.Database.BeginTransactionAsync()) { - User = user, - AuditArea = area, - AuditType = type, - DateTime = DateTime.UtcNow, - Description = description - }); + await Ctx.Audit.AddAsync(new Audit + { + User = user, + AuditArea = area, + AuditType = type, + DateTime = DateTime.UtcNow, + Description = description + }); - await Ctx.SaveChangesAsync(); + await Ctx.SaveChangesAsync(); + tran.Commit(); + } } } } diff --git a/src/Ombi.Store/Repository/BaseRepository.cs b/src/Ombi.Store/Repository/BaseRepository.cs new file mode 100644 index 000000000..14b871a12 --- /dev/null +++ b/src/Ombi.Store/Repository/BaseRepository.cs @@ -0,0 +1,131 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Threading.Tasks; +using Microsoft.Data.Sqlite; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Query; +using Ombi.Helpers; +using Ombi.Store.Context; +using Ombi.Store.Entities; +using Polly; + +namespace Ombi.Store.Repository +{ + public class BaseRepository : IRepository where T : Entity where U : IDbContext + { + public BaseRepository(U ctx) + { + _ctx = ctx; + _db = _ctx.Set(); + } + public DbSet _db { get; } + private readonly U _ctx; + + public async Task Find(object key) + { + return await _db.FindAsync(key); + } + + public IQueryable GetAll() + { + return _db.AsQueryable(); + } + + public async Task FirstOrDefaultAsync(Expression> predicate) + { + return await _db.FirstOrDefaultAsync(predicate); + } + + public async Task AddRange(IEnumerable content, bool save = true) + { + _db.AddRange(content); + if (save) + { + await InternalSaveChanges(); + } + } + + public async Task Add(T content) + { + await _db.AddAsync(content); + await InternalSaveChanges(); + return content; + } + + public async Task Delete(T request) + { + _db.Remove(request); + await InternalSaveChanges(); + } + + public async Task DeleteRange(IEnumerable req) + { + _db.RemoveRange(req); + await InternalSaveChanges(); + } + + public async Task SaveChangesAsync() + { + return await InternalSaveChanges(); + } + + public IIncludableQueryable Include( + IQueryable source, Expression> navigationPropertyPath) + where TEntity : class + { + return source.Include(navigationPropertyPath); + } + + public async Task ExecuteSql(string sql) + { + await _ctx.Database.ExecuteSqlCommandAsync(sql); + } + + protected async Task InternalSaveChanges() + { + var policy = Policy + .Handle() + .WaitAndRetryAsync(new[] + { + TimeSpan.FromSeconds(1), + TimeSpan.FromSeconds(5), + TimeSpan.FromSeconds(10) + }); + + var result = await policy.ExecuteAndCaptureAsync(async () => + { + using (var tran = await _ctx.Database.BeginTransactionAsync()) + { + var r = await _ctx.SaveChangesAsync(); + tran.Commit(); + return r; + } + }); + return result.Result; + } + + + private bool _disposed; + // Protected implementation of Dispose pattern. + protected virtual void Dispose(bool disposing) + { + if (_disposed) + return; + + if (disposing) + { + _ctx?.Dispose(); + } + + _disposed = true; + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + } +} \ No newline at end of file diff --git a/src/Ombi.Store/Repository/EmbyContentRepository.cs b/src/Ombi.Store/Repository/EmbyContentRepository.cs index c4377f929..2ada709ab 100644 --- a/src/Ombi.Store/Repository/EmbyContentRepository.cs +++ b/src/Ombi.Store/Repository/EmbyContentRepository.cs @@ -35,15 +35,15 @@ using Ombi.Store.Entities; namespace Ombi.Store.Repository { - public class EmbyContentRepository : Repository, IEmbyContentRepository + public class EmbyContentRepository : ExternalRepository, IEmbyContentRepository { - public EmbyContentRepository(IOmbiContext db):base(db) + public EmbyContentRepository(IExternalContext db):base(db) { Db = db; } - private IOmbiContext Db { get; } + private IExternalContext Db { get; } public async Task GetByImdbId(string imdbid) @@ -72,7 +72,7 @@ namespace Ombi.Store.Repository public async Task Update(EmbyContent existingContent) { Db.EmbyContent.Update(existingContent); - await Db.SaveChangesAsync(); + await InternalSaveChanges(); } public IQueryable GetAllEpisodes() @@ -83,7 +83,7 @@ namespace Ombi.Store.Repository public async Task Add(EmbyEpisode content) { await Db.EmbyEpisode.AddAsync(content); - await Db.SaveChangesAsync(); + await InternalSaveChanges(); return content; } public async Task GetEpisodeByEmbyId(string key) @@ -94,12 +94,13 @@ namespace Ombi.Store.Repository public async Task AddRange(IEnumerable content) { Db.EmbyEpisode.AddRange(content); - await Db.SaveChangesAsync(); + await InternalSaveChanges(); } public void UpdateWithoutSave(EmbyContent existingContent) { Db.EmbyContent.Update(existingContent); } + } } \ No newline at end of file diff --git a/src/Ombi.Store/Repository/ExternalRepository.cs b/src/Ombi.Store/Repository/ExternalRepository.cs new file mode 100644 index 000000000..d7494afd4 --- /dev/null +++ b/src/Ombi.Store/Repository/ExternalRepository.cs @@ -0,0 +1,12 @@ +using Ombi.Store.Context; +using Ombi.Store.Entities; + +namespace Ombi.Store.Repository +{ + public class ExternalRepository : BaseRepository, IExternalRepository where T : Entity + { + public ExternalRepository(IExternalContext ctx) : base(ctx) + { + } + } +} \ No newline at end of file diff --git a/src/Ombi.Store/Repository/IApplicationConfigRepository.cs b/src/Ombi.Store/Repository/IApplicationConfigRepository.cs index 9e35bde7e..6aa76e357 100644 --- a/src/Ombi.Store/Repository/IApplicationConfigRepository.cs +++ b/src/Ombi.Store/Repository/IApplicationConfigRepository.cs @@ -5,6 +5,7 @@ namespace Ombi.Store.Repository { public interface IApplicationConfigRepository { - Task Get(ConfigurationTypes type); + Task GetAsync(ConfigurationTypes type); + ApplicationConfiguration Get(ConfigurationTypes type); } } \ No newline at end of file diff --git a/src/Ombi.Store/Repository/IExternalRepository.cs b/src/Ombi.Store/Repository/IExternalRepository.cs new file mode 100644 index 000000000..de8b6db67 --- /dev/null +++ b/src/Ombi.Store/Repository/IExternalRepository.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Query; +using Ombi.Store.Entities; + +namespace Ombi.Store.Repository +{ + public interface IExternalRepository : IDisposable where T : Entity + { + Task Find(object key); + IQueryable GetAll(); + Task FirstOrDefaultAsync(Expression> predicate); + Task AddRange(IEnumerable content, bool save = true); + Task Add(T content); + Task DeleteRange(IEnumerable req); + Task Delete(T request); + Task SaveChangesAsync(); + + IIncludableQueryable Include( + IQueryable source, Expression> navigationPropertyPath) + where TEntity : class; + + Task ExecuteSql(string sql); + DbSet _db { get; } + } +} \ No newline at end of file diff --git a/src/Ombi.Store/Repository/IPlexContentRepository.cs b/src/Ombi.Store/Repository/IPlexContentRepository.cs index 381a89fa3..7bce2e75a 100644 --- a/src/Ombi.Store/Repository/IPlexContentRepository.cs +++ b/src/Ombi.Store/Repository/IPlexContentRepository.cs @@ -7,7 +7,7 @@ using Ombi.Store.Entities; namespace Ombi.Store.Repository { - public interface IPlexContentRepository : IRepository + public interface IPlexContentRepository : IExternalRepository { Task ContentExists(string providerId); Task Get(string providerId); diff --git a/src/Ombi.Store/Repository/NotificationTemplatesRepository.cs b/src/Ombi.Store/Repository/NotificationTemplatesRepository.cs index 175d0e6a9..8f3968dc0 100644 --- a/src/Ombi.Store/Repository/NotificationTemplatesRepository.cs +++ b/src/Ombi.Store/Repository/NotificationTemplatesRepository.cs @@ -45,7 +45,7 @@ namespace Ombi.Store.Repository Db.Attach(template); Db.Entry(template).State = EntityState.Modified; } - await Db.SaveChangesAsync(); + await InternalSaveChanges(); } public async Task UpdateRange(IEnumerable templates) @@ -56,16 +56,21 @@ namespace Ombi.Store.Repository Db.Attach(t); Db.Entry(t).State = EntityState.Modified; } - await Db.SaveChangesAsync(); + await InternalSaveChanges(); } public async Task Insert(NotificationTemplates entity) { var settings = await Db.NotificationTemplates.AddAsync(entity).ConfigureAwait(false); - await Db.SaveChangesAsync().ConfigureAwait(false); + await InternalSaveChanges().ConfigureAwait(false); return settings.Entity; } + private async Task InternalSaveChanges() + { + return await Db.SaveChangesAsync(); + } + private bool _disposed; // Protected implementation of Dispose pattern. protected virtual void Dispose(bool disposing) diff --git a/src/Ombi.Store/Repository/PlexContentRepository.cs b/src/Ombi.Store/Repository/PlexContentRepository.cs index e452eeb7d..37275a47c 100644 --- a/src/Ombi.Store/Repository/PlexContentRepository.cs +++ b/src/Ombi.Store/Repository/PlexContentRepository.cs @@ -36,15 +36,15 @@ using Ombi.Store.Entities; namespace Ombi.Store.Repository { - public class PlexServerContentRepository : Repository, IPlexContentRepository + public class PlexServerContentRepository : ExternalRepository, IPlexContentRepository { - public PlexServerContentRepository(IOmbiContext db) : base(db) + public PlexServerContentRepository(IExternalContext db) : base(db) { Db = db; } - private IOmbiContext Db { get; } + private IExternalContext Db { get; } public async Task ContentExists(string providerId) @@ -96,7 +96,7 @@ namespace Ombi.Store.Repository public async Task Update(PlexServerContent existingContent) { Db.PlexServerContent.Update(existingContent); - await Db.SaveChangesAsync(); + await InternalSaveChanges(); } public void UpdateWithoutSave(PlexServerContent existingContent) { @@ -106,7 +106,7 @@ namespace Ombi.Store.Repository public async Task UpdateRange(IEnumerable existingContent) { Db.PlexServerContent.UpdateRange(existingContent); - await Db.SaveChangesAsync(); + await InternalSaveChanges(); } public IQueryable GetAllEpisodes() @@ -127,14 +127,14 @@ namespace Ombi.Store.Repository public async Task Add(PlexEpisode content) { await Db.PlexEpisode.AddAsync(content); - await Db.SaveChangesAsync(); + await InternalSaveChanges(); return content; } public async Task DeleteEpisode(PlexEpisode content) { Db.PlexEpisode.Remove(content); - await Db.SaveChangesAsync(); + await InternalSaveChanges(); } public async Task GetEpisodeByKey(int key) @@ -144,7 +144,7 @@ namespace Ombi.Store.Repository public async Task AddRange(IEnumerable content) { Db.PlexEpisode.AddRange(content); - await Db.SaveChangesAsync(); + await InternalSaveChanges(); } } } \ No newline at end of file diff --git a/src/Ombi.Store/Repository/Repository.cs b/src/Ombi.Store/Repository/Repository.cs index 8c07c2371..9d49ded58 100644 --- a/src/Ombi.Store/Repository/Repository.cs +++ b/src/Ombi.Store/Repository/Repository.cs @@ -1,105 +1,12 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Linq.Expressions; -using System.Threading.Tasks; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Query; -using Ombi.Store.Context; +using Ombi.Store.Context; using Ombi.Store.Entities; namespace Ombi.Store.Repository { - public class Repository : IRepository where T : Entity + public class Repository : BaseRepository, IRepository where T : Entity { - public Repository(IOmbiContext ctx) + public Repository(IOmbiContext ctx) : base(ctx) { - _ctx = ctx; - _db = _ctx.Set(); - } - public DbSet _db { get; } - private readonly IOmbiContext _ctx; - - public async Task Find(object key) - { - return await _db.FindAsync(key); - } - - public IQueryable GetAll() - { - return _db.AsQueryable(); - } - - public async Task FirstOrDefaultAsync(Expression> predicate) - { - return await _db.FirstOrDefaultAsync(predicate); - } - - public async Task AddRange(IEnumerable content, bool save = true) - { - _db.AddRange(content); - if (save) - { - await _ctx.SaveChangesAsync(); - } - } - - public async Task Add(T content) - { - await _db.AddAsync(content); - await _ctx.SaveChangesAsync(); - return content; - } - - public async Task Delete(T request) - { - _db.Remove(request); - await _ctx.SaveChangesAsync(); - } - - public async Task DeleteRange(IEnumerable req) - { - _db.RemoveRange(req); - await _ctx.SaveChangesAsync(); - } - - public async Task SaveChangesAsync() - { - return await _ctx.SaveChangesAsync(); - } - - public IIncludableQueryable Include( - IQueryable source, Expression> navigationPropertyPath) - where TEntity : class - { - return source.Include(navigationPropertyPath); - } - - public async Task ExecuteSql(string sql) - { - await _ctx.Database.ExecuteSqlCommandAsync(sql); - } - - - private bool _disposed; - // Protected implementation of Dispose pattern. - protected virtual void Dispose(bool disposing) - { - if (_disposed) - return; - - if (disposing) - { - _ctx?.Dispose(); - } - - _disposed = true; - } - - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); } } } \ No newline at end of file diff --git a/src/Ombi.Store/Repository/Requests/IMusicRequestRepository.cs b/src/Ombi.Store/Repository/Requests/IMusicRequestRepository.cs new file mode 100644 index 000000000..28cb0b2f9 --- /dev/null +++ b/src/Ombi.Store/Repository/Requests/IMusicRequestRepository.cs @@ -0,0 +1,17 @@ +using System.Linq; +using System.Threading.Tasks; +using Ombi.Store.Entities.Requests; + +namespace Ombi.Store.Repository.Requests +{ + public interface IMusicRequestRepository : IRepository + { + IQueryable GetAll(string userId); + AlbumRequest GetRequest(string foreignAlbumId); + Task GetRequestAsync(string foreignAlbumId); + IQueryable GetWithUser(); + IQueryable GetWithUser(string userId); + Task Save(); + Task Update(AlbumRequest request); + } +} \ No newline at end of file diff --git a/src/Ombi.Store/Repository/Requests/MovieRequestRepository.cs b/src/Ombi.Store/Repository/Requests/MovieRequestRepository.cs index d4a550528..2cea81200 100644 --- a/src/Ombi.Store/Repository/Requests/MovieRequestRepository.cs +++ b/src/Ombi.Store/Repository/Requests/MovieRequestRepository.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; +using Ombi.Helpers; using Ombi.Store.Context; using Ombi.Store.Entities.Requests; @@ -70,12 +71,12 @@ namespace Ombi.Store.Repository.Requests Db.MovieRequests.Attach(request); Db.Update(request); } - await Db.SaveChangesAsync(); + await InternalSaveChanges(); } public async Task Save() { - await Db.SaveChangesAsync(); + await InternalSaveChanges(); } } } \ No newline at end of file diff --git a/src/Ombi.Store/Repository/Requests/MusicRequestRepository.cs b/src/Ombi.Store/Repository/Requests/MusicRequestRepository.cs new file mode 100644 index 000000000..971d53b39 --- /dev/null +++ b/src/Ombi.Store/Repository/Requests/MusicRequestRepository.cs @@ -0,0 +1,73 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Ombi.Helpers; +using Ombi.Store.Context; +using Ombi.Store.Entities.Requests; + +namespace Ombi.Store.Repository.Requests +{ + public class MusicRequestRepository : Repository, IMusicRequestRepository + { + public MusicRequestRepository(IOmbiContext ctx) : base(ctx) + { + Db = ctx; + } + + private IOmbiContext Db { get; } + + public Task GetRequestAsync(string foreignAlbumId) + { + return Db.AlbumRequests.Where(x => x.ForeignAlbumId == foreignAlbumId) + .Include(x => x.RequestedUser) + .FirstOrDefaultAsync(); + } + + public IQueryable GetAll(string userId) + { + return GetWithUser().Where(x => x.RequestedUserId == userId); + } + + public AlbumRequest GetRequest(string foreignAlbumId) + { + return Db.AlbumRequests.Where(x => x.ForeignAlbumId == foreignAlbumId) + .Include(x => x.RequestedUser) + .FirstOrDefault(); + } + + public IQueryable GetWithUser() + { + return Db.AlbumRequests + .Include(x => x.RequestedUser) + .ThenInclude(x => x.NotificationUserIds) + .AsQueryable(); + } + + + public IQueryable GetWithUser(string userId) + { + return Db.AlbumRequests + .Where(x => x.RequestedUserId == userId) + .Include(x => x.RequestedUser) + .ThenInclude(x => x.NotificationUserIds) + .AsQueryable(); + } + + public async Task Update(AlbumRequest request) + { + if (Db.Entry(request).State == EntityState.Detached) + { + Db.AlbumRequests.Attach(request); + Db.Update(request); + } + await InternalSaveChanges(); + } + + public async Task Save() + { + await InternalSaveChanges(); + } + } +} \ No newline at end of file diff --git a/src/Ombi.Store/Repository/Requests/TvRequestRepository.cs b/src/Ombi.Store/Repository/Requests/TvRequestRepository.cs index daac7d4df..6528f0969 100644 --- a/src/Ombi.Store/Repository/Requests/TvRequestRepository.cs +++ b/src/Ombi.Store/Repository/Requests/TvRequestRepository.cs @@ -2,14 +2,15 @@ using System.Linq; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; +using Ombi.Helpers; using Ombi.Store.Context; using Ombi.Store.Entities.Requests; namespace Ombi.Store.Repository.Requests { - public class TvRequestRepository : ITvRequestRepository + public class TvRequestRepository : BaseRepository, ITvRequestRepository { - public TvRequestRepository(IOmbiContext ctx) + public TvRequestRepository(IOmbiContext ctx) : base(ctx) { Db = ctx; } @@ -101,20 +102,20 @@ namespace Ombi.Store.Repository.Requests public async Task Save() { - await Db.SaveChangesAsync(); + await InternalSaveChanges(); } public async Task Add(TvRequests request) { await Db.TvRequests.AddAsync(request); - await Db.SaveChangesAsync(); + await InternalSaveChanges(); return request; } public async Task AddChild(ChildRequests request) { await Db.ChildRequests.AddAsync(request); - await Db.SaveChangesAsync(); + await InternalSaveChanges(); return request; } @@ -122,33 +123,33 @@ namespace Ombi.Store.Repository.Requests public async Task Delete(TvRequests request) { Db.TvRequests.Remove(request); - await Db.SaveChangesAsync(); + await InternalSaveChanges(); } public async Task DeleteChild(ChildRequests request) { Db.ChildRequests.Remove(request); - await Db.SaveChangesAsync(); + await InternalSaveChanges(); } public async Task DeleteChildRange(IEnumerable request) { Db.ChildRequests.RemoveRange(request); - await Db.SaveChangesAsync(); + await InternalSaveChanges(); } public async Task Update(TvRequests request) { Db.Update(request); - await Db.SaveChangesAsync(); + await InternalSaveChanges(); } public async Task UpdateChild(ChildRequests request) { Db.Update(request); - await Db.SaveChangesAsync(); + await InternalSaveChanges(); } } } \ No newline at end of file diff --git a/src/Ombi.Store/Repository/SettingsJsonRepository.cs b/src/Ombi.Store/Repository/SettingsJsonRepository.cs index 248413ccc..622e32997 100644 --- a/src/Ombi.Store/Repository/SettingsJsonRepository.cs +++ b/src/Ombi.Store/Repository/SettingsJsonRepository.cs @@ -12,29 +12,40 @@ namespace Ombi.Store.Repository { public class SettingsJsonRepository : ISettingsRepository { - public SettingsJsonRepository(IOmbiContext ctx, ICacheService mem) + public SettingsJsonRepository(ISettingsContext ctx, ICacheService mem) { Db = ctx; _cache = mem; } - private IOmbiContext Db { get; } + private ISettingsContext Db { get; } private readonly ICacheService _cache; public GlobalSettings Insert(GlobalSettings entity) { //_cache.Remove(GetName(entity.SettingsName)); - var settings = Db.Settings.Add(entity); - Db.SaveChanges(); - return settings.Entity; + + using (var tran = Db.Database.BeginTransaction()) + { + var settings = Db.Settings.Add(entity); + Db.SaveChanges(); + tran.Commit(); + return settings.Entity; + } } public async Task InsertAsync(GlobalSettings entity) { - //_cache.Remove(GetName(entity.SettingsName)); - var settings = await Db.Settings.AddAsync(entity).ConfigureAwait(false); - await Db.SaveChangesAsync().ConfigureAwait(false); - return settings.Entity; + + using (var tran = Db.Database.BeginTransaction()) + { + //_cache.Remove(GetName(entity.SettingsName)); + var settings = await Db.Settings.AddAsync(entity); + await Db.SaveChangesAsync(); + tran.Commit(); + + return settings.Entity; + } } @@ -43,8 +54,8 @@ namespace Ombi.Store.Repository //return _cache.GetOrCreate(GetName(pageName), entry => //{ // entry.AbsoluteExpiration = DateTimeOffset.Now.AddHours(1); - var entity = Db.Settings.AsNoTracking().FirstOrDefault(x => x.SettingsName == pageName); - return entity; + var entity = Db.Settings.AsNoTracking().FirstOrDefault(x => x.SettingsName == pageName); + return entity; //}); } @@ -52,9 +63,9 @@ namespace Ombi.Store.Repository { //return await _cache.GetOrCreateAsync(GetName(settingsName), async entry => //{ - //entry.AbsoluteExpiration = DateTimeOffset.Now.AddHours(1); - var obj = await Db.Settings.AsNoTracking().FirstOrDefaultAsync(x => x.SettingsName == settingsName); - return obj; + //entry.AbsoluteExpiration = DateTimeOffset.Now.AddHours(1); + var obj = await Db.Settings.AsNoTracking().FirstOrDefaultAsync(x => x.SettingsName == settingsName); + return obj; //}); } @@ -62,28 +73,37 @@ namespace Ombi.Store.Repository { //_cache.Remove(GetName(entity.SettingsName)); Db.Settings.Remove(entity); - await Db.SaveChangesAsync(); + await InternalSaveChanges(); } public async Task UpdateAsync(GlobalSettings entity) { //_cache.Remove(GetName(entity.SettingsName)); Db.Update(entity); - await Db.SaveChangesAsync(); + await InternalSaveChanges(); } public void Delete(GlobalSettings entity) { //_cache.Remove(GetName(entity.SettingsName)); - Db.Settings.Remove(entity); - Db.SaveChanges(); + + using (var tran = Db.Database.BeginTransaction()) + { + Db.Settings.Remove(entity); + Db.SaveChanges(); + tran.Commit(); + } } public void Update(GlobalSettings entity) { - Db.Update(entity); - //_cache.Remove(GetName(entity.SettingsName)); - Db.SaveChanges(); + using (var tran = Db.Database.BeginTransaction()) + { + Db.Update(entity); + //_cache.Remove(GetName(entity.SettingsName)); + Db.SaveChanges(); + tran.Commit(); + } } private string GetName(string entity) @@ -91,6 +111,17 @@ namespace Ombi.Store.Repository return $"{entity}Json"; } + private async Task InternalSaveChanges() + { + + using (var tran = Db.Database.BeginTransaction()) + { + var r = await Db.SaveChangesAsync(); + tran.Commit(); + return r; + } + } + private bool _disposed; protected virtual void Dispose(bool disposing) { diff --git a/src/Ombi.Store/Repository/TokenRepository.cs b/src/Ombi.Store/Repository/TokenRepository.cs index d766c5690..4e35b0aa8 100644 --- a/src/Ombi.Store/Repository/TokenRepository.cs +++ b/src/Ombi.Store/Repository/TokenRepository.cs @@ -4,12 +4,13 @@ using Ombi.Store.Entities; using System; using System.Linq; using System.Threading.Tasks; +using Ombi.Helpers; namespace Ombi.Store.Repository { - public class TokenRepository : ITokenRepository + public class TokenRepository : BaseRepository, ITokenRepository { - public TokenRepository(IOmbiContext db) + public TokenRepository(IOmbiContext db) : base(db) { Db = db; } @@ -19,7 +20,7 @@ namespace Ombi.Store.Repository public async Task CreateToken(Tokens token) { await Db.Tokens.AddAsync(token); - await Db.SaveChangesAsync(); + await InternalSaveChanges(); } public IQueryable GetToken(string tokenId) diff --git a/src/Ombi.Tests/Ombi.Tests.csproj b/src/Ombi.Tests/Ombi.Tests.csproj index 95eb09353..fdfec1cb2 100644 --- a/src/Ombi.Tests/Ombi.Tests.csproj +++ b/src/Ombi.Tests/Ombi.Tests.csproj @@ -1,18 +1,18 @@ - netcoreapp2.1 + netcoreapp2.2 false - - - - - - + + + + + + diff --git a/src/Ombi.Tests/TestStartup.cs b/src/Ombi.Tests/TestStartup.cs index fad346df0..07d54a936 100644 --- a/src/Ombi.Tests/TestStartup.cs +++ b/src/Ombi.Tests/TestStartup.cs @@ -1,71 +1,71 @@ -using System; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Http.Features.Authentication; -using Microsoft.AspNetCore.Identity; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Options; -using Moq; -using Ombi.Api.Emby; -using Ombi.Api.Plex; -using Ombi.Core.Authentication; -using Ombi.Core.Settings; -using Ombi.Core.Settings.Models.External; -using Ombi.Models.Identity; -using Ombi.Store.Context; -using Ombi.Store.Entities; -using Ombi.Store.Repository; +//using System; +//using Microsoft.AspNetCore.Builder; +//using Microsoft.AspNetCore.Hosting; +//using Microsoft.AspNetCore.Http; +//using Microsoft.AspNetCore.Http.Features.Authentication; +//using Microsoft.AspNetCore.Identity; +//using Microsoft.Extensions.DependencyInjection; +//using Microsoft.Extensions.Options; +//using Moq; +//using Ombi.Api.Emby; +//using Ombi.Api.Plex; +//using Ombi.Core.Authentication; +//using Ombi.Core.Settings; +//using Ombi.Core.Settings.Models.External; +//using Ombi.Models.Identity; +//using Ombi.Store.Context; +//using Ombi.Store.Entities; +//using Ombi.Store.Repository; -namespace Ombi.Tests -{ - public class TestStartup - { - public IServiceProvider ConfigureServices(IServiceCollection services) - { - var _plexApi = new Mock(); - var _embyApi = new Mock(); - var _tokenSettings = new Mock>(); - var _embySettings = new Mock>(); - var _plexSettings = new Mock>(); - var audit = new Mock(); - var tokenRepo = new Mock(); +//namespace Ombi.Tests +//{ +// public class TestStartup +// { +// public IServiceProvider ConfigureServices(IServiceCollection services) +// { +// var _plexApi = new Mock(); +// var _embyApi = new Mock(); +// var _tokenSettings = new Mock>(); +// var _embySettings = new Mock>(); +// var _plexSettings = new Mock>(); +// var audit = new Mock(); +// var tokenRepo = new Mock(); - services.AddEntityFrameworkInMemoryDatabase() - .AddDbContext(); - services.AddIdentity() - .AddEntityFrameworkStores().AddUserManager(); +// services.AddEntityFrameworkInMemoryDatabase() +// .AddDbContext(); +// services.AddIdentity() +// .AddEntityFrameworkStores().AddUserManager(); - services.AddTransient(x => _plexApi.Object); - services.AddTransient(x => _embyApi.Object); - services.AddTransient(x => _tokenSettings.Object); - services.AddTransient(x => _embySettings.Object); - services.AddTransient(x => _plexSettings.Object); - services.AddTransient(x => audit.Object); - services.AddTransient(x => tokenRepo.Object); - // Taken from https://github.com/aspnet/MusicStore/blob/dev/test/MusicStore.Test/ManageControllerTest.cs (and modified) - var context = new DefaultHttpContext(); - context.Features.Set(new HttpAuthenticationFeature()); - services.AddSingleton(h => new HttpContextAccessor { HttpContext = context }); +// services.AddTransient(x => _plexApi.Object); +// services.AddTransient(x => _embyApi.Object); +// services.AddTransient(x => _tokenSettings.Object); +// services.AddTransient(x => _embySettings.Object); +// services.AddTransient(x => _plexSettings.Object); +// services.AddTransient(x => audit.Object); +// services.AddTransient(x => tokenRepo.Object); +// // Taken from https://github.com/aspnet/MusicStore/blob/dev/test/MusicStore.Test/ManageControllerTest.cs (and modified) +// var context = new DefaultHttpContext(); +// context.Features.Set(new HttpAuthenticationFeature()); +// services.AddSingleton(h => new HttpContextAccessor { HttpContext = context }); - services.Configure(options => - { - options.Password.RequireDigit = false; - options.Password.RequiredLength = 1; - options.Password.RequireLowercase = false; - options.Password.RequireNonAlphanumeric = false; - options.Password.RequireUppercase = false; - options.User.AllowedUserNameCharacters = string.Empty; - }); +// services.Configure(options => +// { +// options.Password.RequireDigit = false; +// options.Password.RequiredLength = 1; +// options.Password.RequireLowercase = false; +// options.Password.RequireNonAlphanumeric = false; +// options.Password.RequireUppercase = false; +// options.User.AllowedUserNameCharacters = string.Empty; +// }); - return services.BuildServiceProvider(); +// return services.BuildServiceProvider(); - } +// } - public void Configure(IApplicationBuilder app, IHostingEnvironment env) - { +// public void Configure(IApplicationBuilder app, IHostingEnvironment env) +// { - } - } -} \ No newline at end of file +// } +// } +//} \ No newline at end of file diff --git a/src/Ombi.Tests/TokenControllerTests.cs b/src/Ombi.Tests/TokenControllerTests.cs index ec0e616ac..0fb1e9200 100644 --- a/src/Ombi.Tests/TokenControllerTests.cs +++ b/src/Ombi.Tests/TokenControllerTests.cs @@ -1,60 +1,60 @@ -using System.Net.Http; -using System.Threading.Tasks; -using AutoMapper; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Http.Features.Authentication; -using Microsoft.AspNetCore.Identity; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Options; -using Moq; -using NUnit.Framework; -using Ombi.Api.Emby; -using Ombi.Api.Plex; -using Ombi.Controllers; -using Ombi.Core.Authentication; -using Ombi.Core.Settings; -using Ombi.Core.Settings.Models.External; -using Ombi.Models.Identity; -using Ombi.Notifications; -using Ombi.Schedule.Jobs.Ombi; -using Ombi.Settings.Settings.Models; -using Ombi.Settings.Settings.Models.Notifications; -using Ombi.Store.Context; -using Ombi.Store.Entities; -using Ombi.Store.Repository; -using Microsoft.AspNetCore.Hosting.Server; -using Microsoft.AspNetCore.TestHost; -using Newtonsoft.Json; -using Ombi.Models; +//using System.Net.Http; +//using System.Threading.Tasks; +//using AutoMapper; +//using Microsoft.AspNetCore.Hosting; +//using Microsoft.AspNetCore.Http; +//using Microsoft.AspNetCore.Http.Features.Authentication; +//using Microsoft.AspNetCore.Identity; +//using Microsoft.Extensions.DependencyInjection; +//using Microsoft.Extensions.Options; +//using Moq; +//using NUnit.Framework; +//using Ombi.Api.Emby; +//using Ombi.Api.Plex; +//using Ombi.Controllers; +//using Ombi.Core.Authentication; +//using Ombi.Core.Settings; +//using Ombi.Core.Settings.Models.External; +//using Ombi.Models.Identity; +//using Ombi.Notifications; +//using Ombi.Schedule.Jobs.Ombi; +//using Ombi.Settings.Settings.Models; +//using Ombi.Settings.Settings.Models.Notifications; +//using Ombi.Store.Context; +//using Ombi.Store.Entities; +//using Ombi.Store.Repository; +//using Microsoft.AspNetCore.Hosting.Server; +//using Microsoft.AspNetCore.TestHost; +//using Newtonsoft.Json; +//using Ombi.Models; -namespace Ombi.Tests -{ - [TestFixture] - [Ignore("TODO")] - public class TokenControllerTests - { - [SetUp] - public void Setup() - { - _testServer = new TestServer(new WebHostBuilder() - .UseStartup()); - _client = _testServer.CreateClient(); - } +//namespace Ombi.Tests +//{ +// [TestFixture] +// [Ignore("TODO")] +// public class TokenControllerTests +// { +// [SetUp] +// public void Setup() +// { +// _testServer = new TestServer(new WebHostBuilder() +// .UseStartup()); +// _client = _testServer.CreateClient(); +// } - private TestServer _testServer; - private HttpClient _client; +// private TestServer _testServer; +// private HttpClient _client; - [Test] - public async Task GetToken_FromValid_LocalUser() - { - var model = new UserAuthModel - { - Password = "a", - Username = "a" - }; - HttpResponseMessage response = await _client.PostAsync("/api/v1/token", new StringContent(JsonConvert.SerializeObject(model)) ); - } - } -} \ No newline at end of file +// [Test] +// public async Task GetToken_FromValid_LocalUser() +// { +// var model = new UserAuthModel +// { +// Password = "a", +// Username = "a" +// }; +// HttpResponseMessage response = await _client.PostAsync("/api/v1/token", new StringContent(JsonConvert.SerializeObject(model)) ); +// } +// } +//} \ No newline at end of file diff --git a/src/Ombi.TheMovieDbApi/IMovieDbApi.cs b/src/Ombi.TheMovieDbApi/IMovieDbApi.cs index 0b24bd55c..43d8b02c1 100644 --- a/src/Ombi.TheMovieDbApi/IMovieDbApi.cs +++ b/src/Ombi.TheMovieDbApi/IMovieDbApi.cs @@ -8,16 +8,18 @@ namespace Ombi.Api.TheMovieDb public interface IMovieDbApi { Task GetMovieInformation(int movieId); - Task GetMovieInformationWithExtraInfo(int movieId); - Task> NowPlaying(); - Task> PopularMovies(); - Task> SearchMovie(string searchTerm); + Task GetMovieInformationWithExtraInfo(int movieId, string langCode = "en"); + Task> NowPlaying(string languageCode); + Task> PopularMovies(string languageCode); + Task> SearchMovie(string searchTerm, int? year, string languageCode); Task> SearchTv(string searchTerm); - Task> TopRated(); - Task> Upcoming(); - Task> SimilarMovies(int movieId); + Task> TopRated(string languageCode); + Task> Upcoming(string languageCode); + Task> SimilarMovies(int movieId, string langCode); Task Find(string externalId, ExternalSource source); Task GetTvExternals(int theMovieDbId); Task GetTVInfo(string themoviedbid); + Task> SearchByActor(string searchTerm, string langCode); + Task GetActorMovieCredits(int actorId, string langCode); } } \ No newline at end of file diff --git a/src/Ombi.TheMovieDbApi/Models/ActorCredits.cs b/src/Ombi.TheMovieDbApi/Models/ActorCredits.cs new file mode 100644 index 000000000..cb27bc677 --- /dev/null +++ b/src/Ombi.TheMovieDbApi/Models/ActorCredits.cs @@ -0,0 +1,51 @@ +namespace Ombi.Api.TheMovieDb.Models +{ + public class ActorCredits + { + public Cast[] cast { get; set; } + public Crew[] crew { get; set; } + public int id { get; set; } + } + + public class Cast + { + public string character { get; set; } + public string credit_id { get; set; } + public string poster_path { get; set; } + public int id { get; set; } + public bool video { get; set; } + public int vote_count { get; set; } + public bool adult { get; set; } + public string backdrop_path { get; set; } + public int?[] genre_ids { get; set; } + public string original_language { get; set; } + public string original_title { get; set; } + public float popularity { get; set; } + public string title { get; set; } + public float vote_average { get; set; } + public string overview { get; set; } + public string release_date { get; set; } + } + + public class Crew + { + public int id { get; set; } + public string department { get; set; } + public string original_language { get; set; } + public string original_title { get; set; } + public string job { get; set; } + public string overview { get; set; } + public int vote_count { get; set; } + public bool video { get; set; } + public string release_date { get; set; } + public float vote_average { get; set; } + public string title { get; set; } + public float popularity { get; set; } + public int?[] genre_ids { get; set; } + public string backdrop_path { get; set; } + public bool adult { get; set; } + public string poster_path { get; set; } + public string credit_id { get; set; } + } + +} \ No newline at end of file diff --git a/src/Ombi.TheMovieDbApi/Models/ActorSearchResult.cs b/src/Ombi.TheMovieDbApi/Models/ActorSearchResult.cs new file mode 100644 index 000000000..b16b35ab9 --- /dev/null +++ b/src/Ombi.TheMovieDbApi/Models/ActorSearchResult.cs @@ -0,0 +1,33 @@ +namespace Ombi.Api.TheMovieDb.Models +{ + + public class ActorResult + { + public float popularity { get; set; } + public int id { get; set; } + public string profile_path { get; set; } + public string name { get; set; } + public Known_For[] known_for { get; set; } + public bool adult { get; set; } + } + + public class Known_For + { + public float vote_average { get; set; } + public int vote_count { get; set; } + public int id { get; set; } + public bool video { get; set; } + public string media_type { get; set; } + public string title { get; set; } + public float popularity { get; set; } + public string poster_path { get; set; } + public string original_language { get; set; } + public string original_title { get; set; } + public int[] genre_ids { get; set; } + public string backdrop_path { get; set; } + public bool adult { get; set; } + public string overview { get; set; } + public string release_date { get; set; } + } + +} \ No newline at end of file diff --git a/src/Ombi.TheMovieDbApi/TheMovieDbApi.cs b/src/Ombi.TheMovieDbApi/TheMovieDbApi.cs index af9423f5d..79ccc5bb7 100644 --- a/src/Ombi.TheMovieDbApi/TheMovieDbApi.cs +++ b/src/Ombi.TheMovieDbApi/TheMovieDbApi.cs @@ -43,6 +43,27 @@ namespace Ombi.Api.TheMovieDb return await Api.Request(request); } + public async Task> SearchByActor(string searchTerm, string langCode) + { + var request = new Request($"search/person", BaseUri, HttpMethod.Get); + request.FullUri = request.FullUri.AddQueryParameter("api_key", ApiToken); + request.FullUri = request.FullUri.AddQueryParameter("query", searchTerm); + request.FullUri = request.FullUri.AddQueryParameter("language", langCode); + + var result = await Api.Request>(request); + return result; + } + + public async Task GetActorMovieCredits(int actorId, string langCode) + { + var request = new Request($"person/{actorId}/movie_credits", BaseUri, HttpMethod.Get); + request.FullUri = request.FullUri.AddQueryParameter("api_key", ApiToken); + request.FullUri = request.FullUri.AddQueryParameter("language", langCode); + + var result = await Api.Request(request); + return result; + } + public async Task> SearchTv(string searchTerm) { var request = new Request($"search/tv", BaseUri, HttpMethod.Get); @@ -63,68 +84,80 @@ namespace Ombi.Api.TheMovieDb return await Api.Request(request); } - public async Task> SimilarMovies(int movieId) + public async Task> SimilarMovies(int movieId, string langCode) { var request = new Request($"movie/{movieId}/similar", BaseUri, HttpMethod.Get); request.FullUri = request.FullUri.AddQueryParameter("api_key", ApiToken); + + request.FullUri = request.FullUri.AddQueryParameter("language", langCode); AddRetry(request); var result = await Api.Request>(request); return Mapper.Map>(result.results); } - public async Task GetMovieInformationWithExtraInfo(int movieId) + public async Task GetMovieInformationWithExtraInfo(int movieId, string langCode = "en") { var request = new Request($"movie/{movieId}", BaseUri, HttpMethod.Get); request.FullUri = request.FullUri.AddQueryParameter("api_key", ApiToken); request.FullUri = request.FullUri.AddQueryParameter("append_to_response", "videos,release_dates"); + request.FullUri = request.FullUri.AddQueryParameter("language", langCode); AddRetry(request); var result = await Api.Request(request); return Mapper.Map(result); } - public async Task> SearchMovie(string searchTerm) + public async Task> SearchMovie(string searchTerm, int? year, string langageCode) { var request = new Request($"search/movie", BaseUri, HttpMethod.Get); request.FullUri = request.FullUri.AddQueryParameter("api_key", ApiToken); request.FullUri = request.FullUri.AddQueryParameter("query", searchTerm); + request.FullUri = request.FullUri.AddQueryParameter("language", langageCode); + if (year.HasValue && year.Value > 0) + { + request.FullUri = request.FullUri.AddQueryParameter("year", year.Value.ToString()); + } AddRetry(request); var result = await Api.Request>(request); return Mapper.Map>(result.results); } - public async Task> PopularMovies() + public async Task> PopularMovies(string langageCode) { var request = new Request($"movie/popular", BaseUri, HttpMethod.Get); request.FullUri = request.FullUri.AddQueryParameter("api_key", ApiToken); + request.FullUri = request.FullUri.AddQueryParameter("language", langageCode); AddRetry(request); var result = await Api.Request>(request); return Mapper.Map>(result.results); } - public async Task> TopRated() + public async Task> TopRated(string langageCode) { var request = new Request($"movie/top_rated", BaseUri, HttpMethod.Get); request.FullUri = request.FullUri.AddQueryParameter("api_key", ApiToken); + request.FullUri = request.FullUri.AddQueryParameter("language", langageCode); AddRetry(request); var result = await Api.Request>(request); return Mapper.Map>(result.results); } - public async Task> Upcoming() + public async Task> Upcoming(string langageCode) { var request = new Request($"movie/upcoming", BaseUri, HttpMethod.Get); request.FullUri = request.FullUri.AddQueryParameter("api_key", ApiToken); + request.FullUri = request.FullUri.AddQueryParameter("language", langageCode); AddRetry(request); var result = await Api.Request>(request); return Mapper.Map>(result.results); } - public async Task> NowPlaying() + public async Task> NowPlaying(string langageCode) { var request = new Request($"movie/now_playing", BaseUri, HttpMethod.Get); request.FullUri = request.FullUri.AddQueryParameter("api_key", ApiToken); + request.FullUri = request.FullUri.AddQueryParameter("language", langageCode); AddRetry(request); var result = await Api.Request>(request); return Mapper.Map>(result.results); diff --git a/src/Ombi.Updater/IProcessProvider.cs b/src/Ombi.Updater/IProcessProvider.cs index bc81ceb7c..68052c912 100644 --- a/src/Ombi.Updater/IProcessProvider.cs +++ b/src/Ombi.Updater/IProcessProvider.cs @@ -11,7 +11,7 @@ namespace Ombi.Updater ProcessInfo GetCurrentProcess(); int GetCurrentProcessId(); ProcessInfo GetProcessById(int id); - void Kill(StartupOptions opts); + bool Kill(StartupOptions opts); void KillAll(string processName); void SetPriority(int processId, ProcessPriorityClass priority); void WaitForExit(Process process); diff --git a/src/Ombi.Updater/Installer.cs b/src/Ombi.Updater/Installer.cs index 4827d45f9..10d574a10 100644 --- a/src/Ombi.Updater/Installer.cs +++ b/src/Ombi.Updater/Installer.cs @@ -22,29 +22,23 @@ namespace Ombi.Updater { // Kill Ombi Process var p = new ProcessProvider(); + bool killed = false; try { - p.Kill(opt); + killed = p.Kill(opt); } catch (Exception e) { Console.WriteLine(e); } - // Make sure the process has been killed - while (p.FindProcessByName(opt.ProcessName).Any()) + if (!killed) { - Thread.Sleep(500); - _log.LogDebug("Found another process called {0}, KILLING!", opt.ProcessName); - var proc = p.FindProcessByName(opt.ProcessName).FirstOrDefault(); - if (proc != null) - { - _log.LogDebug($"[{proc.Id}] - {proc.Name} - Path: {proc.StartPath}"); - opt.OmbiProcessId = proc.Id; - p.Kill(opt); - } + + _log.LogDebug("Couldn't kill the ombi process"); + return; } _log.LogDebug("Starting to move the files"); @@ -56,6 +50,7 @@ namespace Ombi.Updater private void StartOmbi(StartupOptions options) { + var startupArgsBuilder = new StringBuilder(); _log.LogDebug("Starting ombi"); var fileName = "Ombi.exe"; if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) @@ -79,7 +74,6 @@ namespace Ombi.Updater } else { - var startupArgsBuilder = new StringBuilder(); if (!string.IsNullOrEmpty(options.Host)) { startupArgsBuilder.Append($"--host {options.Host} "); @@ -102,7 +96,10 @@ namespace Ombi.Updater } } - _log.LogDebug("Ombi started, now exiting"); + _log.LogDebug($"Ombi started, now exiting"); + _log.LogDebug($"Working dir: {options.ApplicationPath} (Application Path)"); + _log.LogDebug($"Filename: {Path.Combine(options.ApplicationPath, fileName)}"); + _log.LogDebug($"Startup Args: {startupArgsBuilder.ToString()}"); Environment.Exit(0); } @@ -111,21 +108,23 @@ namespace Ombi.Updater var location = System.Reflection.Assembly.GetEntryAssembly().Location; location = Path.GetDirectoryName(location); _log.LogDebug("We are currently in dir {0}", location); + var updatedLocation = Directory.GetParent(location).FullName; + _log.LogDebug("The files are in {0}", updatedLocation); // Since the updater is a folder deeper _log.LogDebug("Ombi is installed at {0}", options.ApplicationPath); //Now Create all of the directories - foreach (string dirPath in Directory.GetDirectories(location, "*", + foreach (string dirPath in Directory.GetDirectories(updatedLocation, "*", SearchOption.AllDirectories)) { - var newDir = dirPath.Replace(location, options.ApplicationPath); + var newDir = dirPath.Replace(updatedLocation, options.ApplicationPath); Directory.CreateDirectory(newDir); _log.LogDebug("Created dir {0}", newDir); } //Copy all the files & Replaces any files with the same name - foreach (string currentPath in Directory.GetFiles(location, "*.*", + foreach (string currentPath in Directory.GetFiles(updatedLocation, "*.*", SearchOption.AllDirectories)) { - var newFile = currentPath.Replace(location, options.ApplicationPath); + var newFile = currentPath.Replace(updatedLocation, options.ApplicationPath); File.Copy(currentPath, newFile, true); _log.LogDebug("Replaced file {0}", newFile); } diff --git a/src/Ombi.Updater/Ombi.Updater.csproj b/src/Ombi.Updater/Ombi.Updater.csproj index 07fb92d81..6220a100b 100644 --- a/src/Ombi.Updater/Ombi.Updater.csproj +++ b/src/Ombi.Updater/Ombi.Updater.csproj @@ -3,7 +3,7 @@ Exe win10-x64;win10-x86;osx-x64;ubuntu-x64;debian.8-x64;centos.7-x64;linux-x64;linux-arm;linux-arm64; - netcoreapp2.1 + netcoreapp2.2 3.0.0.0 3.0.0.0 @@ -12,17 +12,17 @@ - - - - - - - - - + + + + + + + + + - + \ No newline at end of file diff --git a/src/Ombi.Updater/ProcessProvider.cs b/src/Ombi.Updater/ProcessProvider.cs index 3a3f36355..f3324fc47 100644 --- a/src/Ombi.Updater/ProcessProvider.cs +++ b/src/Ombi.Updater/ProcessProvider.cs @@ -73,33 +73,32 @@ namespace Ombi.Updater process.PriorityClass = priority; } - public void Kill(StartupOptions opts) + public bool Kill(StartupOptions opts) { - if (opts.IsWindowsService) - { - Console.WriteLine("Stopping Service {0}", opts.WindowsServiceName); - var process = new Process(); - var startInfo = - new ProcessStartInfo - { - WindowStyle = ProcessWindowStyle.Hidden, - FileName = "cmd.exe", - Arguments = $"/C net stop \"{opts.WindowsServiceName}\"" - }; - process.StartInfo = startInfo; - process.Start(); - } - else - { + //if (opts.IsWindowsService) + //{ + // Console.WriteLine("Stopping Service {0}", opts.WindowsServiceName); + // var process = new Process(); + // var startInfo = + // new ProcessStartInfo + // { + // WindowStyle = ProcessWindowStyle.Hidden, + // FileName = "cmd.exe", + // Arguments = $"/C net stop \"{opts.WindowsServiceName}\"" + // }; + // process.StartInfo = startInfo; + // process.Start(); + //} + //else + //{ var process = Process.GetProcesses().FirstOrDefault(p => p.ProcessName == opts.ProcessName); if (process == null) { Console.WriteLine("Cannot find process with name: {0}", opts.ProcessName); - return; + return false; } - - process.Refresh(); + if (process.Id > 0) { @@ -108,8 +107,12 @@ namespace Ombi.Updater Console.WriteLine("[{0}]: Waiting for exit", process.Id); process.WaitForExit(); Console.WriteLine("[{0}]: Process terminated successfully", process.Id); + + return true; } - } + + return false; + //} } public void KillAll(string processName) diff --git a/src/Ombi.Updater/Properties/launchSettings.json b/src/Ombi.Updater/Properties/launchSettings.json index d25212166..c91e9f79f 100644 --- a/src/Ombi.Updater/Properties/launchSettings.json +++ b/src/Ombi.Updater/Properties/launchSettings.json @@ -2,7 +2,7 @@ "profiles": { "Ombi.Updater": { "commandName": "Project", - "commandLineArgs": "--applicationPath \\\"C:\\\\Users\\\\Jamie\\\\Source\\\\Repos\\\\Ombi\\\\src\\\\Ombi\\\\bin\\\\Debug\\\\netcoreapp2.0\\\" --processname \\\"Ombi\\\" --startupArgs http://*:5000" + "commandLineArgs": "--applicationPath \"C:\\_git\\ombi\\src\\Ombi.Updater\\bin\\Debug\\netcoreapp2.0\" --processname \"Ombi\"" } } } \ No newline at end of file diff --git a/src/Ombi.sln b/src/Ombi.sln index ab9f8550c..f4f683c11 100644 --- a/src/Ombi.sln +++ b/src/Ombi.sln @@ -92,7 +92,13 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ombi.Api.Github", "Ombi.Api EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ombi.Api.SickRage", "Ombi.Api.SickRage\Ombi.Api.SickRage.csproj", "{94C9A366-2595-45EA-AABB-8E4A2E90EC5B}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ombi.Api.Notifications", "Ombi.Api.Notifications\Ombi.Api.Notifications.csproj", "{10D1FE9D-9124-42B7-B1E1-CEB99B832618}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ombi.Api.Notifications", "Ombi.Api.Notifications\Ombi.Api.Notifications.csproj", "{10D1FE9D-9124-42B7-B1E1-CEB99B832618}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ombi.Api.Lidarr", "Ombi.Api.Lidarr\Ombi.Api.Lidarr.csproj", "{4FA21A20-92F4-462C-B929-2C517A88CC56}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ombi.Helpers.Tests", "Ombi.Helpers.Tests\Ombi.Helpers.Tests.csproj", "{CC8CEFCD-0CB6-45BB-845F-508BCAB5BDC3}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ombi.Api.Gotify", "Ombi.Api.Gotify\Ombi.Api.Gotify.csproj", "{105EA346-766E-45B8-928B-DE6991DCB7EB}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -244,6 +250,18 @@ Global {10D1FE9D-9124-42B7-B1E1-CEB99B832618}.Debug|Any CPU.Build.0 = Debug|Any CPU {10D1FE9D-9124-42B7-B1E1-CEB99B832618}.Release|Any CPU.ActiveCfg = Release|Any CPU {10D1FE9D-9124-42B7-B1E1-CEB99B832618}.Release|Any CPU.Build.0 = Release|Any CPU + {4FA21A20-92F4-462C-B929-2C517A88CC56}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4FA21A20-92F4-462C-B929-2C517A88CC56}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4FA21A20-92F4-462C-B929-2C517A88CC56}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4FA21A20-92F4-462C-B929-2C517A88CC56}.Release|Any CPU.Build.0 = Release|Any CPU + {CC8CEFCD-0CB6-45BB-845F-508BCAB5BDC3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CC8CEFCD-0CB6-45BB-845F-508BCAB5BDC3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CC8CEFCD-0CB6-45BB-845F-508BCAB5BDC3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CC8CEFCD-0CB6-45BB-845F-508BCAB5BDC3}.Release|Any CPU.Build.0 = Release|Any CPU + {105EA346-766E-45B8-928B-DE6991DCB7EB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {105EA346-766E-45B8-928B-DE6991DCB7EB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {105EA346-766E-45B8-928B-DE6991DCB7EB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {105EA346-766E-45B8-928B-DE6991DCB7EB}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -279,6 +297,9 @@ Global {55866DEE-46D1-4AF7-B1A2-62F6190C8EC7} = {9293CA11-360A-4C20-A674-B9E794431BF5} {94C9A366-2595-45EA-AABB-8E4A2E90EC5B} = {9293CA11-360A-4C20-A674-B9E794431BF5} {10D1FE9D-9124-42B7-B1E1-CEB99B832618} = {9293CA11-360A-4C20-A674-B9E794431BF5} + {4FA21A20-92F4-462C-B929-2C517A88CC56} = {9293CA11-360A-4C20-A674-B9E794431BF5} + {CC8CEFCD-0CB6-45BB-845F-508BCAB5BDC3} = {6F42AB98-9196-44C4-B888-D5E409F415A1} + {105EA346-766E-45B8-928B-DE6991DCB7EB} = {9293CA11-360A-4C20-A674-B9E794431BF5} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {192E9BF8-00B4-45E4-BCCC-4C215725C869} diff --git a/src/Ombi/.gitignore b/src/Ombi/.gitignore index b370cf723..49c402ee8 100644 --- a/src/Ombi/.gitignore +++ b/src/Ombi/.gitignore @@ -1,23 +1,10 @@ -/wwwroot/css/** -/wwwroot/fonts/** -/wwwroot/lib/** -/wwwroot/maps/** -/wwwroot/dist/** -/wwwroot/*.js.map -/wwwroot/*.js - -# dependencies -/node_modules -/bower_components - -# misc +node_modules +bin +obj +wwwroot/dist +*.log /.sass-cache /connect.lock /coverage/* -/libpeerconnection.log -npm-debug.log -testem.log -#/typings -/systemjs.config.js* /Logs/** **.db diff --git a/src/Ombi/.vscode/tasks.json b/src/Ombi/.vscode/tasks.json index 0f5fbd905..e57a5dee7 100644 --- a/src/Ombi/.vscode/tasks.json +++ b/src/Ombi/.vscode/tasks.json @@ -4,7 +4,7 @@ "version": "2.0.0", "tasks": [ { - "taskName": "restore", + "label": "restore", "command": "npm", "type": "shell", "args": [ @@ -14,7 +14,16 @@ "problemMatcher": [] }, { - "taskName": "build", + "label": "clean", + "command": "dotnet", + "type": "shell", + "args": [ + "clean" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "build", "command": "dotnet", "type": "shell", "args": [ @@ -27,7 +36,7 @@ "problemMatcher": "$msCompile" }, { - "taskName": "lint", + "label": "lint", "type": "shell", "command": "npm", "args": [ diff --git a/src/Ombi/ApiKeyMiddlewear.cs b/src/Ombi/ApiKeyMiddlewear.cs index f3c956df4..e8fa02d78 100644 --- a/src/Ombi/ApiKeyMiddlewear.cs +++ b/src/Ombi/ApiKeyMiddlewear.cs @@ -8,6 +8,7 @@ using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using Ombi.Core.Authentication; using Ombi.Core.Settings; +using Ombi.Helpers; using Ombi.Settings.Settings.Models; namespace Ombi @@ -98,6 +99,10 @@ namespace Ombi if (context.Request.Headers.Keys.Contains("UserName", StringComparer.InvariantCultureIgnoreCase)) { var username = context.Request.Headers["UserName"].FirstOrDefault(); + if (username.IsNullOrEmpty()) + { + UseApiUser(context); + } var um = context.RequestServices.GetService(); var user = await um.Users.FirstOrDefaultAsync(x => x.UserName.Equals(username, StringComparison.InvariantCultureIgnoreCase)); @@ -114,13 +119,18 @@ namespace Ombi } else { - var identity = new GenericIdentity("API"); - var principal = new GenericPrincipal(identity, new[] { "Admin", "ApiUser" }); - context.User = principal; + UseApiUser(context); } await next.Invoke(context); } } + + private void UseApiUser(HttpContext context) + { + var identity = new GenericIdentity("API"); + var principal = new GenericPrincipal(identity, new[] { "Admin", "ApiUser" }); + context.User = principal; + } } } \ No newline at end of file diff --git a/src/Ombi/Attributes/UserAttribute.cs b/src/Ombi/Attributes/UserAttribute.cs new file mode 100644 index 000000000..3ab4cef49 --- /dev/null +++ b/src/Ombi/Attributes/UserAttribute.cs @@ -0,0 +1,14 @@ +using Microsoft.AspNetCore.Authorization; +using Ombi.Helpers; + + +namespace Ombi.Attributes +{ + public class UserAttribute : AuthorizeAttribute + { + public UserAttribute() + { + Roles = "ManageOwnRequests"; + } + } +} diff --git a/src/Ombi/ClientApp/app/animations/fadeinout.ts b/src/Ombi/ClientApp/app/animations/fadeinout.ts index 8ecf15a15..d0d5692a2 100644 --- a/src/Ombi/ClientApp/app/animations/fadeinout.ts +++ b/src/Ombi/ClientApp/app/animations/fadeinout.ts @@ -1,7 +1,7 @@ import { animate, style, transition, trigger } from "@angular/animations"; -import { AnimationEntryMetadata } from "@angular/core"; +import { AnimationTriggerMetadata } from "@angular/animations"; -export const fadeInOutAnimation: AnimationEntryMetadata = trigger("fadeInOut", [ +export const fadeInOutAnimation: AnimationTriggerMetadata = trigger("fadeInOut", [ transition(":enter", [ // :enter is alias to 'void => *' style({ opacity: 0 }), animate(1000, style({ opacity: 1 })), diff --git a/src/Ombi/ClientApp/app/app.component.html b/src/Ombi/ClientApp/app/app.component.html index 787bafddc..d0d4e2a81 100644 --- a/src/Ombi/ClientApp/app/app.component.html +++ b/src/Ombi/ClientApp/app/app.component.html @@ -34,6 +34,14 @@ {{ 'NavigationBar.Requests' | translate }} +
+
@@ -96,3 +117,12 @@ + + + Please enter a rejection reason, the user will be notified of this: + + + + + + \ No newline at end of file diff --git a/src/Ombi/ClientApp/app/requests/tvrequest-children.component.ts b/src/Ombi/ClientApp/app/requests/tvrequest-children.component.ts index 4a10dc937..2c2145fb9 100644 --- a/src/Ombi/ClientApp/app/requests/tvrequest-children.component.ts +++ b/src/Ombi/ClientApp/app/requests/tvrequest-children.component.ts @@ -4,12 +4,17 @@ import { IChildRequests } from "../interfaces"; import { NotificationService, RequestService } from "../services"; @Component({ - selector:"tvrequests-children", + selector: "tvrequests-children", templateUrl: "./tvrequest-children.component.html", }) export class TvRequestChildrenComponent { @Input() public childRequests: IChildRequests[]; @Input() public isAdmin: boolean; + @Input() public currentUser: string; + + public denyDisplay: boolean; + public requestToDeny: IChildRequests; + public rejectionReason: string; @Output() public requestDeleted = new EventEmitter(); @@ -21,17 +26,17 @@ export class TvRequestChildrenComponent { .subscribe(x => { this.removeRequestFromUi(request); this.requestDeleted.emit(request.id); - }); + }); } public changeAvailability(request: IChildRequests, available: boolean) { request.available = available; - request.seasonRequests.forEach((season)=> { - season.episodes.forEach((ep)=> { + request.seasonRequests.forEach((season) => { + season.episodes.forEach((ep) => { ep.available = available; }); }); - if(available) { + if (available) { this.requestService.markTvAvailable({ id: request.id }).subscribe(x => { if (x.result) { this.notificationService.success( @@ -56,20 +61,26 @@ export class TvRequestChildrenComponent { public deny(request: IChildRequests) { request.denied = true; + this.requestToDeny = request; + this.denyDisplay = true; request.seasonRequests.forEach((season) => { season.episodes.forEach((ep) => { ep.approved = false; }); }); - this.requestService.denyChild({ id: request.id }) + } + + public denyRequest() { + this.requestService.denyChild({ id: this.requestToDeny.id, reason: this.rejectionReason }) .subscribe(x => { + this.denyDisplay = false; if (x.result) { this.notificationService.success( `Request has been denied successfully`); } else { this.notificationService.warning("Request Denied", x.message ? x.message : x.errorMessage); - request.approved = false; + this.requestToDeny.approved = false; } }); } @@ -110,10 +121,18 @@ export class TvRequestChildrenComponent { }); } + public isRequestUser(request: IChildRequests) { + if (request.requestedUser.userName === this.currentUser) { + return true; + } + return false; + } + private removeRequestFromUi(key: IChildRequests) { const index = this.childRequests.indexOf(key, 0); if (index > -1) { this.childRequests.splice(index, 1); } } + } diff --git a/src/Ombi/ClientApp/app/requests/tvrequests.component.html b/src/Ombi/ClientApp/app/requests/tvrequests.component.html index cfe589e23..7c5e13479 100644 --- a/src/Ombi/ClientApp/app/requests/tvrequests.component.html +++ b/src/Ombi/ClientApp/app/requests/tvrequests.component.html @@ -4,124 +4,111 @@
- - -
- +
+
+ +
+
+
+
- - - Results - - - -
-
-
-
+
-
+ poster - poster +
+
+ +
+
+ Status: + {{node.status}}
-
- -
-
- Status: - {{node.data.status}} -
- -
Release Date: {{node.data.releaseDate | date}}
-
-
{{ 'Requests.QualityOverride' | translate }} - {{node.data.qualityOverrideTitle}} -
-
{{ 'Requests.RootFolderOverride' | translate }} - {{node.data.rootPathOverrideTitle}} -
+
Release Date: {{node.releaseDate | amLocal | amDateFormat: 'LL'}}
+
+
{{ 'Requests.QualityOverride' | translate }} + {{node.qualityOverrideTitle}} +
+
{{ 'Requests.RootFolderOverride' | translate }} + {{node.rootPathOverrideTitle}}
- -
-
- -
- -
- - - -
+
+
+
- -
- - - -
- -
-
- -
- -
- - - +
+ +
+ +
+ +
+
+ +
- \ No newline at end of file + \ No newline at end of file diff --git a/src/Ombi/ClientApp/app/requests/tvrequests.component.ts b/src/Ombi/ClientApp/app/requests/tvrequests.component.ts index 202b6dbf6..924fccd1f 100644 --- a/src/Ombi/ClientApp/app/requests/tvrequests.component.ts +++ b/src/Ombi/ClientApp/app/requests/tvrequests.component.ts @@ -1,21 +1,13 @@ -import { PlatformLocation } from "@angular/common"; +import { PlatformLocation } from "@angular/common"; import { Component, Input, OnInit } from "@angular/core"; import { DomSanitizer } from "@angular/platform-browser"; -import "rxjs/add/operator/debounceTime"; -import "rxjs/add/operator/distinctUntilChanged"; -import "rxjs/add/operator/map"; -import { Subject } from "rxjs/Subject"; -import { ImageService } from "./../services/image.service"; - -import "rxjs/add/operator/debounceTime"; -import "rxjs/add/operator/distinctUntilChanged"; -import "rxjs/add/operator/map"; +import { Subject } from "rxjs"; +import { debounceTime, distinctUntilChanged } from "rxjs/operators"; import { AuthService } from "../auth/auth.service"; +import { FilterType, IIssueCategory, IPagenator, IRequestsViewModel, ISonarrProfile, ISonarrRootFolder, ITvRequests, OrderType } from "../interfaces"; import { NotificationService, RequestService, SonarrService } from "../services"; - -import { TreeNode } from "primeng/primeng"; -import { IIssueCategory, IPagenator, ISonarrProfile, ISonarrRootFolder, ITvRequests } from "../interfaces"; +import { ImageService } from "../services/image.service"; @Component({ selector: "tv-requests", @@ -24,10 +16,11 @@ import { IIssueCategory, IPagenator, ISonarrProfile, ISonarrRootFolder, ITvRequ }) export class TvRequestsComponent implements OnInit { - public tvRequests: TreeNode[]; + public tvRequests: IRequestsViewModel; public searchChanged = new Subject(); public searchText: string; public isAdmin: boolean; + public currentUser: string; public showChildDialogue = false; // This is for the child modal popup public selectedSeason: ITvRequests; public defaultPoster: string; @@ -46,90 +39,69 @@ export class TvRequestsComponent implements OnInit { private currentlyLoaded: number; private amountToLoad: number; - constructor(private requestService: RequestService, - private auth: AuthService, - private sanitizer: DomSanitizer, - private imageService: ImageService, - private sonarrService: SonarrService, - private notificationService: NotificationService, - private readonly platformLocation: PlatformLocation) { - this.searchChanged - .debounceTime(600) // Wait Xms after the last event before emitting last event - .distinctUntilChanged() // only emit if value is different from previous value - .subscribe(x => { - this.searchText = x as string; - if (this.searchText === "") { - this.resetSearch(); - return; - } - this.requestService.searchTvRequestsTree(this.searchText) - .subscribe(m => { - this.tvRequests = m; - this.tvRequests.forEach((val) => this.loadBackdrop(val)); - this.tvRequests.forEach((val) => this.setOverride(val.data)); - }); - }); - this.defaultPoster = "../../../images/default_tv_poster.png"; - const base = this.platformLocation.getBaseHrefFromDOM(); - if (base) { - this.defaultPoster = "../../.." + base + "/images/default_tv_poster.png"; - } + constructor( + private requestService: RequestService, + private auth: AuthService, + private sanitizer: DomSanitizer, + private imageService: ImageService, + private sonarrService: SonarrService, + private notificationService: NotificationService, + private readonly platformLocation: PlatformLocation) { + + this.isAdmin = this.auth.hasRole("admin") || this.auth.hasRole("poweruser"); + this.currentUser = this.auth.claims().name; + if (this.isAdmin) { + this.sonarrService.getQualityProfilesWithoutSettings() + .subscribe(x => this.sonarrProfiles = x); + + this.sonarrService.getRootFoldersWithoutSettings() + .subscribe(x => this.sonarrRootFolders = x); + } } - public openClosestTab(el: any) { - const rowclass = "undefined ng-star-inserted"; - el = el.toElement || el.relatedTarget || el.target || el.srcElement; - - if (el.nodeName === "BUTTON") { - - const isButtonAlreadyActive = el.parentElement.querySelector(".active"); - // if a Button already has Class: .active - if (isButtonAlreadyActive) { - isButtonAlreadyActive.classList.remove("active"); - } else { - el.className += " active"; - } - } - - while (el.className !== rowclass) { - // Increment the loop to the parent node until we find the row we need - el = el.parentNode; - } - // At this point, the while loop has stopped and `el` represents the element that has - // the class you specified - - // Then we loop through the children to find the caret which we want to click - const caretright = "fa-caret-right"; - const caretdown = "fa-caret-down"; - for (const value of el.children) { - // the caret from the ui has 2 class selectors depending on if expanded or not - // we search for both since we want to still toggle the clicking - if (value.className.includes(caretright) || value.className.includes(caretdown)) { - // Then we tell JS to click the element even though we hid it from the UI - value.click(); - //Break from loop since we no longer need to continue looking - break; - } - } + public openClosestTab(node: ITvRequests,el: any) { + el.preventDefault(); + node.open = !node.open; } public ngOnInit() { this.amountToLoad = 10; this.currentlyLoaded = 10; - this.tvRequests = []; - this.isAdmin = this.auth.hasRole("admin") || this.auth.hasRole("poweruser"); - + this.tvRequests = {collection:[], total:0}; + + this.searchChanged.pipe( + debounceTime(600), // Wait Xms after the last event before emitting last event + distinctUntilChanged(), // only emit if value is different from previous value + ).subscribe(x => { + this.searchText = x as string; + if (this.searchText === "") { + this.resetSearch(); + return; + } + this.requestService.searchTvRequests(this.searchText) + .subscribe(m => { + this.tvRequests.collection = m; + this.tvRequests.collection.forEach((val) => this.loadBackdrop(val)); + this.tvRequests.collection.forEach((val) => this.setOverride(val)); + }); + }); + this.defaultPoster = "../../../images/default_tv_poster.png"; + const base = this.platformLocation.getBaseHrefFromDOM(); + if (base) { + this.defaultPoster = "../../.." + base + "/images/default_tv_poster.png"; + } + this.loadInit(); } public paginate(event: IPagenator) { const skipAmount = event.first; - this.requestService.getTvRequestsTree(this.amountToLoad, skipAmount) - .subscribe(x => { - this.tvRequests = x; - this.currentlyLoaded = this.currentlyLoaded + this.amountToLoad; - }); + this.requestService.getTvRequests(this.amountToLoad, skipAmount, OrderType.RequestedDateDesc, FilterType.None, FilterType.None) + .subscribe(x => { + this.tvRequests = x; + this.currentlyLoaded = this.currentlyLoaded + this.amountToLoad; + }); } public search(text: any) { @@ -150,14 +122,14 @@ export class TvRequestsComponent implements OnInit { event.preventDefault(); searchResult.rootFolder = rootFolderSelected.id; this.setOverride(searchResult); - this.updateRequest(searchResult); + this.setRootFolder(searchResult); } public selectQualityProfile(searchResult: ITvRequests, profileSelected: ISonarrProfile, event: any) { event.preventDefault(); searchResult.qualityOverride = profileSelected.id; this.setOverride(searchResult); - this.updateRequest(searchResult); + this.setQualityProfile(searchResult); } public reportIssue(catId: IIssueCategory, req: ITvRequests) { @@ -172,13 +144,24 @@ export class TvRequestsComponent implements OnInit { this.setRootFolderOverrides(req); } - private updateRequest(request: ITvRequests) { - this.requestService.updateTvRequest(request) - .subscribe(x => { - this.notificationService.success("Request Updated"); - this.setOverride(x); - request = x; - }); + private setQualityProfile(req: ITvRequests) { + this.requestService.setQualityProfile(req.id, req.qualityOverride).subscribe(x => { + if(x) { + this.notificationService.success("Quality profile updated"); + } else { + this.notificationService.error("Could not update the quality profile"); + } + }); + } + + private setRootFolder(req: ITvRequests) { + this.requestService.setRootFolder(req.id, req.rootFolder).subscribe(x => { + if(x) { + this.notificationService.success("Quality profile updated"); + } else { + this.notificationService.error("Could not update the quality profile"); + } + }); } private setQualityOverrides(req: ITvRequests): void { @@ -204,23 +187,15 @@ export class TvRequestsComponent implements OnInit { private loadInit() { this.requestService.getTotalTv().subscribe(x => this.totalTv = x); - this.requestService.getTvRequestsTree(this.amountToLoad, 0) + this.requestService.getTvRequests(this.amountToLoad, 0, OrderType.RequestedDateDesc, FilterType.None, FilterType.None) .subscribe(x => { this.tvRequests = x; - this.tvRequests.forEach((val, index) => { + this.tvRequests.collection.forEach((val, index) => { this.setDefaults(val); this.loadBackdrop(val); - this.setOverride(val.data); - }); - }); - - if(this.isAdmin) { - this.sonarrService.getQualityProfilesWithoutSettings() - .subscribe(x => this.sonarrProfiles = x); - - this.sonarrService.getRootFoldersWithoutSettings() - .subscribe(x => this.sonarrRootFolders = x); - } + this.setOverride(val); + }); + }); } private resetSearch() { @@ -228,21 +203,21 @@ export class TvRequestsComponent implements OnInit { this.loadInit(); } - private setDefaults(val: any) { - if (val.data.posterPath === null) { - val.data.posterPath = this.defaultPoster; + private setDefaults(val: ITvRequests) { + if (val.posterPath === null) { + val.posterPath = this.defaultPoster; } } - private loadBackdrop(val: TreeNode): void { - if (val.data.background != null) { - val.data.background = this.sanitizer.bypassSecurityTrustStyle - ("url(https://image.tmdb.org/t/p/w1280" + val.data.background + ")"); + private loadBackdrop(val: ITvRequests): void { + if (val.background != null) { + val.background = this.sanitizer.bypassSecurityTrustStyle + ("url(https://image.tmdb.org/t/p/w1280" + val.background + ")"); } else { - this.imageService.getTvBanner(val.data.tvDbId).subscribe(x => { - if(x) { - val.data.background = this.sanitizer.bypassSecurityTrustStyle - ("url(" + x + ")"); + this.imageService.getTvBanner(val.tvDbId).subscribe(x => { + if (x) { + val.background = this.sanitizer.bypassSecurityTrustStyle + ("url(" + x + ")"); } }); } diff --git a/src/Ombi/ClientApp/app/search/moviesearch.component.html b/src/Ombi/ClientApp/app/search/moviesearch.component.html index 44dc345bc..ed669e0f5 100644 --- a/src/Ombi/ClientApp/app/search/moviesearch.component.html +++ b/src/Ombi/ClientApp/app/search/moviesearch.component.html @@ -1,9 +1,11 @@ 
-
- + +
+
-
+
  • +
    +
    -
    -
    + +
    +
    +
    + + + +
    +
    + + +
    +
    + +
    + +
    +
    +
    + +
    +
    +
    + + +
    +
    +
    +
    + +
    +
    + + + +
    -
    + +
    - -
    - -
    -
    + +
    + +
    +
    - poster + poster
    -

    {{result.title}} ({{result.releaseDate | date: 'yyyy'}})

    +

    {{result.title}} ({{result.releaseDate | amLocal | amDateFormat: 'YYYY'}})

    - - {{ 'Search.TheatricalRelease' | translate: {date: result.releaseDate | date: 'mediumDate'} }} - {{ 'Search.DigitalDate' | translate: {date: result.digitalReleaseDate | date: 'mediumDate'} }} + + {{ + 'Search.TheatricalRelease' | translate: {date: result.releaseDate | amLocal | + amDateFormat: 'LL'} }} + {{ 'Search.DigitalDate' | translate: {date: result.digitalReleaseDate | + amLocal | amDateFormat: 'LL'} }} - + - - {{result.quality}}p - - - - - + + {{result.quality}}p + + + + + - + -
    +

    {{result.overview}}

    -
    -
    - - - -
    -
    - +
    -
    - - - - - - -
    - - -
    +
    + + + + + + +
    + + + +
    -
    -
    +
    +
    @@ -115,4 +181,4 @@ + [issueCategory]="issueCategorySelected" [id]="issueRequestId" [providerId]="issueProviderId"> diff --git a/src/Ombi/ClientApp/app/search/moviesearch.component.ts b/src/Ombi/ClientApp/app/search/moviesearch.component.ts index 236ae8ed8..90502bb93 100644 --- a/src/Ombi/ClientApp/app/search/moviesearch.component.ts +++ b/src/Ombi/ClientApp/app/search/moviesearch.component.ts @@ -1,28 +1,36 @@ -import { PlatformLocation } from "@angular/common"; +import { PlatformLocation } from "@angular/common"; import { Component, Input, OnInit } from "@angular/core"; import { DomSanitizer } from "@angular/platform-browser"; import { TranslateService } from "@ngx-translate/core"; -import "rxjs/add/operator/debounceTime"; -import "rxjs/add/operator/distinctUntilChanged"; -import "rxjs/add/operator/map"; -import { Subject } from "rxjs/Subject"; +import { Subject } from "rxjs"; +import { debounceTime, distinctUntilChanged } from "rxjs/operators"; import { AuthService } from "../auth/auth.service"; -import { IIssueCategory, IRequestEngineResult, ISearchMovieResult } from "../interfaces"; -import { NotificationService, RequestService, SearchService } from "../services"; +import { IIssueCategory, ILanguageRefine, IRequestEngineResult, ISearchMovieResult } from "../interfaces"; +import { NotificationService, RequestService, SearchService, SettingsService } from "../services"; + +import * as languageData from "../../other/iso-lang.json"; @Component({ selector: "movie-search", templateUrl: "./moviesearch.component.html", + styleUrls: ["./search.component.scss"], }) export class MovieSearchComponent implements OnInit { public searchText: string; public searchChanged: Subject = new Subject(); + public movieRequested: Subject = new Subject(); public movieResults: ISearchMovieResult[]; public result: IRequestEngineResult; + public searchApplied = false; - + public refineSearchEnabled = false; + public searchYear?: number; + public actorSearch: boolean; + public selectedLanguage: string; + public langauges: ILanguageRefine[]; + @Input() public issueCategories: IIssueCategory[]; @Input() public issuesEnabled: boolean; public issuesBarVisible = false; @@ -31,30 +39,20 @@ export class MovieSearchComponent implements OnInit { public issueProviderId: string; public issueCategorySelected: IIssueCategory; public defaultPoster: string; - - constructor(private searchService: SearchService, private requestService: RequestService, - private notificationService: NotificationService, private authService: AuthService, - private readonly translate: TranslateService, private sanitizer: DomSanitizer, - private readonly platformLocation: PlatformLocation) { - this.searchChanged - .debounceTime(600) // Wait Xms after the last event before emitting last event - .distinctUntilChanged() // only emit if value is different from previous value - .subscribe(x => { - this.searchText = x as string; - if (this.searchText === "") { - this.clearResults(); - return; - } - this.searchService.searchMovie(this.searchText) - .subscribe(x => { - this.movieResults = x; - this.searchApplied = true; - // Now let's load some extra info including IMDB Id - // This way the search is fast at displaying results. - this.getExtraInfo(); - }); - }); + constructor( + private searchService: SearchService, private requestService: RequestService, + private notificationService: NotificationService, private authService: AuthService, + private readonly translate: TranslateService, private sanitizer: DomSanitizer, + private readonly platformLocation: PlatformLocation, private settingsService: SettingsService) { + this.langauges = languageData; + this.searchChanged.pipe( + debounceTime(600), // Wait Xms after the last event before emitting last event + distinctUntilChanged(), // only emit if value is different from previous value + ).subscribe(x => { + this.searchText = x as string; + this.runSearch(); + }); this.defaultPoster = "../../../images/default_movie_poster.png"; const base = this.platformLocation.getBaseHrefFromDOM(); if (base) { @@ -69,7 +67,8 @@ export class MovieSearchComponent implements OnInit { message: "", result: false, errorMessage: "", - }; + }; + this.settingsService.getDefaultLanguage().subscribe(x => this.selectedLanguage = x); this.popularMovies(); } @@ -86,11 +85,12 @@ export class MovieSearchComponent implements OnInit { } try { - this.requestService.requestMovie({ theMovieDbId: searchResult.id }) + const language = this.selectedLanguage && this.selectedLanguage.length > 0 ? this.selectedLanguage : "en"; + this.requestService.requestMovie({ theMovieDbId: searchResult.id, languageCode: language }) .subscribe(x => { this.result = x; - if (this.result.result) { + this.movieRequested.next(); this.translate.get("Search.RequestAdded", { title: searchResult.title }).subscribe(x => { this.notificationService.success(x); searchResult.processed = true; @@ -105,7 +105,7 @@ export class MovieSearchComponent implements OnInit { searchResult.approved = false; searchResult.processed = false; searchResult.requestProcessing = false; - + } }); } catch (e) { @@ -151,7 +151,8 @@ export class MovieSearchComponent implements OnInit { public reportIssue(catId: IIssueCategory, req: ISearchMovieResult) { this.issueRequestId = req.id; - this.issueRequestTitle = req.title; + const releaseDate = new Date(req.releaseDate); + this.issueRequestTitle = req.title + ` (${releaseDate.getFullYear()})`; this.issueCategorySelected = catId; this.issuesBarVisible = true; this.issueProviderId = req.id.toString(); @@ -159,18 +160,19 @@ export class MovieSearchComponent implements OnInit { public similarMovies(theMovieDbId: number) { this.clearResults(); - this.searchService.similarMovies(theMovieDbId) - .subscribe(x => { - this.movieResults = x; - this.getExtraInfo(); - }); + const lang = this.selectedLanguage && this.selectedLanguage.length > 0 ? this.selectedLanguage : ""; + this.searchService.similarMovies(theMovieDbId, lang) + .subscribe(x => { + this.movieResults = x; + this.getExtraInfo(); + }); } - + public subscribe(r: ISearchMovieResult) { r.subscribed = true; this.requestService.subscribeToMovie(r.requestId) .subscribe(x => { - this.notificationService.success("Subscribed To Movie!"); + this.notificationService.success(`Subscribed To Movie ${r.title}!`); }); } @@ -182,20 +184,39 @@ export class MovieSearchComponent implements OnInit { }); } - private getExtraInfo() { + public refineOpen() { + this.refineSearchEnabled = !this.refineSearchEnabled; + if (!this.refineSearchEnabled) { + this.searchYear = undefined; + } + } - this.movieResults.forEach((val, index) => { - if (val.posterPath === null) { - val.posterPath = this.defaultPoster; - } else { - val.posterPath = "https://image.tmdb.org/t/p/w300/" + val.posterPath; - } - val.background = this.sanitizer.bypassSecurityTrustStyle - ("url(" + "https://image.tmdb.org/t/p/w1280" + val.backdropPath + ")"); - this.searchService.getMovieInformation(val.id) - .subscribe(m => { - this.updateItem(val, m); - }); + public applyRefinedSearch() { + this.runSearch(); + } + + private getExtraInfo() { + + this.movieResults.forEach((val, index) => { + if (val.posterPath === null) { + val.posterPath = this.defaultPoster; + } else { + val.posterPath = "https://image.tmdb.org/t/p/w300/" + val.posterPath; + } + val.background = this.sanitizer.bypassSecurityTrustStyle + ("url(" + "https://image.tmdb.org/t/p/w1280" + val.backdropPath + ")"); + + if (this.applyRefinedSearch) { + this.searchService.getMovieInformationWithRefined(val.id, this.selectedLanguage) + .subscribe(m => { + this.updateItem(val, m); + }); + } else { + this.searchService.getMovieInformation(val.id) + .subscribe(m => { + this.updateItem(val, m); + }); + } }); } @@ -203,7 +224,7 @@ export class MovieSearchComponent implements OnInit { const index = this.movieResults.indexOf(key, 0); if (index > -1) { const copy = { ...this.movieResults[index] }; - this.movieResults[index] = updated; + this.movieResults[index] = updated; this.movieResults[index].background = copy.background; this.movieResults[index].posterPath = copy.posterPath; } @@ -212,4 +233,41 @@ export class MovieSearchComponent implements OnInit { this.movieResults = []; this.searchApplied = false; } + + private runSearch() { + if (this.searchText === "") { + this.clearResults(); + return; + } + if (this.refineOpen) { + if (!this.actorSearch) { + this.searchService.searchMovieWithRefined(this.searchText, this.searchYear, this.selectedLanguage) + .subscribe(x => { + this.movieResults = x; + this.searchApplied = true; + // Now let's load some extra info including IMDB Id + // This way the search is fast at displaying results. + this.getExtraInfo(); + }); + } else { + this.searchService.searchMovieByActor(this.searchText, this.selectedLanguage) + .subscribe(x => { + this.movieResults = x; + this.searchApplied = true; + // Now let's load some extra info including IMDB Id + // This way the search is fast at displaying results. + this.getExtraInfo(); + }); + } + } else { + this.searchService.searchMovie(this.searchText) + .subscribe(x => { + this.movieResults = x; + this.searchApplied = true; + // Now let's load some extra info including IMDB Id + // This way the search is fast at displaying results. + this.getExtraInfo(); + }); + } + } } diff --git a/src/Ombi/ClientApp/app/search/moviesearchgrid.component.html b/src/Ombi/ClientApp/app/search/moviesearchgrid.component.html index 4a817dd18..53270f943 100644 --- a/src/Ombi/ClientApp/app/search/moviesearchgrid.component.html +++ b/src/Ombi/ClientApp/app/search/moviesearchgrid.component.html @@ -67,10 +67,10 @@
    -

    {{result.title}} ({{result.releaseDate | date: 'yyyy'}})

    +

    {{result.title}} ({{result.releaseDate | amLocal | amDateFormat: 'YYYY'}})

    - Release Date: {{result.releaseDate | date: 'dd/MM/yyyy'}} + Release Date: {{result.releaseDate | amLocal | amDateFormat: 'L'}} HomePage diff --git a/src/Ombi/ClientApp/app/search/moviesearchgrid.component.ts b/src/Ombi/ClientApp/app/search/moviesearchgrid.component.ts index 1c9c7beeb..4ff6fa986 100644 --- a/src/Ombi/ClientApp/app/search/moviesearchgrid.component.ts +++ b/src/Ombi/ClientApp/app/search/moviesearchgrid.component.ts @@ -1,13 +1,10 @@ -import { Component, OnInit } from "@angular/core"; -import "rxjs/add/operator/debounceTime"; -import "rxjs/add/operator/distinctUntilChanged"; -import "rxjs/add/operator/map"; -import { Subject } from "rxjs/Subject"; +import { Component, OnInit } from "@angular/core"; +import { Subject } from "rxjs"; +import { debounceTime, distinctUntilChanged } from "rxjs/operators"; import { AuthService } from "../auth/auth.service"; -import { NotificationService, RequestService, SearchService } from "../services"; - import { IRequestEngineResult, ISearchMovieResult, ISearchMovieResultContainer } from "../interfaces"; +import { NotificationService, RequestService, SearchService } from "../services"; @Component({ selector: "movie-search-grid", @@ -21,28 +18,29 @@ export class MovieSearchGridComponent implements OnInit { public movieResultGrid: ISearchMovieResultContainer[] = []; public result: IRequestEngineResult; public searchApplied = false; - - constructor(private searchService: SearchService, private requestService: RequestService, - private notificationService: NotificationService, private authService: AuthService) { - this.searchChanged - .debounceTime(600) // Wait Xms afterthe last event before emitting last event - .distinctUntilChanged() // only emit if value is different from previous value - .subscribe(x => { - this.searchText = x as string; - if (this.searchText === "") { - this.clearResults(); - return; - } - this.searchService.searchMovie(this.searchText) - .subscribe(x => { - this.movieResults = x; - this.searchApplied = true; - // Now let's load some exta info including IMDBId - // This way the search is fast at displaying results. - this.getExtaInfo(); - }); - }); + constructor( + private searchService: SearchService, private requestService: RequestService, + private notificationService: NotificationService, private authService: AuthService) { + + this.searchChanged.pipe( + debounceTime(600), // Wait Xms afterthe last event before emitting last event + distinctUntilChanged(), // only emit if value is different from previous value + ).subscribe(x => { + this.searchText = x as string; + if (this.searchText === "") { + this.clearResults(); + return; + } + this.searchService.searchMovie(this.searchText) + .subscribe(x => { + this.movieResults = x; + this.searchApplied = true; + // Now let's load some exta info including IMDBId + // This way the search is fast at displaying results. + this.getExtaInfo(); + }); + }); } public ngOnInit() { @@ -67,7 +65,7 @@ export class MovieSearchGridComponent implements OnInit { } try { - this.requestService.requestMovie({ theMovieDbId : searchResult.id}) + this.requestService.requestMovie({ theMovieDbId: searchResult.id, languageCode: "en" }) .subscribe(x => { this.result = x; @@ -129,7 +127,7 @@ export class MovieSearchGridComponent implements OnInit { }); } - private getExtaInfo() { + private getExtaInfo() { this.movieResults.forEach((val) => { this.searchService.getMovieInformation(val.id) .subscribe(m => this.updateItem(val, m)); @@ -147,18 +145,17 @@ export class MovieSearchGridComponent implements OnInit { this.movieResults = []; this.searchApplied = false; } - + private processGrid(movies: ISearchMovieResult[]) { - let container = { movies: [] }; + let container = { movies: [] }; movies.forEach((movie, i) => { i++; - if((i % 4) === 0) { - container.movies.push(movie); + if ((i % 4) === 0) { + container.movies.push(movie); this.movieResultGrid.push(container); - container = { movies: [] }; + container = { movies: [] }; } else { - - container.movies.push(movie); + container.movies.push(movie); } }); this.movieResultGrid.push(container); diff --git a/src/Ombi/ClientApp/app/search/music/albumsearch.component.html b/src/Ombi/ClientApp/app/search/music/albumsearch.component.html new file mode 100644 index 000000000..2b31df040 --- /dev/null +++ b/src/Ombi/ClientApp/app/search/music/albumsearch.component.html @@ -0,0 +1,116 @@ +
    + +
    +
    + + +
    + poster +
    + + +
    + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + Release Date: {{result.releaseDate | amLocal | amDateFormat: 'L'}} + + + {{result.rating}}/10 + + + + + +
    +
    + + +
    + +
    + +
    +
    +
    + + + + + + +
    + + + + +
    + +
    + + + + diff --git a/src/Ombi/ClientApp/app/search/music/albumsearch.component.ts b/src/Ombi/ClientApp/app/search/music/albumsearch.component.ts new file mode 100644 index 000000000..1a44e120e --- /dev/null +++ b/src/Ombi/ClientApp/app/search/music/albumsearch.component.ts @@ -0,0 +1,91 @@ +import { Component, EventEmitter, Input, Output } from "@angular/core"; +import { TranslateService } from "@ngx-translate/core"; + +import { Subject } from "rxjs"; +import { AuthService } from "../../auth/auth.service"; +import { IIssueCategory, IRequestEngineResult } from "../../interfaces"; +import { ISearchAlbumResult } from "../../interfaces/ISearchMusicResult"; +import { NotificationService, RequestService } from "../../services"; + +@Component({ + selector: "album-search", + templateUrl: "./albumsearch.component.html", +}) +export class AlbumSearchComponent { + + @Input() public result: ISearchAlbumResult; + public engineResult: IRequestEngineResult; + @Input() public defaultPoster: string; + + @Input() public issueCategories: IIssueCategory[]; + @Input() public issuesEnabled: boolean; + + @Input() public musicRequested: Subject; + public issuesBarVisible = false; + public issueRequestTitle: string; + public issueRequestId: number; + public issueProviderId: string; + public issueCategorySelected: IIssueCategory; + + @Output() public setSearch = new EventEmitter(); + + constructor( + private requestService: RequestService, + private notificationService: NotificationService, private authService: AuthService, + private readonly translate: TranslateService) { + } + + public selectArtist(event: Event, artistId: string) { + event.preventDefault(); + this.setSearch.emit(artistId); + } + + public reportIssue(catId: IIssueCategory, req: ISearchAlbumResult) { + this.issueRequestId = req.id; + this.issueRequestTitle = req.title + `(${req.releaseDate.getFullYear})`; + this.issueCategorySelected = catId; + this.issuesBarVisible = true; + this.issueProviderId = req.id.toString(); + } + + public request(searchResult: ISearchAlbumResult) { + searchResult.requested = true; + searchResult.requestProcessing = true; + searchResult.showSubscribe = false; + if (this.authService.hasRole("admin") || this.authService.hasRole("AutoApproveMusic")) { + searchResult.approved = true; + } + + try { + this.requestService.requestAlbum({ foreignAlbumId: searchResult.foreignAlbumId }) + .subscribe(x => { + + this.engineResult = x; + + if (this.engineResult.result) { + this.musicRequested.next(); + this.translate.get("Search.RequestAdded", { title: searchResult.title }).subscribe(x => { + this.notificationService.success(x); + searchResult.processed = true; + }); + } else { + if (this.engineResult.errorMessage && this.engineResult.message) { + this.notificationService.warning("Request Added", `${this.engineResult.message} - ${this.engineResult.errorMessage}`); + } else { + this.notificationService.warning("Request Added", this.engineResult.message ? this.engineResult.message : this.engineResult.errorMessage); + } + searchResult.requested = false; + searchResult.approved = false; + searchResult.processed = false; + searchResult.requestProcessing = false; + + } + }); + } catch (e) { + + searchResult.processed = false; + searchResult.requestProcessing = false; + this.notificationService.error(e); + } + } +} diff --git a/src/Ombi/ClientApp/app/search/music/artistsearch.component.html b/src/Ombi/ClientApp/app/search/music/artistsearch.component.html new file mode 100644 index 000000000..cc1f093df --- /dev/null +++ b/src/Ombi/ClientApp/app/search/music/artistsearch.component.html @@ -0,0 +1,65 @@ +
    + +
    +
    +
    + poster + +
    +
    +
    + +

    {{result.artistName}}

    +
    + + + + + {{result.artistType}} + + + {{result.disambiguation}} + + + Monitored + + + +
    +
    +

    {{result.overview | truncate: 350 }}

    + + +
    + +
    +
    +
    + + +
    +
    + + +
    + + +
    + + + + +
    \ No newline at end of file diff --git a/src/Ombi/ClientApp/app/search/music/artistsearch.component.ts b/src/Ombi/ClientApp/app/search/music/artistsearch.component.ts new file mode 100644 index 000000000..852e294e3 --- /dev/null +++ b/src/Ombi/ClientApp/app/search/music/artistsearch.component.ts @@ -0,0 +1,27 @@ +import { Component, EventEmitter, Input, Output } from "@angular/core"; + +import { ISearchAlbumResult, ISearchArtistResult } from "../../interfaces/ISearchMusicResult"; +import { SearchService } from "../../services"; + +@Component({ + selector: "artist-search", + templateUrl: "./artistsearch.component.html", +}) +export class ArtistSearchComponent { + + @Input() public result: ISearchArtistResult; + @Input() public defaultPoster: string; + public searchingAlbums: boolean; + + @Output() public viewAlbumsResult = new EventEmitter(); + + constructor(private searchService: SearchService) { + } + + public viewAllAlbums() { + this.searchingAlbums = true; + this.searchService.getAlbumsForArtist(this.result.forignArtistId).subscribe(x => { + this.viewAlbumsResult.emit(x); + }); + } +} diff --git a/src/Ombi/ClientApp/app/search/music/musicsearch.component.html b/src/Ombi/ClientApp/app/search/music/musicsearch.component.html new file mode 100644 index 000000000..89af8de01 --- /dev/null +++ b/src/Ombi/ClientApp/app/search/music/musicsearch.component.html @@ -0,0 +1,52 @@ + +
    +
    + +
    + +
    +
    +
    +
    + + + + +
    +
    + + +
    +
    +
    +
    + +
    +
    +
    + +
    +
    + + + + +
    + +
    +
    +
    +
    +
    + +
    +
    +
    +
    + +
    +
    + + + diff --git a/src/Ombi/ClientApp/app/search/music/musicsearch.component.ts b/src/Ombi/ClientApp/app/search/music/musicsearch.component.ts new file mode 100644 index 000000000..2fd9839f2 --- /dev/null +++ b/src/Ombi/ClientApp/app/search/music/musicsearch.component.ts @@ -0,0 +1,148 @@ +import { PlatformLocation } from "@angular/common"; +import { Component, Input, OnInit } from "@angular/core"; +import { DomSanitizer } from "@angular/platform-browser"; +import { Subject } from "rxjs"; +import { debounceTime, distinctUntilChanged } from "rxjs/operators"; +import { IIssueCategory, IRequestEngineResult } from "../../interfaces"; +import { ISearchAlbumResult, ISearchArtistResult } from "../../interfaces/ISearchMusicResult"; +import { SearchService } from "../../services"; + +@Component({ + selector: "music-search", + templateUrl: "./musicsearch.component.html", +}) +export class MusicSearchComponent implements OnInit { + + public searchText: string; + public searchChanged: Subject = new Subject(); + public artistResult: ISearchArtistResult[]; + public albumResult: ISearchAlbumResult[]; + public result: IRequestEngineResult; + public searchApplied = false; + public searchAlbum: boolean = true; + + public musicRequested: Subject = new Subject(); + @Input() public issueCategories: IIssueCategory[]; + @Input() public issuesEnabled: boolean; + public issuesBarVisible = false; + public issueRequestTitle: string; + public issueRequestId: number; + public issueProviderId: string; + public issueCategorySelected: IIssueCategory; + public defaultPoster: string; + + constructor( + private searchService: SearchService, private sanitizer: DomSanitizer, + private platformLocation: PlatformLocation) { + + this.searchChanged.pipe( + debounceTime(600), // Wait Xms after the last event before emitting last event + distinctUntilChanged(), // only emit if value is different from previous value + ).subscribe(x => { + this.searchText = x as string; + if (this.searchText === "") { + if(this.searchAlbum) { + this.clearAlbumResults(); + } else { + this.clearArtistResults(); + } + + return; + } + if(this.searchAlbum) { + if(!this.searchText) { + this.searchText = "iowa"; // REMOVE + } + this.searchService.searchAlbum(this.searchText) + .subscribe(x => { + this.albumResult = x; + this.searchApplied = true; + this.setAlbumBackground(); + }); + } else { + this.searchService.searchArtist(this.searchText) + .subscribe(x => { + this.artistResult = x; + this.searchApplied = true; + this.setArtistBackground(); + }); + } + }); + this.defaultPoster = "../../../images/default-music-placeholder.png"; + const base = this.platformLocation.getBaseHrefFromDOM(); + if (base) { + this.defaultPoster = "../../.." + base + "/images/default-music-placeholder.png"; + } + } + + public ngOnInit() { + this.searchText = ""; + this.artistResult = []; + this.result = { + message: "", + result: false, + errorMessage: "", + }; + } + + public search(text: any) { + this.searchChanged.next(text.target.value); + } + + public searchMode(val: boolean) { + this.searchAlbum = val; + if(val) { + // Album + this.clearArtistResults(); + } else { + this.clearAlbumResults(); + } + } + + public setArtistSearch(artistId: string) { + this.searchAlbum = false; + this.clearAlbumResults(); + this.searchChanged.next(`lidarr:${artistId}`); + } + + public viewAlbumsForArtist(albums: ISearchAlbumResult[]) { + this.clearArtistResults(); + this.searchAlbum = true; + this.albumResult = albums; + this.setAlbumBackground(); + } + + private clearArtistResults() { + this.artistResult = []; + this.searchApplied = false; + } + + private clearAlbumResults() { + this.albumResult = []; + this.searchApplied = false; + } + + private setArtistBackground() { + this.artistResult.forEach((val, index) => { + if (val.poster === null) { + val.poster = this.defaultPoster; + } + val.background = this.sanitizer.bypassSecurityTrustStyle + ("url(" + val.banner + ")"); + }); + } + + private setAlbumBackground() { + this.albumResult.forEach((val, index) => { + if (val.disk === null) { + if(val.cover === null) { + val.disk = this.defaultPoster; + } else { + val.disk = val.cover; + } + } + val.background = this.sanitizer.bypassSecurityTrustStyle + ("url(" + val.cover + ")"); + }); + } +} diff --git a/src/Ombi/ClientApp/app/search/search.component.html b/src/Ombi/ClientApp/app/search/search.component.html index 398bfd311..7e1d280d2 100644 --- a/src/Ombi/ClientApp/app/search/search.component.html +++ b/src/Ombi/ClientApp/app/search/search.component.html @@ -4,7 +4,7 @@ -
    diff --git a/src/Ombi/ClientApp/app/search/search.component.scss b/src/Ombi/ClientApp/app/search/search.component.scss new file mode 100644 index 000000000..b8339bd82 --- /dev/null +++ b/src/Ombi/ClientApp/app/search/search.component.scss @@ -0,0 +1,34 @@ +@media (max-width: 978px) { + .top-spacing { + padding-top: 5% + } + .form-control-search { + width: 77%; + } + +} +@media (min-width: 979px) { + .top-spacing { + padding-top: 2% + } + .form-control-search { + width: 90%; + } +} + +.tab-content { + margin-top: 1.5em; +} + +.search-bar-background { + background-color: #333333; +} + +.vcenter { + display: flex; + align-items: center; +} + +.refine-option { + box-shadow: inset 0 1px 5px rgba(0,0,0,1.0); +} diff --git a/src/Ombi/ClientApp/app/search/search.component.ts b/src/Ombi/ClientApp/app/search/search.component.ts index f583266ee..43d926970 100644 --- a/src/Ombi/ClientApp/app/search/search.component.ts +++ b/src/Ombi/ClientApp/app/search/search.component.ts @@ -1,7 +1,7 @@ import { Component, OnInit } from "@angular/core"; -import { IIssueCategory } from "./../interfaces"; -import { IssuesService, SettingsService } from "./../services"; +import { IIssueCategory } from "../interfaces"; +import { IssuesService, SettingsService } from "../services"; @Component({ templateUrl: "./search.component.html", @@ -9,8 +9,10 @@ import { IssuesService, SettingsService } from "./../services"; export class SearchComponent implements OnInit { public showTv: boolean; public showMovie: boolean; + public showMusic: boolean; public issueCategories: IIssueCategory[]; public issuesEnabled = false; + public musicEnabled: boolean; constructor(private issuesService: IssuesService, private settingsService: SettingsService) { @@ -18,8 +20,10 @@ export class SearchComponent implements OnInit { } public ngOnInit() { + this.settingsService.lidarrEnabled().subscribe(x => this.musicEnabled = x); this.showMovie = true; this.showTv = false; + this.showMusic = false; this.issuesService.getCategories().subscribe(x => this.issueCategories = x); this.settingsService.getIssueSettings().subscribe(x => this.issuesEnabled = x.enabled); } @@ -27,10 +31,17 @@ export class SearchComponent implements OnInit { public selectMovieTab() { this.showMovie = true; this.showTv = false; + this.showMusic = false; } public selectTvTab() { this.showMovie = false; this.showTv = true; + this.showMusic = false; + } + public selectMusicTab() { + this.showMovie = false; + this.showTv = false; + this.showMusic = true; } } diff --git a/src/Ombi/ClientApp/app/search/search.module.ts b/src/Ombi/ClientApp/app/search/search.module.ts index d5b8669ef..4e87940f5 100644 --- a/src/Ombi/ClientApp/app/search/search.module.ts +++ b/src/Ombi/ClientApp/app/search/search.module.ts @@ -7,6 +7,9 @@ import { NgbModule } from "@ng-bootstrap/ng-bootstrap"; import { MovieSearchComponent } from "./moviesearch.component"; import { MovieSearchGridComponent } from "./moviesearchgrid.component"; +import { AlbumSearchComponent } from "./music/albumsearch.component"; +import { ArtistSearchComponent } from "./music/artistsearch.component"; +import { MusicSearchComponent } from "./music/musicsearch.component"; import { SearchComponent } from "./search.component"; import { SeriesInformationComponent } from "./seriesinformation.component"; import { TvSearchComponent } from "./tvsearch.component"; @@ -18,6 +21,7 @@ import { SearchService } from "../services"; import { AuthGuard } from "../auth/auth.guard"; +import { RemainingRequestsComponent } from "../requests/remainingrequests.component"; import { SharedModule } from "../shared/shared.module"; const routes: Routes = [ @@ -25,7 +29,7 @@ const routes: Routes = [ { path: "show/:id", component: SeriesInformationComponent, canActivate: [AuthGuard] }, ]; @NgModule({ - imports: [ + imports: [ CommonModule, FormsModule, RouterModule.forChild(routes), @@ -41,6 +45,10 @@ const routes: Routes = [ TvSearchComponent, SeriesInformationComponent, MovieSearchGridComponent, + RemainingRequestsComponent, + MusicSearchComponent, + ArtistSearchComponent, + AlbumSearchComponent, ], exports: [ RouterModule, diff --git a/src/Ombi/ClientApp/app/search/seriesinformation.component.html b/src/Ombi/ClientApp/app/search/seriesinformation.component.html index e5eb15d69..12059e417 100644 --- a/src/Ombi/ClientApp/app/search/seriesinformation.component.html +++ b/src/Ombi/ClientApp/app/search/seriesinformation.component.html @@ -1,13 +1,4 @@ - -
    +
    @@ -51,8 +42,11 @@
    - + diff --git a/src/Ombi/ClientApp/app/settings/customization/customization.component.html b/src/Ombi/ClientApp/app/settings/customization/customization.component.html index 9d05edf1f..1f7a158ae 100644 --- a/src/Ombi/ClientApp/app/settings/customization/customization.component.html +++ b/src/Ombi/ClientApp/app/settings/customization/customization.component.html @@ -21,8 +21,8 @@
    - +
    @@ -36,8 +36,8 @@
    - +
    @@ -47,37 +47,39 @@
    - -
    - -
    - -
    -
    -
    - +
    -
    +
    -
    -
    +
    -
    +
    +
    + + + +
    +
    @@ -89,21 +91,14 @@
    -
    +
    - -
    - -
    +
    -
    - +
    +
    - Preset themes are powered by - layer#Cake. -
    diff --git a/src/Ombi/ClientApp/app/settings/customization/customization.component.ts b/src/Ombi/ClientApp/app/settings/customization/customization.component.ts index 77e9a16bd..0daf9ffad 100644 --- a/src/Ombi/ClientApp/app/settings/customization/customization.component.ts +++ b/src/Ombi/ClientApp/app/settings/customization/customization.component.ts @@ -1,6 +1,6 @@ import { Component, OnInit } from "@angular/core"; -import { ICustomizationSettings, IThemes } from "../../interfaces"; +import { ICustomizationSettings } from "../../interfaces"; import { NotificationService } from "../../services"; import { SettingsService } from "../../services"; @@ -10,7 +10,6 @@ import { SettingsService } from "../../services"; export class CustomizationComponent implements OnInit { public settings: ICustomizationSettings; - public themes: IThemes[]; public advanced: boolean; constructor(private settingsService: SettingsService, private notificationService: NotificationService) { } @@ -18,26 +17,6 @@ export class CustomizationComponent implements OnInit { public ngOnInit() { this.settingsService.getCustomization().subscribe(x => { this.settings = x; - this.settingsService.getThemes().subscribe(t => { - this.themes = t; - - const existingTheme = this.themes.filter((item) => { - return item.fullName === this.settings.presetThemeName; - })[0]; - - if(existingTheme) { - const index = this.themes.indexOf(existingTheme, 0); - if (index > -1) { - this.themes.splice(index, 1); - } - } - if(x.hasPresetTheme) { - this.themes.unshift({displayName: x.presetThemeDisplayName, fullName: x.presetThemeName, url: existingTheme.url, version: x.presetThemeVersion}); - this.themes.unshift({displayName: "None", fullName: "None", url: "", version: ""}); - } else { - this.themes.unshift({displayName: "Please Select", fullName: "-1", url: "-1", version: ""}); - } - }); }); } @@ -45,8 +24,8 @@ export class CustomizationComponent implements OnInit { public save() { this.settingsService.verifyUrl(this.settings.applicationUrl).subscribe(x => { - if(this.settings.applicationUrl) { - if(!x) { + if (this.settings.applicationUrl) { + if (!x) { this.notificationService.error(`The URL "${this.settings.applicationUrl}" is not valid. Please format it correctly e.g. http://www.google.com/`); return; } @@ -62,26 +41,4 @@ export class CustomizationComponent implements OnInit { }); } - - public dropDownChange(event: any): void { - const selectedThemeFullName = event.target.value; - const selectedTheme = this.themes.filter((val) => { - return val.fullName === selectedThemeFullName; - })[0]; - - if(selectedTheme.fullName === this.settings.presetThemeName) { - return; - } - - if(selectedTheme.fullName === "None" || selectedTheme.fullName === "-1") { - this.settings.presetThemeName = ""; - this.settings.presetThemeContent = ""; - return; - } - - this.settings.presetThemeName = selectedThemeFullName; - this.settingsService.getThemeContent(selectedTheme.url).subscribe(x => { - this.settings.presetThemeContent = x; - }); - } } diff --git a/src/Ombi/ClientApp/app/settings/emby/emby.component.html b/src/Ombi/ClientApp/app/settings/emby/emby.component.html index ec966acf5..18d42bb47 100644 --- a/src/Ombi/ClientApp/app/settings/emby/emby.component.html +++ b/src/Ombi/ClientApp/app/settings/emby/emby.component.html @@ -3,7 +3,7 @@
    - Emby Configuration + Emby/Jellyfin Configuration
    @@ -63,11 +63,28 @@
    + +
    + +
    + + Current URL: "{{server.serverHostname}}/#!/{{settings.isJellyfin ? ("itemdetails"): ("item/item")}}.html?id=1" + Current URL: "https://app.emby.media/#!/{{settings.isJellyfin ? ("itemdetails"): ("item/item")}}.html?id=1 +
    +
    +
    +
    + +
    +
    @@ -76,7 +93,7 @@
    - +
    @@ -88,4 +105,4 @@
    - \ No newline at end of file + diff --git a/src/Ombi/ClientApp/app/settings/emby/emby.component.ts b/src/Ombi/ClientApp/app/settings/emby/emby.component.ts index c2752a973..bc1d2bbb2 100644 --- a/src/Ombi/ClientApp/app/settings/emby/emby.component.ts +++ b/src/Ombi/ClientApp/app/settings/emby/emby.component.ts @@ -1,7 +1,7 @@ import { Component, OnInit } from "@angular/core"; import { IEmbyServer, IEmbySettings } from "../../interfaces"; -import { JobService, NotificationService, SettingsService, TesterService } from "../../services"; +import { EmbyService, JobService, NotificationService, SettingsService, TesterService } from "../../services"; @Component({ templateUrl: "./emby.component.html", @@ -9,16 +9,25 @@ import { JobService, NotificationService, SettingsService, TesterService } from export class EmbyComponent implements OnInit { public settings: IEmbySettings; + public hasDiscovered: boolean; constructor(private settingsService: SettingsService, private notificationService: NotificationService, private testerService: TesterService, - private jobService: JobService) { } + private jobService: JobService, + private embyService: EmbyService) { } public ngOnInit() { this.settingsService.getEmby().subscribe(x => this.settings = x); } + public async discoverServerInfo(server: IEmbyServer) { + const result = await this.embyService.getPublicInfo(server).toPromise(); + this.settings.isJellyfin = result.isJellyfin; + server.name = result.serverName; + this.hasDiscovered = true; + } + public addTab() { if (this.settings.servers == null) { this.settings.servers = []; diff --git a/src/Ombi/ClientApp/app/settings/failedrequests/failedrequest.component.html b/src/Ombi/ClientApp/app/settings/failedrequests/failedrequest.component.html new file mode 100644 index 000000000..898710199 --- /dev/null +++ b/src/Ombi/ClientApp/app/settings/failedrequests/failedrequest.component.html @@ -0,0 +1,26 @@ + + + + +
    "); + sb.Append(""); + sb.Append(""); + await ProcessAlbums(albums, sb); sb.Append(""); sb.Append("
    "); sb.Append("
    - {{ep.airDate | date: 'dd/MM/yyyy' }} + {{ep.airDate | amLocal | amDateFormat: 'L' }} - - - + + + + +
    -
    +
    - +
    {{ep.title}} - {{ep.airDate | date: 'dd/MM/yyyy' }} + + {{ep.airDate | amLocal | amDateFormat: 'L' }} + + {{ep.airDateDisplay }} Available diff --git a/src/Ombi/ClientApp/app/search/seriesinformation.component.scss b/src/Ombi/ClientApp/app/search/seriesinformation.component.scss index 1a1ac35b3..c7dca5e86 100644 --- a/src/Ombi/ClientApp/app/search/seriesinformation.component.scss +++ b/src/Ombi/ClientApp/app/search/seriesinformation.component.scss @@ -10,4 +10,16 @@ #requestFloatingBtn:hover { background-color: #555; /* Add a dark-grey background on hover */ + } + + #bannerimage { + width: 758px; + height: 140px; + background-color: black; + background-position: center; + padding-bottom:30px; + } + + .content-space { + padding-top: 10px; } \ No newline at end of file diff --git a/src/Ombi/ClientApp/app/search/seriesinformation.component.ts b/src/Ombi/ClientApp/app/search/seriesinformation.component.ts index f2eef3d57..6a918a69a 100644 --- a/src/Ombi/ClientApp/app/search/seriesinformation.component.ts +++ b/src/Ombi/ClientApp/app/search/seriesinformation.component.ts @@ -1,5 +1,4 @@ -import { Component, Input, OnInit} from "@angular/core"; -import "rxjs/add/operator/takeUntil"; +import { Component, Input, OnInit } from "@angular/core"; import { NotificationService } from "../services"; import { RequestService } from "../services"; @@ -9,6 +8,8 @@ import { INewSeasonRequests, IRequestEngineResult, ISeasonsViewModel, ITvRequest import { IEpisodesRequests } from "../interfaces"; import { ISearchTvResult } from "../interfaces"; +import { Subject } from "rxjs"; + @Component({ selector: "seriesinformation", templateUrl: "./seriesinformation.component.html", @@ -19,7 +20,7 @@ export class SeriesInformationComponent implements OnInit { public result: IRequestEngineResult; public series: ISearchTvResult; public requestedEpisodes: IEpisodesRequests[] = []; - + @Input() public tvRequested: Subject; @Input() private seriesId: number; constructor(private searchService: SearchService, private requestService: RequestService, private notificationService: NotificationService) { } @@ -39,30 +40,31 @@ export class SeriesInformationComponent implements OnInit { }); }); - if(!selected) { + if (!selected) { this.notificationService.error("You need to select some episodes!"); return; } this.series.requested = true; - const viewModel = { firstSeason: this.series.firstSeason, latestSeason: this.series.latestSeason, requestAll: this.series.requestAll, tvDbId: this.series.id}; + const viewModel = { firstSeason: this.series.firstSeason, latestSeason: this.series.latestSeason, requestAll: this.series.requestAll, tvDbId: this.series.id}; viewModel.seasons = []; this.series.seasonRequests.forEach((season) => { - const seasonsViewModel = {seasonNumber: season.seasonNumber, episodes: []}; + const seasonsViewModel = {seasonNumber: season.seasonNumber, episodes: []}; season.episodes.forEach(ep => { - if(!this.series.latestSeason || !this.series.requestAll || !this.series.firstSeason) { - if(ep.selected) { + if (!this.series.latestSeason || !this.series.requestAll || !this.series.firstSeason) { + if (ep.selected) { seasonsViewModel.episodes.push({episodeNumber: ep.episodeNumber}); } } }); - + viewModel.seasons.push(seasonsViewModel); }); this.requestService.requestTv(viewModel) .subscribe(x => { + this.tvRequested.next(); this.result = x as IRequestEngineResult; if (this.result.result) { this.notificationService.success( diff --git a/src/Ombi/ClientApp/app/search/tvsearch.component.html b/src/Ombi/ClientApp/app/search/tvsearch.component.html index ee5667f76..5975abb0d 100644 --- a/src/Ombi/ClientApp/app/search/tvsearch.component.html +++ b/src/Ombi/ClientApp/app/search/tvsearch.component.html @@ -1,11 +1,11 @@ 
    - + -
    -
    + + +
    - -
    -
    @@ -42,132 +40,128 @@
    {{ 'Search.NoResults' | translate }}
    - - - - {{ 'Search.TvShows.Results' | translate }} - - - -
    -
    - -
    -
    -
    +
    +
    + +
    +
    - poster +
    +
    +
    -
    -
    - +
    +
    + +

    {{node.title}} ({{node.firstAired | amLocal | amDateFormat: 'YYYY'}})

    + +
    + + + {{ 'Search.Movies.HomePage' | translate }} - - {{ 'Search.Movies.HomePage' | translate }} - - {{ 'Search.Movies.Trailer' | translate }} - {{node.data.status}} + + {{ 'Search.Movies.Trailer' | translate }} + + + {{node.status}} - {{ 'Search.TvShows.AirDate' | translate }} {{node.data.firstAired | date: 'dd/MM/yyyy'}} + {{ 'Search.TvShows.AirDate' | translate }} {{node.firstAired | amLocal | amDateFormat: 'L'}} + + {{node.network}} + + + {{ 'Common.Available' | translate }} + + {{ 'Common.PartlyAvailable' | translate }} - {{node.data.network}} - - {{ 'Common.Available' | translate }} - - {{ 'Common.PartlyAvailable' | translate }} - - -
    -
    -
    -

    {{node.data.overview}}

    +
    +
    +
    +

    {{node.overview}}

    +
    + + +
    + - -
    - - -
    - -
    +
    + +
    +
    + + + +
    +

    - - - -
    -
    -
    -
    -
    -
    - -
    - -
    -
    -
    - - - +
    +
    + +
    + +
    + +
    +
    +
    \ No newline at end of file + [issueCategory]="issueCategorySelected" [id]="issueRequestId" [providerId]="issueProviderId"> diff --git a/src/Ombi/ClientApp/app/search/tvsearch.component.ts b/src/Ombi/ClientApp/app/search/tvsearch.component.ts index 2a456cb6e..92d0d549a 100644 --- a/src/Ombi/ClientApp/app/search/tvsearch.component.ts +++ b/src/Ombi/ClientApp/app/search/tvsearch.component.ts @@ -1,15 +1,13 @@ -import { PlatformLocation } from "@angular/common"; +import { PlatformLocation } from "@angular/common"; import { Component, Input, OnInit } from "@angular/core"; import { DomSanitizer } from "@angular/platform-browser"; -import { Subject } from "rxjs/Subject"; +import { Subject } from "rxjs"; +import { debounceTime, distinctUntilChanged } from "rxjs/operators"; import { AuthService } from "../auth/auth.service"; +import { IIssueCategory, IRequestEngineResult, ISearchTvResult, ISeasonsViewModel, ITvRequestViewModel } from "../interfaces"; import { ImageService, NotificationService, RequestService, SearchService } from "../services"; -import { TreeNode } from "primeng/primeng"; -import { IRequestEngineResult } from "../interfaces"; -import { IIssueCategory, ISearchTvResult, ISeasonsViewModel, ITvRequestViewModel } from "../interfaces"; - @Component({ selector: "tv-search", templateUrl: "./tvsearch.component.html", @@ -19,7 +17,8 @@ export class TvSearchComponent implements OnInit { public searchText: string; public searchChanged = new Subject(); - public tvResults: TreeNode[]; + public tvResults: ISearchTvResult[]; + public tvRequested: Subject = new Subject(); public result: IRequestEngineResult; public searchApplied = false; public defaultPoster: string; @@ -32,57 +31,37 @@ export class TvSearchComponent implements OnInit { public issueProviderId: string; public issueCategorySelected: IIssueCategory; - constructor(private searchService: SearchService, private requestService: RequestService, - private notificationService: NotificationService, private authService: AuthService, - private imageService: ImageService, private sanitizer: DomSanitizer, - private readonly platformLocation: PlatformLocation) { + constructor( + private searchService: SearchService, private requestService: RequestService, + private notificationService: NotificationService, private authService: AuthService, + private imageService: ImageService, private sanitizer: DomSanitizer, + private readonly platformLocation: PlatformLocation) { - this.searchChanged - .debounceTime(600) // Wait Xms after the last event before emitting last event - .distinctUntilChanged() // only emit if value is different from previous value - .subscribe(x => { - this.searchText = x as string; - if (this.searchText === "") { - this.clearResults(); - return; - } - this.searchService.searchTvTreeNode(this.searchText) - .subscribe(x => { - this.tvResults = x; - this.searchApplied = true; - this.getExtraInfo(); - }); - }); + this.searchChanged.pipe( + debounceTime(600), // Wait Xms after the last event before emitting last event + distinctUntilChanged(), // only emit if value is different from previous value + ).subscribe(x => { + this.searchText = x as string; + if (this.searchText === "") { + this.clearResults(); + return; + } + this.searchService.searchTv(this.searchText) + .subscribe(x => { + this.tvResults = x; + this.searchApplied = true; + this.getExtraInfo(); + }); + }); this.defaultPoster = "../../../images/default_tv_poster.png"; const base = this.platformLocation.getBaseHrefFromDOM(); - if(base) { + if (base) { this.defaultPoster = "../../.." + base + "/images/default_tv_poster.png"; } } - public openClosestTab(el: any) { + public openClosestTab(node: ISearchTvResult,el: any) { el.preventDefault(); - const rowclass = "undefined ng-star-inserted"; - el = el.toElement || el.relatedTarget || el.target; - while (el.className !== rowclass) { - // Increment the loop to the parent node until we find the row we need - el = el.parentNode; - } - // At this point, the while loop has stopped and `el` represents the element that has - // the class you specified - - // Then we loop through the children to find the caret which we want to click - const caretright = "fa-caret-right"; - const caretdown = "fa-caret-down"; - for (const value of el.children) { - // the caret from the ui has 2 class selectors depending on if expanded or not - // we search for both since we want to still toggle the clicking - if (value.className.includes(caretright) || value.className.includes(caretdown)) { - // Then we tell JS to click the element even though we hid it from the UI - value.click(); - //Break from loop since we no longer need to continue looking - break; - } - } + node.open = !node.open; } public ngOnInit() { @@ -91,7 +70,7 @@ export class TvSearchComponent implements OnInit { this.result = { message: "", result: false, - errorMessage:"", + errorMessage: "", }; this.popularShows(); } @@ -138,16 +117,16 @@ export class TvSearchComponent implements OnInit { public getExtraInfo() { this.tvResults.forEach((val, index) => { - this.imageService.getTvBanner(val.data.id).subscribe(x => { - if(x) { - val.data.background = this.sanitizer. - bypassSecurityTrustStyle - ("url(" + x + ")"); + this.imageService.getTvBanner(val.id).subscribe(x => { + if (x) { + val.background = this.sanitizer. + bypassSecurityTrustStyle + ("url(" + x + ")"); } }); - this.searchService.getShowInformationTreeNode(val.data.id) + this.searchService.getShowInformation(val.id) .subscribe(x => { - if (x.data) { + if (x) { this.setDefaults(x); this.updateItem(val, x); } else { @@ -165,24 +144,25 @@ export class TvSearchComponent implements OnInit { if (this.authService.hasRole("admin") || this.authService.hasRole("AutoApproveMovie")) { searchResult.approved = true; } - - const viewModel = { firstSeason: searchResult.firstSeason, latestSeason: searchResult.latestSeason, requestAll: searchResult.requestAll, tvDbId: searchResult.id}; + + const viewModel = { firstSeason: searchResult.firstSeason, latestSeason: searchResult.latestSeason, requestAll: searchResult.requestAll, tvDbId: searchResult.id }; viewModel.seasons = []; searchResult.seasonRequests.forEach((season) => { - const seasonsViewModel = {seasonNumber: season.seasonNumber, episodes: []}; + const seasonsViewModel = { seasonNumber: season.seasonNumber, episodes: [] }; season.episodes.forEach(ep => { - if(!searchResult.latestSeason || !searchResult.requestAll || !searchResult.firstSeason) { - if(ep.requested) { - seasonsViewModel.episodes.push({episodeNumber: ep.episodeNumber}); + if (!searchResult.latestSeason || !searchResult.requestAll || !searchResult.firstSeason) { + if (ep.requested) { + seasonsViewModel.episodes.push({ episodeNumber: ep.episodeNumber }); } } }); - + viewModel.seasons.push(seasonsViewModel); }); this.requestService.requestTv(viewModel) .subscribe(x => { + this.tvRequested.next(); this.result = x; if (this.result.result) { this.notificationService.success( @@ -217,36 +197,37 @@ export class TvSearchComponent implements OnInit { public reportIssue(catId: IIssueCategory, req: ISearchTvResult) { this.issueRequestId = req.id; - this.issueRequestTitle = req.title; + const firstAiredDate = new Date(req.firstAired); + this.issueRequestTitle = req.title + ` (${firstAiredDate.getFullYear()})`; this.issueCategorySelected = catId; this.issuesBarVisible = true; this.issueProviderId = req.id.toString(); } - private updateItem(key: TreeNode, updated: TreeNode) { + private updateItem(key: ISearchTvResult, updated: ISearchTvResult) { const index = this.tvResults.indexOf(key, 0); if (index > -1) { // Update certain properties, otherwise we will loose some data - this.tvResults[index].data.title = updated.data.title; - this.tvResults[index].data.banner = updated.data.banner; - this.tvResults[index].data.imdbId = updated.data.imdbId; - this.tvResults[index].data.seasonRequests = updated.data.seasonRequests; - this.tvResults[index].data.seriesId = updated.data.seriesId; - this.tvResults[index].data.fullyAvailable = updated.data.fullyAvailable; - this.tvResults[index].data.backdrop = updated.data.backdrop; + this.tvResults[index].title = updated.title; + this.tvResults[index].banner = updated.banner; + this.tvResults[index].imdbId = updated.imdbId; + this.tvResults[index].seasonRequests = updated.seasonRequests; + this.tvResults[index].seriesId = updated.seriesId; + this.tvResults[index].fullyAvailable = updated.fullyAvailable; + this.tvResults[index].background = updated.banner; } } - private setDefaults(x: any) { - if (x.data.banner === null) { - x.data.banner = this.defaultPoster; - } - - if (x.data.imdbId === null) { - x.data.imdbId = "https://www.tvmaze.com/shows/" + x.data.seriesId; - } else { - x.data.imdbId = "http://www.imdb.com/title/" + x.data.imdbId + "/"; - } + private setDefaults(x: ISearchTvResult) { + if (x.banner === null) { + x.banner = this.defaultPoster; + } + + if (x.imdbId === null) { + x.imdbId = "https://www.tvmaze.com/shows/" + x.seriesId; + } else { + x.imdbId = "http://www.imdb.com/title/" + x.imdbId + "/"; + } } private clearResults() { diff --git a/src/Ombi/ClientApp/app/services/applications/couchpotato.service.ts b/src/Ombi/ClientApp/app/services/applications/couchpotato.service.ts index 667cd1b98..44fa409a4 100644 --- a/src/Ombi/ClientApp/app/services/applications/couchpotato.service.ts +++ b/src/Ombi/ClientApp/app/services/applications/couchpotato.service.ts @@ -1,7 +1,7 @@ -import { PlatformLocation } from "@angular/common"; +import { PlatformLocation } from "@angular/common"; import { HttpClient } from "@angular/common/http"; import { Injectable } from "@angular/core"; -import { Observable } from "rxjs/Rx"; +import { Observable } from "rxjs"; import { ServiceHelpers } from "../service.helpers"; diff --git a/src/Ombi/ClientApp/app/services/applications/emby.service.ts b/src/Ombi/ClientApp/app/services/applications/emby.service.ts index a8897043c..9b433f5b7 100644 --- a/src/Ombi/ClientApp/app/services/applications/emby.service.ts +++ b/src/Ombi/ClientApp/app/services/applications/emby.service.ts @@ -1,11 +1,11 @@ -import { PlatformLocation } from "@angular/common"; +import { PlatformLocation } from "@angular/common"; import { HttpClient } from "@angular/common/http"; import { Injectable } from "@angular/core"; -import { Observable } from "rxjs/Rx"; +import { Observable } from "rxjs"; import { ServiceHelpers } from "../service.helpers"; -import { IEmbySettings, IUsersModel } from "../../interfaces"; +import { IEmbyServer, IEmbySettings, IPublicInfo, IUsersModel } from "../../interfaces"; @Injectable() export class EmbyService extends ServiceHelpers { @@ -16,8 +16,13 @@ export class EmbyService extends ServiceHelpers { public logIn(settings: IEmbySettings): Observable { return this.http.post(`${this.url}`, JSON.stringify(settings), {headers: this.headers}); } + public getUsers(): Observable { return this.http.get(`${this.url}users`, {headers: this.headers}); } + + public getPublicInfo(server: IEmbyServer): Observable { + return this.http.post(`${this.url}info`, JSON.stringify(server), {headers: this.headers}); + } } diff --git a/src/Ombi/ClientApp/app/services/applications/index.ts b/src/Ombi/ClientApp/app/services/applications/index.ts index 9fe4a5403..295a53415 100644 --- a/src/Ombi/ClientApp/app/services/applications/index.ts +++ b/src/Ombi/ClientApp/app/services/applications/index.ts @@ -6,3 +6,4 @@ export * from "./sonarr.service"; export * from "./tester.service"; export * from "./plexoauth.service"; export * from "./plextv.service"; +export * from "./lidarr.service"; diff --git a/src/Ombi/ClientApp/app/services/applications/lidarr.service.ts b/src/Ombi/ClientApp/app/services/applications/lidarr.service.ts new file mode 100644 index 000000000..b0cdf5a86 --- /dev/null +++ b/src/Ombi/ClientApp/app/services/applications/lidarr.service.ts @@ -0,0 +1,33 @@ +import { PlatformLocation } from "@angular/common"; +import { HttpClient } from "@angular/common/http"; +import { Injectable } from "@angular/core"; +import { Observable } from "rxjs"; + +import { ILidarrProfile, ILidarrRootFolder, IProfiles } from "../../interfaces"; +import { ILidarrSettings } from "../../interfaces"; +import { ServiceHelpers } from "../service.helpers"; + +@Injectable() +export class LidarrService extends ServiceHelpers { + constructor(http: HttpClient, public platformLocation: PlatformLocation) { + super(http, "/api/v1/Lidarr", platformLocation); + } + + public getRootFolders(settings: ILidarrSettings): Observable { + return this.http.post(`${this.url}/RootFolders/`, JSON.stringify(settings), {headers: this.headers}); + } + public getQualityProfiles(settings: ILidarrSettings): Observable { + return this.http.post(`${this.url}/Profiles/`, JSON.stringify(settings), {headers: this.headers}); + } + + public getRootFoldersFromSettings(): Observable { + return this.http.get(`${this.url}/RootFolders/`, {headers: this.headers}); + } + public getQualityProfilesFromSettings(): Observable { + return this.http.get(`${this.url}/Profiles/`, {headers: this.headers}); + } + + public getMetadataProfiles(settings: ILidarrSettings): Observable { + return this.http.post(`${this.url}/Metadata/`, JSON.stringify(settings), {headers: this.headers}); + } +} diff --git a/src/Ombi/ClientApp/app/services/applications/plex.service.ts b/src/Ombi/ClientApp/app/services/applications/plex.service.ts index 296f79ddb..9f53d0e34 100644 --- a/src/Ombi/ClientApp/app/services/applications/plex.service.ts +++ b/src/Ombi/ClientApp/app/services/applications/plex.service.ts @@ -1,12 +1,12 @@ -import { PlatformLocation } from "@angular/common"; +import { PlatformLocation } from "@angular/common"; import { HttpClient } from "@angular/common/http"; import { Injectable } from "@angular/core"; -import { Observable } from "rxjs/Rx"; +import { Observable } from "rxjs"; import { ServiceHelpers } from "../service.helpers"; -import { IPlexAuthentication, IPlexLibResponse, IPlexOAuthViewModel,IPlexServer, IPlexServerViewModel, IUsersModel } from "../../interfaces"; +import { IPlexAuthentication, IPlexLibResponse, IPlexLibSimpleResponse, IPlexOAuthViewModel, IPlexServer, IPlexServerAddViewModel, IPlexServerViewModel, IPlexUserAddResponse, IPlexUserViewModel, IUsersModel } from "../../interfaces"; @Injectable() export class PlexService extends ServiceHelpers { @@ -22,10 +22,22 @@ export class PlexService extends ServiceHelpers { return this.http.post(`${this.url}servers`, JSON.stringify({ login, password }), {headers: this.headers}); } + public getServersFromSettings(): Observable { + return this.http.get(`${this.url}servers`, {headers: this.headers}); + } + public getLibraries(plexSettings: IPlexServer): Observable { return this.http.post(`${this.url}Libraries`, JSON.stringify(plexSettings), {headers: this.headers}); } + public getLibrariesFromSettings(machineId: string): Observable { + return this.http.get(`${this.url}Libraries/${machineId}`, {headers: this.headers}); + } + + public addUserToServer(user: IPlexUserViewModel): Observable { + return this.http.post(`${this.url}user`,JSON.stringify(user), {headers: this.headers}); + } + public getFriends(): Observable { return this.http.get(`${this.url}Friends`, {headers: this.headers}); } diff --git a/src/Ombi/ClientApp/app/services/applications/plexoauth.service.ts b/src/Ombi/ClientApp/app/services/applications/plexoauth.service.ts index 59a884714..7a1b495b5 100644 --- a/src/Ombi/ClientApp/app/services/applications/plexoauth.service.ts +++ b/src/Ombi/ClientApp/app/services/applications/plexoauth.service.ts @@ -1,8 +1,8 @@ -import { PlatformLocation } from "@angular/common"; +import { PlatformLocation } from "@angular/common"; import { HttpClient } from "@angular/common/http"; import { Injectable } from "@angular/core"; -import { Observable } from "rxjs/Rx"; +import { Observable } from "rxjs"; import { ServiceHelpers } from "../service.helpers"; diff --git a/src/Ombi/ClientApp/app/services/applications/plextv.service.ts b/src/Ombi/ClientApp/app/services/applications/plextv.service.ts index 58a756007..03a49e90c 100644 --- a/src/Ombi/ClientApp/app/services/applications/plextv.service.ts +++ b/src/Ombi/ClientApp/app/services/applications/plextv.service.ts @@ -2,19 +2,18 @@ import { HttpClient, HttpHeaders } from "@angular/common/http"; import { Injectable } from "@angular/core"; -import { Observable } from "rxjs/Rx"; +import { Observable } from "rxjs"; import { IPlexPin } from "../../interfaces"; @Injectable() export class PlexTvService { - + constructor(private http: HttpClient, public platformLocation: PlatformLocation) { - } public GetPin(clientId: string, applicationName: string): Observable { - const headers = new HttpHeaders({"Content-Type":"application/json", + const headers = new HttpHeaders({"Content-Type": "application/json", "X-Plex-Client-Identifier": clientId, "X-Plex-Product": applicationName, "X-Plex-Version": "3", diff --git a/src/Ombi/ClientApp/app/services/applications/radarr.service.ts b/src/Ombi/ClientApp/app/services/applications/radarr.service.ts index 2403b3c8e..140a59d28 100644 --- a/src/Ombi/ClientApp/app/services/applications/radarr.service.ts +++ b/src/Ombi/ClientApp/app/services/applications/radarr.service.ts @@ -1,7 +1,7 @@ -import { PlatformLocation } from "@angular/common"; +import { PlatformLocation } from "@angular/common"; import { HttpClient } from "@angular/common/http"; import { Injectable } from "@angular/core"; -import { Observable } from "rxjs/Rx"; +import { Observable } from "rxjs"; import { IRadarrProfile, IRadarrRootFolder } from "../../interfaces"; import { IRadarrSettings } from "../../interfaces"; diff --git a/src/Ombi/ClientApp/app/services/applications/sonarr.service.ts b/src/Ombi/ClientApp/app/services/applications/sonarr.service.ts index b6328ad83..bce47acf9 100644 --- a/src/Ombi/ClientApp/app/services/applications/sonarr.service.ts +++ b/src/Ombi/ClientApp/app/services/applications/sonarr.service.ts @@ -1,11 +1,11 @@ -import { PlatformLocation } from "@angular/common"; +import { PlatformLocation } from "@angular/common"; import { Injectable } from "@angular/core"; import { HttpClient } from "@angular/common/http"; -import { Observable } from "rxjs/Rx"; +import { Observable } from "rxjs"; import { ISonarrSettings } from "../../interfaces"; -import { ISonarrProfile, ISonarrRootFolder } from "../../interfaces"; +import { ILanguageProfiles, ISonarrProfile, ISonarrRootFolder } from "../../interfaces"; import { ServiceHelpers } from "../service.helpers"; @Injectable() @@ -27,4 +27,8 @@ export class SonarrService extends ServiceHelpers { public getQualityProfilesWithoutSettings(): Observable { return this.http.get(`${this.url}/Profiles/`, {headers: this.headers}); } + + public getV3LanguageProfiles(settings: ISonarrSettings): Observable { + return this.http.post(`${this.url}/v3/languageprofiles/`, JSON.stringify(settings), {headers: this.headers}); + } } diff --git a/src/Ombi/ClientApp/app/services/applications/tester.service.ts b/src/Ombi/ClientApp/app/services/applications/tester.service.ts index 640d6ec04..af619d583 100644 --- a/src/Ombi/ClientApp/app/services/applications/tester.service.ts +++ b/src/Ombi/ClientApp/app/services/applications/tester.service.ts @@ -1,8 +1,8 @@ -import { PlatformLocation } from "@angular/common"; +import { PlatformLocation } from "@angular/common"; import { Injectable } from "@angular/core"; import { HttpClient } from "@angular/common/http"; -import { Observable } from "rxjs/Rx"; +import { Observable } from "rxjs"; import { ServiceHelpers } from "../service.helpers"; @@ -11,6 +11,8 @@ import { IDiscordNotifcationSettings, IEmailNotificationSettings, IEmbyServer, + IGotifyNotificationSettings, + ILidarrSettings, IMattermostNotifcationSettings, IMobileNotificationTestSettings, INewsletterNotificationSettings, @@ -37,8 +39,13 @@ export class TesterService extends ServiceHelpers { public pushbulletTest(settings: IPushbulletNotificationSettings): Observable { return this.http.post(`${this.url}pushbullet`, JSON.stringify(settings), {headers: this.headers}); } + public pushoverTest(settings: IPushoverNotificationSettings): Observable { - return this.http.post(`${this.url}pushover`, JSON.stringify(settings), {headers: this.headers}); + return this.http.post(`${this.url}pushover`, JSON.stringify(settings), { headers: this.headers }); + } + + public gotifyTest(settings: IGotifyNotificationSettings): Observable { + return this.http.post(`${this.url}gotify`, JSON.stringify(settings), { headers: this.headers }); } public mattermostTest(settings: IMattermostNotifcationSettings): Observable { @@ -52,7 +59,7 @@ export class TesterService extends ServiceHelpers { public emailTest(settings: IEmailNotificationSettings): Observable { return this.http.post(`${this.url}email`, JSON.stringify(settings), {headers: this.headers}); } - + public plexTest(settings: IPlexServer): Observable { return this.http.post(`${this.url}plex`, JSON.stringify(settings), {headers: this.headers}); } @@ -65,13 +72,17 @@ export class TesterService extends ServiceHelpers { return this.http.post(`${this.url}radarr`, JSON.stringify(settings), {headers: this.headers}); } + public lidarrTest(settings: ILidarrSettings): Observable { + return this.http.post(`${this.url}lidarr`, JSON.stringify(settings), {headers: this.headers}); + } + public sonarrTest(settings: ISonarrSettings): Observable { return this.http.post(`${this.url}sonarr`, JSON.stringify(settings), {headers: this.headers}); - } + } public couchPotatoTest(settings: ICouchPotatoSettings): Observable { return this.http.post(`${this.url}couchpotato`, JSON.stringify(settings), {headers: this.headers}); - } + } public telegramTest(settings: ITelegramNotifcationSettings): Observable { return this.http.post(`${this.url}telegram`, JSON.stringify(settings), {headers: this.headers}); @@ -79,10 +90,12 @@ export class TesterService extends ServiceHelpers { public sickrageTest(settings: ISickRageSettings): Observable { return this.http.post(`${this.url}sickrage`, JSON.stringify(settings), {headers: this.headers}); - } + } + public newsletterTest(settings: INewsletterNotificationSettings): Observable { return this.http.post(`${this.url}newsletter`, JSON.stringify(settings), {headers: this.headers}); - } + } + public mobileNotificationTest(settings: IMobileNotificationTestSettings): Observable { return this.http.post(`${this.url}mobile`, JSON.stringify(settings), {headers: this.headers}); } diff --git a/src/Ombi/ClientApp/app/services/custompage.service.ts b/src/Ombi/ClientApp/app/services/custompage.service.ts new file mode 100644 index 000000000..1fc1ea028 --- /dev/null +++ b/src/Ombi/ClientApp/app/services/custompage.service.ts @@ -0,0 +1,25 @@ +import { PlatformLocation } from "@angular/common"; +import { HttpClient } from "@angular/common/http"; +import { Injectable } from "@angular/core"; +import { Observable } from "rxjs"; + +import { + ICustomPage, +} from "../interfaces"; + +import { ServiceHelpers } from "./service.helpers"; + +@Injectable() +export class CustomPageService extends ServiceHelpers { + constructor(public http: HttpClient, public platformLocation: PlatformLocation) { + super(http, "/api/v1/CustomPage", platformLocation); + } + + public getCustomPage(): Observable { + return this.http.get(this.url, {headers: this.headers}); + } + + public saveCustomPage(model: ICustomPage): Observable { + return this.http.post(this.url, model, {headers: this.headers}); + } +} diff --git a/src/Ombi/ClientApp/app/services/identity.service.ts b/src/Ombi/ClientApp/app/services/identity.service.ts index 812885caf..bce159ebe 100644 --- a/src/Ombi/ClientApp/app/services/identity.service.ts +++ b/src/Ombi/ClientApp/app/services/identity.service.ts @@ -1,10 +1,10 @@ -import { PlatformLocation } from "@angular/common"; +import { PlatformLocation } from "@angular/common"; import { Injectable } from "@angular/core"; import { HttpClient } from "@angular/common/http"; -import { Observable } from "rxjs/Rx"; +import { Observable } from "rxjs"; -import { ICheckbox, ICreateWizardUser, IIdentityResult, IResetPasswordToken, IUpdateLocalUser, IUser } from "../interfaces"; +import { ICheckbox, ICreateWizardUser, IIdentityResult, INotificationPreferences, IResetPasswordToken, IUpdateLocalUser, IUser, IWizardUserResult } from "../interfaces"; import { ServiceHelpers } from "./service.helpers"; @Injectable() @@ -12,14 +12,14 @@ export class IdentityService extends ServiceHelpers { constructor(http: HttpClient, public platformLocation: PlatformLocation) { super(http, "/api/v1/Identity/", platformLocation); } - public createWizardUser(user: ICreateWizardUser): Observable { - return this.http.post(`${this.url}Wizard/`, JSON.stringify(user), {headers: this.headers}); + public createWizardUser(user: ICreateWizardUser): Observable { + return this.http.post(`${this.url}Wizard/`, JSON.stringify(user), {headers: this.headers}); } public getUser(): Observable { return this.http.get(this.url, {headers: this.headers}); } - + public getAccessToken(): Observable { return this.http.get(`${this.url}accesstoken`, {headers: this.headers}); } @@ -43,6 +43,11 @@ export class IdentityService extends ServiceHelpers { public updateUser(user: IUser): Observable { return this.http.put(this.url, JSON.stringify(user), {headers: this.headers}); } + + public updateNotificationPreferences(pref: INotificationPreferences[]): Observable { + return this.http.post(`${this.url}NotificationPreferences`, JSON.stringify(pref), {headers: this.headers}); + } + public updateLocalUser(user: IUpdateLocalUser): Observable { return this.http.put(this.url + "local", JSON.stringify(user), {headers: this.headers}); } @@ -67,6 +72,13 @@ export class IdentityService extends ServiceHelpers { return this.http.post(`${this.url}welcomeEmail`, JSON.stringify(user), {headers: this.headers}); } + public getNotificationPreferences(): Observable { + return this.http.get(`${this.url}notificationpreferences`, {headers: this.headers}); + } + public getNotificationPreferencesForUser(userId: string): Observable { + return this.http.get(`${this.url}notificationpreferences/${userId}`, {headers: this.headers}); + } + public hasRole(role: string): boolean { const roles = localStorage.getItem("roles") as string[] | null; if (roles) { diff --git a/src/Ombi/ClientApp/app/services/image.service.ts b/src/Ombi/ClientApp/app/services/image.service.ts index a640ac4de..6307e7e3d 100644 --- a/src/Ombi/ClientApp/app/services/image.service.ts +++ b/src/Ombi/ClientApp/app/services/image.service.ts @@ -1,6 +1,6 @@ -import { PlatformLocation } from "@angular/common"; +import { PlatformLocation } from "@angular/common"; import { Injectable } from "@angular/core"; -import { Observable } from "rxjs/Rx"; +import { Observable } from "rxjs"; import { HttpClient } from "@angular/common/http"; @@ -20,21 +20,21 @@ export class ImageService extends ServiceHelpers { public getTvBanner(tvdbid: number): Observable { return this.http.get(`${this.url}tv/${tvdbid}`, {headers: this.headers}); } - + public getMoviePoster(movieDbId: string): Observable { return this.http.get(`${this.url}poster/movie/${movieDbId}`, { headers: this.headers }); } - + public getTvPoster(tvdbid: number): Observable { return this.http.get(`${this.url}poster/tv/${tvdbid}`, { headers: this.headers }); } - + public getMovieBackground(movieDbId: string): Observable { return this.http.get(`${this.url}background/movie/${movieDbId}`, { headers: this.headers }); } - + public getTvBackground(tvdbid: number): Observable { return this.http.get(`${this.url}background/tv/${tvdbid}`, { headers: this.headers }); } - + } diff --git a/src/Ombi/ClientApp/app/services/index.ts b/src/Ombi/ClientApp/app/services/index.ts index 59a299d3e..5065ce938 100644 --- a/src/Ombi/ClientApp/app/services/index.ts +++ b/src/Ombi/ClientApp/app/services/index.ts @@ -14,3 +14,6 @@ export * from "./issues.service"; export * from "./mobile.service"; export * from "./notificationMessage.service"; export * from "./recentlyAdded.service"; +export * from "./vote.service"; +export * from "./requestretry.service"; +export * from "./custompage.service"; diff --git a/src/Ombi/ClientApp/app/services/issues.service.ts b/src/Ombi/ClientApp/app/services/issues.service.ts index d72f5a842..41cbb4df1 100644 --- a/src/Ombi/ClientApp/app/services/issues.service.ts +++ b/src/Ombi/ClientApp/app/services/issues.service.ts @@ -1,10 +1,10 @@ -import { PlatformLocation } from "@angular/common"; +import { PlatformLocation } from "@angular/common"; import { Injectable } from "@angular/core"; import { HttpClient } from "@angular/common/http"; -import { Observable } from "rxjs/Rx"; +import { Observable } from "rxjs"; -import { IIssueCategory, IIssueComments,IIssueCount, IIssues, IIssuesChat, INewIssueComments, IssueStatus, IUpdateStatus } from "../interfaces"; +import { IIssueCategory, IIssueComments, IIssueCount, IIssues, IIssuesChat, INewIssueComments, IssueStatus, IUpdateStatus } from "../interfaces"; import { ServiceHelpers } from "./service.helpers"; @Injectable() @@ -40,10 +40,10 @@ export class IssuesService extends ServiceHelpers { public createIssue(issue: IIssues): Observable { return this.http.post(this.url, JSON.stringify(issue), {headers: this.headers}); } - + public getIssue(id: number): Observable { return this.http.get(`${this.url}${id}`, {headers: this.headers}); - } + } public getComments(id: number): Observable { return this.http.get(`${this.url}${id}/comments`, {headers: this.headers}); @@ -56,4 +56,8 @@ export class IssuesService extends ServiceHelpers { public updateStatus(model: IUpdateStatus): Observable { return this.http.post(`${this.url}status`, JSON.stringify(model), { headers: this.headers }); } + + public deleteComment(id: number): Observable { + return this.http.delete(`${this.url}comments/${id}`, { headers: this.headers }); + } } diff --git a/src/Ombi/ClientApp/app/services/job.service.ts b/src/Ombi/ClientApp/app/services/job.service.ts index 2fe940316..05425683f 100644 --- a/src/Ombi/ClientApp/app/services/job.service.ts +++ b/src/Ombi/ClientApp/app/services/job.service.ts @@ -1,8 +1,8 @@ -import { PlatformLocation } from "@angular/common"; +import { PlatformLocation } from "@angular/common"; import { Injectable } from "@angular/core"; import { HttpClient } from "@angular/common/http"; -import { Observable } from "rxjs/Rx"; +import { Observable } from "rxjs"; import { ServiceHelpers } from "./service.helpers"; diff --git a/src/Ombi/ClientApp/app/services/landingpage.service.ts b/src/Ombi/ClientApp/app/services/landingpage.service.ts index 6cac16037..22964ff71 100644 --- a/src/Ombi/ClientApp/app/services/landingpage.service.ts +++ b/src/Ombi/ClientApp/app/services/landingpage.service.ts @@ -1,6 +1,6 @@ -import { PlatformLocation } from "@angular/common"; +import { PlatformLocation } from "@angular/common"; import { Injectable } from "@angular/core"; -import { Observable } from "rxjs/Rx"; +import { Observable } from "rxjs"; import { HttpClient } from "@angular/common/http"; diff --git a/src/Ombi/ClientApp/app/services/mobile.service.ts b/src/Ombi/ClientApp/app/services/mobile.service.ts index 04d2a6b36..9ff9b947d 100644 --- a/src/Ombi/ClientApp/app/services/mobile.service.ts +++ b/src/Ombi/ClientApp/app/services/mobile.service.ts @@ -1,10 +1,10 @@ -import { PlatformLocation } from "@angular/common"; +import { PlatformLocation } from "@angular/common"; import { Injectable } from "@angular/core"; import { HttpClient } from "@angular/common/http"; -import { Observable } from "rxjs/Rx"; +import { Observable } from "rxjs"; -import { IMobileUsersViewModel } from "./../interfaces"; +import { IMobileUsersViewModel } from "../interfaces"; import { ServiceHelpers } from "./service.helpers"; @Injectable() @@ -15,4 +15,8 @@ export class MobileService extends ServiceHelpers { public getUserDeviceList(): Observable { return this.http.get(`${this.url}notification/`, {headers: this.headers}); } + + public deleteUser(userId: string): Observable { + return this.http.post(`${this.url}remove/`, userId, {headers: this.headers}); + } } diff --git a/src/Ombi/ClientApp/app/services/notificationMessage.service.ts b/src/Ombi/ClientApp/app/services/notificationMessage.service.ts index 239bcf17c..93727c5d2 100644 --- a/src/Ombi/ClientApp/app/services/notificationMessage.service.ts +++ b/src/Ombi/ClientApp/app/services/notificationMessage.service.ts @@ -1,10 +1,10 @@ -import { PlatformLocation } from "@angular/common"; +import { PlatformLocation } from "@angular/common"; import { Injectable } from "@angular/core"; import { HttpClient } from "@angular/common/http"; -import { Observable } from "rxjs/Rx"; +import { Observable } from "rxjs"; -import { IMassEmailModel } from "./../interfaces"; +import { IMassEmailModel } from "../interfaces"; import { ServiceHelpers } from "./service.helpers"; diff --git a/src/Ombi/ClientApp/app/services/recentlyAdded.service.ts b/src/Ombi/ClientApp/app/services/recentlyAdded.service.ts index 366c6e583..c062f973b 100644 --- a/src/Ombi/ClientApp/app/services/recentlyAdded.service.ts +++ b/src/Ombi/ClientApp/app/services/recentlyAdded.service.ts @@ -1,10 +1,10 @@ -import { PlatformLocation } from "@angular/common"; +import { PlatformLocation } from "@angular/common"; import { Injectable } from "@angular/core"; import { HttpClient } from "@angular/common/http"; -import { Observable } from "rxjs/Rx"; +import { Observable } from "rxjs"; -import { IRecentlyAddedMovies, IRecentlyAddedTvShows } from "./../interfaces"; +import { IRecentlyAddedMovies, IRecentlyAddedTvShows } from "../interfaces"; import { ServiceHelpers } from "./service.helpers"; @Injectable() @@ -18,7 +18,8 @@ export class RecentlyAddedService extends ServiceHelpers { public getRecentlyAddedTv(): Observable { return this.http.get(`${this. url}tv/`, {headers: this.headers}); - } + } + public getRecentlyAddedTvGrouped(): Observable { return this.http.get(`${this.url}tv/grouped`, {headers: this.headers}); } diff --git a/src/Ombi/ClientApp/app/services/request.service.ts b/src/Ombi/ClientApp/app/services/request.service.ts index e52ab3057..5353ea580 100644 --- a/src/Ombi/ClientApp/app/services/request.service.ts +++ b/src/Ombi/ClientApp/app/services/request.service.ts @@ -1,29 +1,43 @@ -import { PlatformLocation } from "@angular/common"; +import { PlatformLocation } from "@angular/common"; import { Injectable } from "@angular/core"; import { HttpClient } from "@angular/common/http"; -import { Observable } from "rxjs/Rx"; +import { Observable } from "rxjs"; import { TreeNode } from "primeng/primeng"; -import { IRequestEngineResult } from "../interfaces"; -import { IChildRequests, IFilter, IMovieRequestModel, IMovieRequests, IMovieUpdateModel, IRequestsViewModel, ITvRequests,ITvUpdateModel, OrderType } from "../interfaces"; +import { FilterType, IAlbumRequest, IAlbumRequestModel, IAlbumUpdateModel, IChildRequests, IDenyAlbumModel, IDenyMovieModel, IFilter, + IMovieRequestModel, IMovieRequests, IMovieUpdateModel, IRequestEngineResult, IRequestsViewModel, ITvDenyModel, ITvRequests, ITvUpdateModel, OrderType } from "../interfaces"; import { ITvRequestViewModel } from "../interfaces"; import { ServiceHelpers } from "./service.helpers"; +import { IRemainingRequests } from "../interfaces/IRemainingRequests"; + @Injectable() export class RequestService extends ServiceHelpers { constructor(http: HttpClient, public platformLocation: PlatformLocation) { super(http, "/api/v1/Request/", platformLocation); } + public getRemainingMovieRequests(): Observable { + return this.http.get(`${this.url}movie/remaining`, {headers: this.headers}); + } + + public getRemainingTvRequests(): Observable { + return this.http.get(`${this.url}tv/remaining`, {headers: this.headers}); + } + + public getRemainingMusicRequests(): Observable { + return this.http.get(`${this.url}music/remaining`, {headers: this.headers}); + } + public requestMovie(movie: IMovieRequestModel): Observable { return this.http.post(`${this.url}Movie/`, JSON.stringify(movie), {headers: this.headers}); } public getTotalMovies(): Observable { return this.http.get(`${this.url}Movie/total`, {headers: this.headers}); - } - + } + public getTotalTv(): Observable { return this.http.get(`${this.url}tv/total`, {headers: this.headers}); } @@ -36,7 +50,7 @@ export class RequestService extends ServiceHelpers { return this.http.post(`${this.url}Movie/Approve`, JSON.stringify(movie), {headers: this.headers}); } - public denyMovie(movie: IMovieUpdateModel): Observable { + public denyMovie(movie: IDenyMovieModel): Observable { return this.http.put(`${this.url}Movie/Deny`, JSON.stringify(movie), {headers: this.headers}); } @@ -64,8 +78,8 @@ export class RequestService extends ServiceHelpers { return this.http.put(`${this.url}movie/`, JSON.stringify(request), {headers: this.headers}); } - public getTvRequests(count: number, position: number): Observable { - return this.http.get(`${this.url}tv/${count}/${position}`, {headers: this.headers}); + public getTvRequests(count: number, position: number, order: OrderType, status: FilterType, availability: FilterType): Observable> { + return this.http.get>(`${this.url}tv/${count}/${position}/${order}/${status}/${availability}`, {headers: this.headers}); } public getTvRequestsTree(count: number, position: number): Observable { @@ -87,7 +101,7 @@ export class RequestService extends ServiceHelpers { public removeTvRequest(request: ITvRequests) { this.http.delete(`${this.url}tv/${request.id}`, {headers: this.headers}).subscribe(); } - + public markTvAvailable(movie: ITvUpdateModel): Observable { return this.http.post(`${this.url}tv/available`, JSON.stringify(movie), {headers: this.headers}); } @@ -102,11 +116,11 @@ export class RequestService extends ServiceHelpers { public updateChild(child: IChildRequests): Observable { return this.http.put(`${this.url}tv/child`, JSON.stringify(child), {headers: this.headers}); - } + } - public denyChild(child: ITvUpdateModel): Observable { + public denyChild(child: ITvDenyModel): Observable { return this.http.put(`${this.url}tv/deny`, JSON.stringify(child), {headers: this.headers}); - } + } public approveChild(child: ITvUpdateModel): Observable { return this.http.post(`${this.url}tv/approve`, JSON.stringify(child), {headers: this.headers}); @@ -114,17 +128,61 @@ export class RequestService extends ServiceHelpers { public deleteChild(child: IChildRequests): Observable { return this.http.delete(`${this.url}tv/child/${child.id}`, {headers: this.headers}); } - + public subscribeToMovie(requestId: number): Observable { return this.http.post(`${this.url}movie/subscribe/${requestId}`, {headers: this.headers}); - } + } public unSubscribeToMovie(requestId: number): Observable { return this.http.post(`${this.url}movie/unsubscribe/${requestId}`, {headers: this.headers}); } public subscribeToTv(requestId: number): Observable { return this.http.post(`${this.url}tv/subscribe/${requestId}`, {headers: this.headers}); - } + } public unSubscribeToTv(requestId: number): Observable { return this.http.post(`${this.url}tv/unsubscribe/${requestId}`, {headers: this.headers}); } + public setQualityProfile(requestId: number, qualityId: number): Observable { + return this.http.put(`${this.url}tv/quality/${requestId}/${qualityId}`, {headers: this.headers}); + } + public setRootFolder(requestId: number, rootFolderId: number): Observable { + return this.http.put(`${this.url}tv/root/${requestId}/${rootFolderId}`, {headers: this.headers}); + } + + // Music + public requestAlbum(Album: IAlbumRequestModel): Observable { + return this.http.post(`${this.url}music/`, JSON.stringify(Album), {headers: this.headers}); + } + + public getTotalAlbums(): Observable { + return this.http.get(`${this.url}music/total`, {headers: this.headers}); + } + + public approveAlbum(Album: IAlbumUpdateModel): Observable { + return this.http.post(`${this.url}music/Approve`, JSON.stringify(Album), {headers: this.headers}); + } + + public denyAlbum(Album: IDenyAlbumModel): Observable { + return this.http.put(`${this.url}music/Deny`, JSON.stringify(Album), {headers: this.headers}); + } + + public markAlbumAvailable(Album: IAlbumUpdateModel): Observable { + return this.http.post(`${this.url}music/available`, JSON.stringify(Album), {headers: this.headers}); + } + + public markAlbumUnavailable(Album: IAlbumUpdateModel): Observable { + return this.http.post(`${this.url}music/unavailable`, JSON.stringify(Album), {headers: this.headers}); + } + + public getAlbumRequests(count: number, position: number, order: OrderType, filter: IFilter): Observable> { + return this.http.get>(`${this.url}music/${count}/${position}/${order}/${filter.statusFilter}/${filter.availabilityFilter}`, {headers: this.headers}); + } + + public searchAlbumRequests(search: string): Observable { + return this.http.get(`${this.url}music/search/${search}`, {headers: this.headers}); + } + + public removeAlbumRequest(request: IAlbumRequest): any { + return this.http.delete(`${this.url}music/${request.id}`, {headers: this.headers}); + } + } diff --git a/src/Ombi/ClientApp/app/services/requestretry.service.ts b/src/Ombi/ClientApp/app/services/requestretry.service.ts new file mode 100644 index 000000000..e5c9cabe0 --- /dev/null +++ b/src/Ombi/ClientApp/app/services/requestretry.service.ts @@ -0,0 +1,21 @@ +import { PlatformLocation } from "@angular/common"; +import { Injectable } from "@angular/core"; + +import { HttpClient } from "@angular/common/http"; +import { Observable } from "rxjs"; + +import { IFailedRequestsViewModel } from "../interfaces"; +import { ServiceHelpers } from "./service.helpers"; + +@Injectable() +export class RequestRetryService extends ServiceHelpers { + constructor(http: HttpClient, public platformLocation: PlatformLocation) { + super(http, "/api/v1/requestretry/", platformLocation); + } + public getFailedRequests(): Observable { + return this.http.get(this.url, {headers: this.headers}); + } + public deleteFailedRequest(failedId: number): Observable { + return this.http.delete(`${this.url}/${failedId}`, {headers: this.headers}); + } +} diff --git a/src/Ombi/ClientApp/app/services/search.service.ts b/src/Ombi/ClientApp/app/services/search.service.ts index 522b7dddf..5379e23e7 100644 --- a/src/Ombi/ClientApp/app/services/search.service.ts +++ b/src/Ombi/ClientApp/app/services/search.service.ts @@ -1,12 +1,13 @@ -import { PlatformLocation } from "@angular/common"; +import { PlatformLocation } from "@angular/common"; import { Injectable } from "@angular/core"; import { HttpClient } from "@angular/common/http"; -import { Observable } from "rxjs/Rx"; +import { Observable } from "rxjs"; import { TreeNode } from "primeng/primeng"; import { ISearchMovieResult } from "../interfaces"; import { ISearchTvResult } from "../interfaces"; +import { ISearchAlbumResult, ISearchArtistResult } from "../interfaces/ISearchMusicResult"; import { ServiceHelpers } from "./service.helpers"; @Injectable() @@ -17,10 +18,15 @@ export class SearchService extends ServiceHelpers { // Movies public searchMovie(searchTerm: string): Observable { - return this.http.get(`${this.url}/Movie/` + searchTerm); + return this.http.get(`${this.url}/Movie/${searchTerm}`); } - public similarMovies(theMovieDbId: number): Observable { - return this.http.get(`${this.url}/Movie/${theMovieDbId}/similar`); + + public searchMovieWithRefined(searchTerm: string, year: number | undefined, langCode: string): Observable { + return this.http.post(`${this.url}/Movie/`, { searchTerm, year, languageCode: langCode }); + } + + public similarMovies(theMovieDbId: number, langCode: string): Observable { + return this.http.post(`${this.url}/Movie/similar`, {theMovieDbId, languageCode: langCode}); } public popularMovies(): Observable { @@ -39,33 +45,51 @@ export class SearchService extends ServiceHelpers { return this.http.get(`${this.url}/Movie/info/${theMovieDbId}`); } + public getMovieInformationWithRefined(theMovieDbId: number, langCode: string): Observable { + return this.http.post(`${this.url}/Movie/info`, { theMovieDbId, languageCode: langCode }); + } + + public searchMovieByActor(searchTerm: string, langCode: string): Observable { + return this.http.post(`${this.url}/Movie/Actor`, { searchTerm, languageCode: langCode }); + } + // TV public searchTv(searchTerm: string): Observable { - return this.http.get(`${this.url}/Tv/${searchTerm}`, {headers: this.headers}); + return this.http.get(`${this.url}/Tv/${searchTerm}`, { headers: this.headers }); } public searchTvTreeNode(searchTerm: string): Observable { - return this.http.get(`${this.url}/Tv/${searchTerm}/tree`, {headers: this.headers}); + return this.http.get(`${this.url}/Tv/${searchTerm}/tree`, { headers: this.headers }); } public getShowInformationTreeNode(theTvDbId: number): Observable { - return this.http.get(`${this.url}/Tv/info/${theTvDbId}/Tree`, {headers: this.headers}); + return this.http.get(`${this.url}/Tv/info/${theTvDbId}/Tree`, { headers: this.headers }); } public getShowInformation(theTvDbId: number): Observable { - return this.http.get(`${this.url}/Tv/info/${theTvDbId}`, {headers: this.headers}); + return this.http.get(`${this.url}/Tv/info/${theTvDbId}`, { headers: this.headers }); } - public popularTv(): Observable { - return this.http.get(`${this.url}/Tv/popular/tree`, {headers: this.headers}); + public popularTv(): Observable { + return this.http.get(`${this.url}/Tv/popular`, { headers: this.headers }); } - public mostWatchedTv(): Observable { - return this.http.get(`${this.url}/Tv/mostwatched/tree`, {headers: this.headers}); + public mostWatchedTv(): Observable { + return this.http.get(`${this.url}/Tv/mostwatched`, { headers: this.headers }); } - public anticipatedTv(): Observable { - return this.http.get(`${this.url}/Tv/anticipated/tree`, {headers: this.headers}); + public anticipatedTv(): Observable { + return this.http.get(`${this.url}/Tv/anticipated`, { headers: this.headers }); } - public trendingTv(): Observable { - return this.http.get(`${this.url}/Tv/trending/tree`, {headers: this.headers}); + public trendingTv(): Observable { + return this.http.get(`${this.url}/Tv/trending`, { headers: this.headers }); + } + // Music + public searchArtist(searchTerm: string): Observable { + return this.http.get(`${this.url}/Music/Artist/` + searchTerm); + } + public searchAlbum(searchTerm: string): Observable { + return this.http.get(`${this.url}/Music/Album/` + searchTerm); + } + public getAlbumsForArtist(foreignArtistId: string): Observable { + return this.http.get(`${this.url}/Music/Artist/Album/${foreignArtistId}`); } } diff --git a/src/Ombi/ClientApp/app/services/service.helpers.ts b/src/Ombi/ClientApp/app/services/service.helpers.ts index dc8c33414..c57eae464 100644 --- a/src/Ombi/ClientApp/app/services/service.helpers.ts +++ b/src/Ombi/ClientApp/app/services/service.helpers.ts @@ -1,6 +1,5 @@ import { PlatformLocation } from "@angular/common"; import { HttpClient, HttpHeaders } from "@angular/common/http"; -import "rxjs/add/observable/throw"; export class ServiceHelpers { diff --git a/src/Ombi/ClientApp/app/services/settings.service.ts b/src/Ombi/ClientApp/app/services/settings.service.ts index ff49e393c..a80cfd772 100644 --- a/src/Ombi/ClientApp/app/services/settings.service.ts +++ b/src/Ombi/ClientApp/app/services/settings.service.ts @@ -1,7 +1,7 @@ -import { PlatformLocation } from "@angular/common"; +import { PlatformLocation } from "@angular/common"; import { HttpClient } from "@angular/common/http"; import { Injectable } from "@angular/core"; -import { Observable } from "rxjs/Rx"; +import { Observable } from "rxjs"; import { IAbout, @@ -14,10 +14,12 @@ import { IDogNzbSettings, IEmailNotificationSettings, IEmbySettings, + IGotifyNotificationSettings, IIssueSettings, IJobSettings, IJobSettingsViewModel, ILandingPageSettings, + ILidarrSettings, IMattermostNotifcationSettings, IMobileNotifcationSettings, INewsletterNotificationSettings, @@ -30,9 +32,9 @@ import { ISlackNotificationSettings, ISonarrSettings, ITelegramNotifcationSettings, - IThemes, IUpdateSettings, IUserManagementSettings, + IVoteSettings, } from "../interfaces"; import { ServiceHelpers } from "./service.helpers"; @@ -51,6 +53,10 @@ export class SettingsService extends ServiceHelpers { return this.http.get(`${this.url}/Ombi/`, {headers: this.headers}); } + public getDefaultLanguage(): Observable { + return this.http.get(`${this.url}/defaultlanguage/`, {headers: this.headers}); + } + public saveOmbi(settings: IOmbiSettings): Observable { return this.http.post(`${this.url}/Ombi/`, JSON.stringify(settings), {headers: this.headers}); } @@ -91,6 +97,18 @@ export class SettingsService extends ServiceHelpers { return this.http.post(`${this.url}/Radarr`, JSON.stringify(settings), {headers: this.headers}); } + public getLidarr(): Observable { + return this.http.get(`${this.url}/Lidarr`, {headers: this.headers}); + } + + public lidarrEnabled(): Observable { + return this.http.get(`${this.url}/lidarrenabled`, {headers: this.headers}); + } + + public saveLidarr(settings: ILidarrSettings): Observable { + return this.http.post(`${this.url}/Lidarr`, JSON.stringify(settings), {headers: this.headers}); + } + public getAuthentication(): Observable { return this.http.get(`${this.url}/Authentication`, {headers: this.headers}); } @@ -121,14 +139,6 @@ export class SettingsService extends ServiceHelpers { return this.http.post(`${this.url}/customization`, JSON.stringify(settings), {headers: this.headers}); } - public getThemes(): Observable { - return this.http.get(`${this.url}/themes`, {headers: this.headers}); - } - - public getThemeContent(themeUrl: string): Observable { - return this.http.get(`${this.url}/themecontent?url=${themeUrl}`, {responseType: "text", headers: this.headers}); - } - public getEmailNotificationSettings(): Observable { return this.http.get(`${this.url}/notifications/email`, {headers: this.headers}); } @@ -173,6 +183,14 @@ export class SettingsService extends ServiceHelpers { .post(`${this.url}/notifications/pushover`, JSON.stringify(settings), {headers: this.headers}); } + public getGotifyNotificationSettings(): Observable { + return this.http.get(`${this.url}/notifications/gotify`, { headers: this.headers }); + } + public saveGotifyNotificationSettings(settings: IGotifyNotificationSettings): Observable { + return this.http + .post(`${this.url}/notifications/gotify`, JSON.stringify(settings), { headers: this.headers }); + } + public getSlackNotificationSettings(): Observable { return this.http.get(`${this.url}/notifications/slack`, {headers: this.headers}); } @@ -185,7 +203,7 @@ export class SettingsService extends ServiceHelpers { public getMobileNotificationSettings(): Observable { return this.http.get(`${this.url}/notifications/mobile`, {headers: this.headers}); } - + public saveMobileNotificationSettings(settings: IMobileNotifcationSettings): Observable { return this.http.post(`${this.url}/notifications/mobile`, JSON.stringify(settings), {headers: this.headers}); } @@ -228,7 +246,7 @@ export class SettingsService extends ServiceHelpers { public getTelegramNotificationSettings(): Observable { return this.http.get(`${this.url}/notifications/telegram`, {headers: this.headers}); - } + } public saveTelegramNotificationSettings(settings: ITelegramNotifcationSettings): Observable { return this.http @@ -242,13 +260,13 @@ export class SettingsService extends ServiceHelpers { public saveJobSettings(settings: IJobSettings): Observable { return this.http .post(`${this.url}/jobs`, JSON.stringify(settings), {headers: this.headers}); - } - + } + public testCron(body: ICronViewModelBody): Observable { return this.http .post(`${this.url}/testcron`, JSON.stringify(body), {headers: this.headers}); } - + public getSickRageSettings(): Observable { return this.http.get(`${this.url}/sickrage`, {headers: this.headers}); } @@ -271,13 +289,25 @@ export class SettingsService extends ServiceHelpers { .post(`${this.url}/issues`, JSON.stringify(settings), {headers: this.headers}); } + public getVoteSettings(): Observable { + return this.http.get(`${this.url}/vote`, {headers: this.headers}); + } + + public voteEnabled(): Observable { + return this.http.get(`${this.url}/voteenabled`, {headers: this.headers}); + } + + public saveVoteSettings(settings: IVoteSettings): Observable { + return this.http.post(`${this.url}/vote`, JSON.stringify(settings), {headers: this.headers}); + } + public getNewsletterSettings(): Observable { return this.http.get(`${this.url}/notifications/newsletter`, {headers: this.headers}); - } + } public updateNewsletterDatabase(): Observable { return this.http.post(`${this.url}/notifications/newsletterdatabase`, {headers: this.headers}); - } + } public saveNewsletterSettings(settings: INewsletterNotificationSettings): Observable { return this.http diff --git a/src/Ombi/ClientApp/app/services/status.service.ts b/src/Ombi/ClientApp/app/services/status.service.ts index 9e84dac4b..795fb1bf1 100644 --- a/src/Ombi/ClientApp/app/services/status.service.ts +++ b/src/Ombi/ClientApp/app/services/status.service.ts @@ -1,8 +1,8 @@ -import { PlatformLocation } from "@angular/common"; +import { PlatformLocation } from "@angular/common"; import { Injectable } from "@angular/core"; import { HttpClient } from "@angular/common/http"; -import { Observable } from "rxjs/Rx"; +import { Observable } from "rxjs"; import { ServiceHelpers } from "./service.helpers"; diff --git a/src/Ombi/ClientApp/app/services/vote.service.ts b/src/Ombi/ClientApp/app/services/vote.service.ts new file mode 100644 index 000000000..375f0fc33 --- /dev/null +++ b/src/Ombi/ClientApp/app/services/vote.service.ts @@ -0,0 +1,36 @@ +import { PlatformLocation } from "@angular/common"; +import { HttpClient } from "@angular/common/http"; +import { Injectable } from "@angular/core"; + +import { IVoteEngineResult, IVoteViewModel } from "../interfaces"; +import { ServiceHelpers } from "./service.helpers"; + +@Injectable() +export class VoteService extends ServiceHelpers { + constructor(public http: HttpClient, public platformLocation: PlatformLocation) { + super(http, "/api/v1/Vote/", platformLocation); + } + + public async getModel(): Promise { + return await this.http.get(`${this.url}`, {headers: this.headers}).toPromise(); + } + + public async upvoteMovie(requestId: number): Promise { + return await this.http.post(`${this.url}up/movie/${requestId}`, {headers: this.headers}).toPromise(); + } + public async upvoteTv(requestId: number): Promise { + return await this.http.post(`${this.url}up/tv/${requestId}`, {headers: this.headers}).toPromise(); + } + public async upvoteAlbum(requestId: number): Promise { + return await this.http.post(`${this.url}up/album/${requestId}`, {headers: this.headers}).toPromise(); + } + public async downvoteMovie(requestId: number): Promise { + return await this.http.post(`${this.url}down/movie/${requestId}`, {headers: this.headers}).toPromise(); + } + public async downvoteTv(requestId: number): Promise { + return await this.http.post(`${this.url}down/tv/${requestId}`, {headers: this.headers}).toPromise(); + } + public async downvoteAlbum(requestId: number): Promise { + return await this.http.post(`${this.url}down/album/${requestId}`, {headers: this.headers}).toPromise(); + } +} diff --git a/src/Ombi/ClientApp/app/settings/about/about.component.html b/src/Ombi/ClientApp/app/settings/about/about.component.html index e05677c0f..fc1efde3f 100644 --- a/src/Ombi/ClientApp/app/settings/about/about.component.html +++ b/src/Ombi/ClientApp/app/settings/about/about.component.html @@ -38,7 +38,7 @@ Discord
    - https://discord.gg/ + https://discord.gg/Sa7wNWb
    + + + + + + + + + + + + + + + + + + +
    TitleTypeRetry CountError DescriptionDelete
    + {{v.title}} + {{RequestType[v.type] | humanize}}{{v.retryCount}}
    diff --git a/src/Ombi/ClientApp/app/settings/failedrequests/failedrequests.component.ts b/src/Ombi/ClientApp/app/settings/failedrequests/failedrequests.component.ts new file mode 100644 index 000000000..a303ac713 --- /dev/null +++ b/src/Ombi/ClientApp/app/settings/failedrequests/failedrequests.component.ts @@ -0,0 +1,27 @@ +import { Component, OnInit } from "@angular/core"; +import { IFailedRequestsViewModel, RequestType } from "../../interfaces"; +import { RequestRetryService } from "../../services"; + +@Component({ + templateUrl: "./failedrequest.component.html", +}) +export class FailedRequestsComponent implements OnInit { + + public vm: IFailedRequestsViewModel[]; + public RequestType = RequestType; + + constructor(private retry: RequestRetryService) { } + + public ngOnInit() { + this.retry.getFailedRequests().subscribe(x => this.vm = x); + } + + public remove(failed: IFailedRequestsViewModel) { + this.retry.deleteFailedRequest(failed.failedId).subscribe(x => { + if(x) { + const index = this.vm.indexOf(failed); + this.vm.splice(index,1); + } + }); + } +} diff --git a/src/Ombi/ClientApp/app/settings/issues/issues.component.html b/src/Ombi/ClientApp/app/settings/issues/issues.component.html index e30d84e26..fa5988a79 100644 --- a/src/Ombi/ClientApp/app/settings/issues/issues.component.html +++ b/src/Ombi/ClientApp/app/settings/issues/issues.component.html @@ -18,6 +18,21 @@ +
    +
    + + +
    +
    + +
    + + +
    + +
    @@ -38,8 +53,8 @@
    - +
    diff --git a/src/Ombi/ClientApp/app/settings/issues/issues.component.ts b/src/Ombi/ClientApp/app/settings/issues/issues.component.ts index c8a85e8e1..cfe0bd65c 100644 --- a/src/Ombi/ClientApp/app/settings/issues/issues.component.ts +++ b/src/Ombi/ClientApp/app/settings/issues/issues.component.ts @@ -21,8 +21,10 @@ export class IssuesComponent implements OnInit { public ngOnInit() { this.settingsService.getIssueSettings().subscribe(x => { this.form = this.fb.group({ - enabled: [x.enabled], - enableInProgress: [x.enableInProgress], + enabled: [x.enabled], + enableInProgress: [x.enableInProgress], + deleteIssues: [x.deleteIssues], + daysAfterResolvedToDelete: [x.daysAfterResolvedToDelete], }); }); this.getCategories(); @@ -53,6 +55,11 @@ export class IssuesComponent implements OnInit { const settings = form.value; + if(settings.deleteIssues && settings.daysAfterResolvedToDelete <= 0) { + this.notificationService.error("You need to enter days greater than 0"); + return; + } + this.settingsService.saveIssueSettings(settings).subscribe(x => { if (x) { this.notificationService.success("Successfully saved the Issue settings"); diff --git a/src/Ombi/ClientApp/app/settings/jobs/jobs.component.html b/src/Ombi/ClientApp/app/settings/jobs/jobs.component.html index 947739d6f..0806fdcc8 100644 --- a/src/Ombi/ClientApp/app/settings/jobs/jobs.component.html +++ b/src/Ombi/ClientApp/app/settings/jobs/jobs.component.html @@ -7,7 +7,8 @@ Job Settings
    - Changes to any of the below requires you to restart Ombi. + Changes to any of the below requires you to restart Ombi. + You can generate valid CRON Expressions here: http://www.cronmaker.com/
    @@ -22,12 +23,19 @@
    - - - The Radarr Sync is required - -
    + + + The Radarr Sync is required + +
    +
    + + + The Lidarr Sync is required + +
    +
    @@ -41,6 +49,13 @@ The Automatic Update is required
    + +
    + + + The Retry Requests is required + +
    @@ -78,12 +93,26 @@
    -
    +
    The Newsletter is required
    + +
    + + + The Issues Purge is required + +
    + +
    + + + The Media Database Refresh is required + +
    @@ -93,9 +122,3 @@
    - - -
      -
    • {{item | date:'short'}}
    • -
    -
    diff --git a/src/Ombi/ClientApp/app/settings/jobs/jobs.component.ts b/src/Ombi/ClientApp/app/settings/jobs/jobs.component.ts index d0a7a8b83..1a543f885 100644 --- a/src/Ombi/ClientApp/app/settings/jobs/jobs.component.ts +++ b/src/Ombi/ClientApp/app/settings/jobs/jobs.component.ts @@ -3,8 +3,6 @@ import { FormBuilder, FormGroup, Validators } from "@angular/forms"; import { NotificationService, SettingsService } from "../../services"; -import { ICronTestModel } from "./../../interfaces"; - @Component({ templateUrl: "./jobs.component.html", }) @@ -13,8 +11,6 @@ export class JobsComponent implements OnInit { public form: FormGroup; public profilesRunning: boolean; - public testModel: ICronTestModel; - public displayTest: boolean; constructor(private readonly settingsService: SettingsService, private readonly fb: FormBuilder, @@ -34,15 +30,18 @@ export class JobsComponent implements OnInit { refreshMetadata: [x.refreshMetadata, Validators.required], newsletter: [x.newsletter, Validators.required], plexRecentlyAddedSync: [x.plexRecentlyAddedSync, Validators.required], + lidarrArtistSync: [x.lidarrArtistSync, Validators.required], + issuesPurge: [x.issuesPurge, Validators.required], + retryRequests: [x.retryRequests, Validators.required], + mediaDatabaseRefresh: [x.mediaDatabaseRefresh, Validators.required], }); }); } public testCron(expression: string) { this.settingsService.testCron({ expression }).subscribe(x => { - if(x.success) { - this.testModel = x; - this.displayTest = true; + if(x.success) { + this.notificationService.success("Cron is Valid"); } else { this.notificationService.error(x.message); } diff --git a/src/Ombi/ClientApp/app/settings/lidarr/lidarr.component.html b/src/Ombi/ClientApp/app/settings/lidarr/lidarr.component.html new file mode 100644 index 000000000..2a1643dc6 --- /dev/null +++ b/src/Ombi/ClientApp/app/settings/lidarr/lidarr.component.html @@ -0,0 +1,132 @@ + + +
    +
    + Lidarr Settings +
    + Advanced + +
    +
    +
    +
    +
    + + +
    +
    + + + +
    + + + +
    + +
    + + + + +
    + + +
    + + + + +
    +
    +
    + + + +
    +
    +
    + +
    + +
    +
    +
    +
    + +
    + +
    + + +
    +
    + +
    + +
    + + + +
    +
    + +
    + +
    + + + + +
    + +
    + +
    +
    + + +
    +
    + +
    +
    + + +
    +
    + +
    +
    + +
    +
    + + +
    +
    + +
    +
    +
    +
    +
    +
    diff --git a/src/Ombi/ClientApp/app/settings/lidarr/lidarr.component.ts b/src/Ombi/ClientApp/app/settings/lidarr/lidarr.component.ts new file mode 100644 index 000000000..45efdb792 --- /dev/null +++ b/src/Ombi/ClientApp/app/settings/lidarr/lidarr.component.ts @@ -0,0 +1,137 @@ +import { Component, OnInit } from "@angular/core"; +import { FormBuilder, FormGroup, Validators } from "@angular/forms"; + +import { ILidarrSettings, IMinimumAvailability, IProfiles, IRadarrProfile, IRadarrRootFolder } from "../../interfaces"; +import { LidarrService, TesterService } from "../../services"; +import { NotificationService } from "../../services"; +import { SettingsService } from "../../services"; + +@Component({ + templateUrl: "./lidarr.component.html", +}) +export class LidarrComponent implements OnInit { + + public qualities: IRadarrProfile[]; + public metadataProfiles: IProfiles[]; + public rootFolders: IRadarrRootFolder[]; + public minimumAvailabilityOptions: IMinimumAvailability[]; + public profilesRunning: boolean; + public rootFoldersRunning: boolean; + public metadataRunning: boolean; + public advanced = false; + public form: FormGroup; + + constructor(private settingsService: SettingsService, + private lidarrService: LidarrService, + private notificationService: NotificationService, + private fb: FormBuilder, + private testerService: TesterService) { } + + public ngOnInit() { + this.settingsService.getLidarr() + .subscribe(x => { + + this.form = this.fb.group({ + enabled: [x.enabled], + apiKey: [x.apiKey, [Validators.required]], + defaultQualityProfile: [x.defaultQualityProfile, [Validators.required]], + defaultRootPath: [x.defaultRootPath, [Validators.required]], + ssl: [x.ssl], + subDir: [x.subDir], + ip: [x.ip, [Validators.required]], + port: [x.port, [Validators.required]], + albumFolder: [x.albumFolder], + metadataProfileId: [x.metadataProfileId, [Validators.required]], + addOnly: [x.addOnly], + }); + + if (x.defaultQualityProfile) { + this.getProfiles(this.form); + } + if (x.defaultRootPath) { + this.getRootFolders(this.form); + } + if (x.metadataProfileId) { + this.getMetadataProfiles(this.form); + } + }); + + this.qualities = []; + this.qualities.push({ name: "Please Select", id: -1 }); + + this.rootFolders = []; + this.rootFolders.push({ path: "Please Select", id: -1 }); + + this.metadataProfiles = []; + this.metadataProfiles.push({ name: "Please Select", id: -1 }); + } + + public getProfiles(form: FormGroup) { + this.profilesRunning = true; + this.lidarrService.getQualityProfiles(form.value).subscribe(x => { + this.qualities = x; + this.qualities.unshift({ name: "Please Select", id: -1 }); + + this.profilesRunning = false; + this.notificationService.success("Successfully retrieved the Quality Profiles"); + }); + } + + public getRootFolders(form: FormGroup) { + this.rootFoldersRunning = true; + this.lidarrService.getRootFolders(form.value).subscribe(x => { + this.rootFolders = x; + this.rootFolders.unshift({ path: "Please Select", id: -1 }); + + this.rootFoldersRunning = false; + this.notificationService.success("Successfully retrieved the Root Folders"); + }); + } + + public getMetadataProfiles(form: FormGroup) { + this.metadataRunning = true; + this.lidarrService.getMetadataProfiles(form.value).subscribe(x => { + this.metadataProfiles = x; + this.metadataProfiles.unshift({ name: "Please Select", id: -1 }); + + this.metadataRunning = false; + this.notificationService.success("Successfully retrieved the Metadata profiles"); + }); + } + + public test(form: FormGroup) { + if (form.invalid) { + this.notificationService.error("Please check your entered values"); + return; + } + const settings = form.value; + this.testerService.lidarrTest(settings).subscribe(x => { + if (x === true) { + this.notificationService.success("Successfully connected to Lidarr!"); + } else { + this.notificationService.error("We could not connect to Lidarr!"); + } + }); + } + + public onSubmit(form: FormGroup) { + if (form.invalid) { + this.notificationService.error("Please check your entered values"); + return; + } + if (form.controls.defaultQualityProfile.value === "-1" || form.controls.defaultRootPath.value === "Please Select") { + this.notificationService.error("Please check your entered values"); + return; + } + + const settings = form.value; + this.settingsService.saveLidarr(settings).subscribe(x => { + if (x) { + this.notificationService.success("Successfully saved Lidarr settings"); + } else { + this.notificationService.success("There was an error when saving the Lidarr settings"); + } + }); + + } +} diff --git a/src/Ombi/ClientApp/app/settings/massemail/massemail.component.html b/src/Ombi/ClientApp/app/settings/massemail/massemail.component.html index e66d83a18..5c51c68ca 100644 --- a/src/Ombi/ClientApp/app/settings/massemail/massemail.component.html +++ b/src/Ombi/ClientApp/app/settings/massemail/massemail.component.html @@ -39,7 +39,7 @@
    - +
    diff --git a/src/Ombi/ClientApp/app/settings/massemail/massemail.component.ts b/src/Ombi/ClientApp/app/settings/massemail/massemail.component.ts index a80f7adfe..91693103f 100644 --- a/src/Ombi/ClientApp/app/settings/massemail/massemail.component.ts +++ b/src/Ombi/ClientApp/app/settings/massemail/massemail.component.ts @@ -38,10 +38,6 @@ export class MassEmailComponent implements OnInit { this.users.forEach(u => u.selected = !u.selected); } - public selectSingleUser(user: IMassEmailUserModel) { - user.selected = !user.selected; - } - public send() { if(!this.subject) { this.missingSubject = true; diff --git a/src/Ombi/ClientApp/app/settings/notifications/discord.component.ts b/src/Ombi/ClientApp/app/settings/notifications/discord.component.ts index d2fee12ee..bbd43e974 100644 --- a/src/Ombi/ClientApp/app/settings/notifications/discord.component.ts +++ b/src/Ombi/ClientApp/app/settings/notifications/discord.component.ts @@ -39,7 +39,7 @@ export class DiscordComponent implements OnInit { return; } - const settings = form.value; + const settings = form.value; settings.notificationTemplates = this.templates; this.settingsService.saveDiscordNotificationSettings(settings).subscribe(x => { diff --git a/src/Ombi/ClientApp/app/settings/notifications/emailnotification.component.ts b/src/Ombi/ClientApp/app/settings/notifications/emailnotification.component.ts index ec0428c75..f67828afb 100644 --- a/src/Ombi/ClientApp/app/settings/notifications/emailnotification.component.ts +++ b/src/Ombi/ClientApp/app/settings/notifications/emailnotification.component.ts @@ -54,7 +54,7 @@ export class EmailNotificationComponent implements OnInit { return; } - const settings = form.value; + const settings = form.value; settings.notificationTemplates = this.templates; this.settingsService.saveEmailNotificationSettings(settings).subscribe(x => { diff --git a/src/Ombi/ClientApp/app/settings/notifications/gotify.component.html b/src/Ombi/ClientApp/app/settings/notifications/gotify.component.html new file mode 100644 index 000000000..9148cb880 --- /dev/null +++ b/src/Ombi/ClientApp/app/settings/notifications/gotify.component.html @@ -0,0 +1,67 @@ + + +
    +
    + Gotify Notifications +
    +
    + +
    +
    + + +
    +
    + +
    + + + + The Base URL is required +
    + +
    + + + + The Application Token is required +
    + +
    + +
    + +
    +
    + + +
    +
    + +
    +
    + + + +
    +
    + +
    +
    +
    +
    + + +
    + +
    +
    +
    \ No newline at end of file diff --git a/src/Ombi/ClientApp/app/settings/notifications/gotify.component.ts b/src/Ombi/ClientApp/app/settings/notifications/gotify.component.ts new file mode 100644 index 000000000..f6c08d41b --- /dev/null +++ b/src/Ombi/ClientApp/app/settings/notifications/gotify.component.ts @@ -0,0 +1,68 @@ +import { Component, OnInit } from "@angular/core"; +import { FormBuilder, FormGroup, Validators } from "@angular/forms"; + +import { IGotifyNotificationSettings, INotificationTemplates, NotificationType } from "../../interfaces"; +import { TesterService } from "../../services"; +import { NotificationService } from "../../services"; +import { SettingsService } from "../../services"; + +@Component({ + templateUrl: "./gotify.component.html", +}) +export class GotifyComponent implements OnInit { + public NotificationType = NotificationType; + public templates: INotificationTemplates[]; + public form: FormGroup; + + constructor(private settingsService: SettingsService, + private notificationService: NotificationService, + private fb: FormBuilder, + private testerService: TesterService) { } + + public ngOnInit() { + this.settingsService.getGotifyNotificationSettings().subscribe(x => { + this.templates = x.notificationTemplates; + + this.form = this.fb.group({ + enabled: [x.enabled], + baseUrl: [x.baseUrl, [Validators.required]], + applicationToken: [x.applicationToken, [Validators.required]], + priority: [x.priority], + }); + }); + } + + public onSubmit(form: FormGroup) { + if (form.invalid) { + this.notificationService.error("Please check your entered values"); + return; + } + + const settings = form.value; + settings.notificationTemplates = this.templates; + + this.settingsService.saveGotifyNotificationSettings(settings).subscribe(x => { + if (x) { + this.notificationService.success("Successfully saved the Gotify settings"); + } else { + this.notificationService.success("There was an error when saving the Gotify settings"); + } + }); + + } + + public test(form: FormGroup) { + if (form.invalid) { + this.notificationService.error("Please check your entered values"); + return; + } + + this.testerService.gotifyTest(form.value).subscribe(x => { + if (x) { + this.notificationService.success("Successfully sent a Gotify message"); + } else { + this.notificationService.error("There was an error when sending the Gotify message. Please check your settings"); + } + }); + } +} diff --git a/src/Ombi/ClientApp/app/settings/notifications/mattermost.component.ts b/src/Ombi/ClientApp/app/settings/notifications/mattermost.component.ts index cb0bf81c0..65a33a4f3 100644 --- a/src/Ombi/ClientApp/app/settings/notifications/mattermost.component.ts +++ b/src/Ombi/ClientApp/app/settings/notifications/mattermost.component.ts @@ -29,7 +29,7 @@ export class MattermostComponent implements OnInit { username: [x.username], webhookUrl: [x.webhookUrl, [Validators.required]], channel: [x.channel], - iconUrl:[x.iconUrl], + iconUrl: [x.iconUrl], }); }); @@ -41,7 +41,7 @@ export class MattermostComponent implements OnInit { return; } - const settings = form.value; + const settings = form.value; settings.notificationTemplates = this.templates; this.settingsService.saveMattermostNotificationSettings(settings).subscribe(x => { diff --git a/src/Ombi/ClientApp/app/settings/notifications/mobile.component.html b/src/Ombi/ClientApp/app/settings/notifications/mobile.component.html index 5c82e03d5..2b7ea9b2c 100644 --- a/src/Ombi/ClientApp/app/settings/notifications/mobile.component.html +++ b/src/Ombi/ClientApp/app/settings/notifications/mobile.component.html @@ -35,7 +35,7 @@
    - +
    + +
    +
    + +
    +
    diff --git a/src/Ombi/ClientApp/app/settings/notifications/newsletter.component.ts b/src/Ombi/ClientApp/app/settings/notifications/newsletter.component.ts index 7154543fc..eae7176e2 100644 --- a/src/Ombi/ClientApp/app/settings/notifications/newsletter.component.ts +++ b/src/Ombi/ClientApp/app/settings/notifications/newsletter.component.ts @@ -2,7 +2,7 @@ import { INewsletterNotificationSettings, NotificationType } from "../../interfaces"; import { JobService, NotificationService, SettingsService } from "../../services"; -import { TesterService } from "./../../services/applications/tester.service"; +import { TesterService } from "../../services/applications/tester.service"; @Component({ templateUrl: "./newsletter.component.html", @@ -49,12 +49,12 @@ export class NewsletterComponent implements OnInit { }); } - public addEmail() { + public addEmail() { - if(this.emailToAdd) { + if (this.emailToAdd) { const emailRegex = "[a-zA-Z0-9.-_]{1,}@[a-zA-Z.-]{2,}[.]{1}[a-zA-Z]{2,}"; const match = this.emailToAdd.match(emailRegex)!; - if(match && match.length > 0) { + if (match && match.length > 0) { this.settings.externalEmails.push(this.emailToAdd); this.emailToAdd = ""; } else { diff --git a/src/Ombi/ClientApp/app/settings/notifications/notificationtemplate.component.html b/src/Ombi/ClientApp/app/settings/notifications/notificationtemplate.component.html index 3b8afe1ef..1fd475a38 100644 --- a/src/Ombi/ClientApp/app/settings/notifications/notificationtemplate.component.html +++ b/src/Ombi/ClientApp/app/settings/notifications/notificationtemplate.component.html @@ -6,7 +6,7 @@ -
    +
    diff --git a/src/Ombi/ClientApp/app/settings/notifications/notificationtemplate.component.scss b/src/Ombi/ClientApp/app/settings/notifications/notificationtemplate.component.scss new file mode 100644 index 000000000..fa0219742 --- /dev/null +++ b/src/Ombi/ClientApp/app/settings/notifications/notificationtemplate.component.scss @@ -0,0 +1,8 @@ +::ng-deep ngb-accordion > div.card { + color:white; + padding-top: 0px; +} + +::ng-deep ngb-accordion > div.card > div.card-header { + padding:0px; +} diff --git a/src/Ombi/ClientApp/app/settings/notifications/notificationtemplate.component.ts b/src/Ombi/ClientApp/app/settings/notifications/notificationtemplate.component.ts index 223da9334..2cf56a80d 100644 --- a/src/Ombi/ClientApp/app/settings/notifications/notificationtemplate.component.ts +++ b/src/Ombi/ClientApp/app/settings/notifications/notificationtemplate.component.ts @@ -5,6 +5,7 @@ import { INotificationTemplates, NotificationType } from "../../interfaces"; @Component({ selector:"notification-templates", templateUrl: "./notificationtemplate.component.html", + styleUrls: ["./notificationtemplate.component.scss"], }) export class NotificationTemplate { @Input() public templates: INotificationTemplates[]; diff --git a/src/Ombi/ClientApp/app/settings/notifications/pushbullet.component.ts b/src/Ombi/ClientApp/app/settings/notifications/pushbullet.component.ts index af681e692..d65a0e3b5 100644 --- a/src/Ombi/ClientApp/app/settings/notifications/pushbullet.component.ts +++ b/src/Ombi/ClientApp/app/settings/notifications/pushbullet.component.ts @@ -37,7 +37,7 @@ export class PushbulletComponent implements OnInit { return; } - const settings = form.value; + const settings = form.value; settings.notificationTemplates = this.templates; this.settingsService.savePushbulletNotificationSettings(settings).subscribe(x => { diff --git a/src/Ombi/ClientApp/app/settings/notifications/pushover.component.html b/src/Ombi/ClientApp/app/settings/notifications/pushover.component.html index b56467c9f..499263dec 100644 --- a/src/Ombi/ClientApp/app/settings/notifications/pushover.component.html +++ b/src/Ombi/ClientApp/app/settings/notifications/pushover.component.html @@ -28,6 +28,48 @@
    +
    + +
    + +
    +
    + +
    + +
    + +
    +
    +
    diff --git a/src/Ombi/ClientApp/app/settings/notifications/pushover.component.ts b/src/Ombi/ClientApp/app/settings/notifications/pushover.component.ts index 51acb5d31..64f339192 100644 --- a/src/Ombi/ClientApp/app/settings/notifications/pushover.component.ts +++ b/src/Ombi/ClientApp/app/settings/notifications/pushover.component.ts @@ -27,6 +27,8 @@ export class PushoverComponent implements OnInit { enabled: [x.enabled], userToken: [x.userToken], accessToken: [x.accessToken, [Validators.required]], + priority: [x.priority], + sound: [x.sound], }); }); } @@ -37,7 +39,7 @@ export class PushoverComponent implements OnInit { return; } - const settings = form.value; + const settings = form.value; settings.notificationTemplates = this.templates; this.settingsService.savePushoverNotificationSettings(settings).subscribe(x => { diff --git a/src/Ombi/ClientApp/app/settings/notifications/slack.component.ts b/src/Ombi/ClientApp/app/settings/notifications/slack.component.ts index e424d8c6a..7ea53d0fb 100644 --- a/src/Ombi/ClientApp/app/settings/notifications/slack.component.ts +++ b/src/Ombi/ClientApp/app/settings/notifications/slack.component.ts @@ -41,7 +41,7 @@ export class SlackComponent implements OnInit { return; } - const settings = form.value; + const settings = form.value; if (settings.iconEmoji && settings.iconUrl) { this.notificationService.error("You cannot have a Emoji icon and a URL icon"); @@ -65,7 +65,7 @@ export class SlackComponent implements OnInit { return; } - const settings = form.value; + const settings = form.value; if (settings.iconEmoji && settings.iconUrl) { this.notificationService.error("You cannot have a Emoji icon and a URL icon"); diff --git a/src/Ombi/ClientApp/app/settings/notifications/telegram.component.ts b/src/Ombi/ClientApp/app/settings/notifications/telegram.component.ts index 0c9965e8e..7d216901b 100644 --- a/src/Ombi/ClientApp/app/settings/notifications/telegram.component.ts +++ b/src/Ombi/ClientApp/app/settings/notifications/telegram.component.ts @@ -40,7 +40,7 @@ export class TelegramComponent implements OnInit { return; } - const settings = form.value; + const settings = form.value; settings.notificationTemplates = this.templates; this.settingsService.saveTelegramNotificationSettings(settings).subscribe(x => { diff --git a/src/Ombi/ClientApp/app/settings/ombi/ombi.component.html b/src/Ombi/ClientApp/app/settings/ombi/ombi.component.html index 3be162a9d..478817d6b 100644 --- a/src/Ombi/ClientApp/app/settings/ombi/ombi.component.html +++ b/src/Ombi/ClientApp/app/settings/ombi/ombi.component.html @@ -22,64 +22,80 @@
    -->
    - -
    - -
    - -
    -
    -
    - -
    - - -
    -
    -
    - -
    -
    +
    + +
    +
    -
    -
    -
    -
    - - +
    + +
    + + +
    +
    +
    + +
    +
    +
    +
    -
    +
    -
    -
    - - +
    +
    + + +
    -
    - -
    -
    - - +
    +
    + + +
    -
    -
    -
    - - -
    -
    -
    -
    - +
    +
    + + +
    +
    + +
    +
    + + +
    +
    + +
    + +
    + +
    +
    + +
    +
    + +
    -
    - + \ No newline at end of file diff --git a/src/Ombi/ClientApp/app/settings/ombi/ombi.component.ts b/src/Ombi/ClientApp/app/settings/ombi/ombi.component.ts index d1832d7de..4aec1a57c 100644 --- a/src/Ombi/ClientApp/app/settings/ombi/ombi.component.ts +++ b/src/Ombi/ClientApp/app/settings/ombi/ombi.component.ts @@ -1,16 +1,19 @@ import { Component, OnInit } from "@angular/core"; import { FormBuilder, FormGroup } from "@angular/forms"; -import { IOmbiSettings } from "../../interfaces"; +import { ILanguageRefine, IOmbiSettings } from "../../interfaces"; import { NotificationService } from "../../services"; import { SettingsService } from "../../services"; +import * as languageData from "../../../other/iso-lang.json"; + @Component({ templateUrl: "./ombi.component.html", }) export class OmbiComponent implements OnInit { public form: FormGroup; + public langauges: ILanguageRefine[]; constructor(private settingsService: SettingsService, private notificationService: NotificationService, @@ -25,8 +28,10 @@ export class OmbiComponent implements OnInit { baseUrl: [x.baseUrl], doNotSendNotificationsForAutoApprove: [x.doNotSendNotificationsForAutoApprove], hideRequestsUsers: [x.hideRequestsUsers], + defaultLanguageCode: [x.defaultLanguageCode], }); }); + this.langauges = languageData; } public refreshApiKey() { @@ -41,9 +46,9 @@ export class OmbiComponent implements OnInit { return; } - const result = form.value; - if(result.baseUrl && result.baseUrl.length > 0) { - if(!result.baseUrl.startsWith("/")) { + const result = form.value; + if (result.baseUrl && result.baseUrl.length > 0) { + if (!result.baseUrl.startsWith("/")) { this.notificationService.error("Please ensure your base url starts with a '/'"); return; } diff --git a/src/Ombi/ClientApp/app/settings/plex/plex.component.ts b/src/Ombi/ClientApp/app/settings/plex/plex.component.ts index 23bd74225..df09e8167 100644 --- a/src/Ombi/ClientApp/app/settings/plex/plex.component.ts +++ b/src/Ombi/ClientApp/app/settings/plex/plex.component.ts @@ -1,10 +1,8 @@ -import { Component, OnDestroy, OnInit } from "@angular/core"; -import "rxjs/add/operator/takeUntil"; -import { Subject } from "rxjs/Subject"; - -import { IPlexServerResponse, IPlexServerViewModel } from "../../interfaces"; -import { IPlexLibrariesSettings, IPlexServer, IPlexSettings } from "../../interfaces"; +import { Component, OnDestroy, OnInit } from "@angular/core"; +import { Subject } from "rxjs"; +import { takeUntil } from "rxjs/operators"; +import { IPlexLibrariesSettings, IPlexServer, IPlexServerResponse, IPlexServerViewModel, IPlexSettings } from "../../interfaces"; import { JobService, NotificationService, PlexService, SettingsService, TesterService } from "../../services"; @Component({ @@ -21,11 +19,12 @@ export class PlexComponent implements OnInit, OnDestroy { private subscriptions = new Subject(); - constructor(private settingsService: SettingsService, - private notificationService: NotificationService, - private plexService: PlexService, - private testerService: TesterService, - private jobService: JobService) { } + constructor( + private settingsService: SettingsService, + private notificationService: NotificationService, + private plexService: PlexService, + private testerService: TesterService, + private jobService: JobService) { } public ngOnInit() { this.settingsService.getPlex().subscribe(x => { @@ -34,17 +33,17 @@ export class PlexComponent implements OnInit, OnDestroy { } public requestServers(server: IPlexServer) { - this.plexService.getServers(this.username, this.password) - .takeUntil(this.subscriptions) - .subscribe(x => { - if (x.success) { - this.loadedServers = x; - this.serversButton = true; - this.notificationService.success("Found the servers! Please select one!"); - } else { - this.notificationService.warning("Error When Requesting Plex Servers", "Please make sure your username and password are correct"); - } - }); + this.plexService.getServers(this.username, this.password).pipe( + takeUntil(this.subscriptions), + ).subscribe(x => { + if (x.success) { + this.loadedServers = x; + this.serversButton = true; + this.notificationService.success("Found the servers! Please select one!"); + } else { + this.notificationService.warning("Error When Requesting Plex Servers", "Please make sure your username and password are correct"); + } + }); } public selectServer(selectedServer: IPlexServerResponse, server: IPlexServer) { @@ -72,7 +71,7 @@ export class PlexComponent implements OnInit, OnDestroy { if (this.settings.servers == null) { this.settings.servers = []; } - this.settings.servers.push({ name: "New*", id: Math.floor(Math.random() * (99999 - 0 + 1) + 1) }); + this.settings.servers.push( { name: "New*", id: Math.floor(Math.random() * (99999 - 0 + 1) + 1) }); } @@ -91,19 +90,19 @@ export class PlexComponent implements OnInit, OnDestroy { this.plexService.getLibraries(server).subscribe(x => { server.plexSelectedLibraries = []; if (x.successful) { - x.data.mediaContainer.directory.forEach((item) => { - const lib: IPlexLibrariesSettings = { - key: item.key, - title: item.title, - enabled: false, - }; - server.plexSelectedLibraries.push(lib); - }); - } else { - this.notificationService.error(x.message); - } - }, - err => { this.notificationService.error(err); }); + x.data.mediaContainer.directory.forEach((item) => { + const lib: IPlexLibrariesSettings = { + key: item.key, + title: item.title, + enabled: false, + }; + server.plexSelectedLibraries.push(lib); + }); + } else { + this.notificationService.error(x.message); + } + }, + err => { this.notificationService.error(err); }); } public save() { @@ -120,7 +119,7 @@ export class PlexComponent implements OnInit, OnDestroy { public runCacher(): void { this.jobService.runPlexCacher().subscribe(x => { - if(x) { + if (x) { this.notificationService.success("Triggered the Plex Full Sync"); } }); @@ -128,7 +127,7 @@ export class PlexComponent implements OnInit, OnDestroy { public runRecentlyAddedCacher(): void { this.jobService.runPlexRecentlyAddedCacher().subscribe(x => { - if(x) { + if (x) { this.notificationService.success("Triggered the Plex Recently Added Sync"); } }); diff --git a/src/Ombi/ClientApp/app/settings/radarr/radarr.component.html b/src/Ombi/ClientApp/app/settings/radarr/radarr.component.html index 909a64226..08a8035c2 100644 --- a/src/Ombi/ClientApp/app/settings/radarr/radarr.component.html +++ b/src/Ombi/ClientApp/app/settings/radarr/radarr.component.html @@ -1,5 +1,4 @@ - - +
    Radarr Settings @@ -19,25 +18,34 @@
    - + - - The IP/Hostname is required +
    - + - - The Port is required +
    - - - - The API Key is required + + +
    @@ -49,63 +57,73 @@
    - +
    +
    -
    - -
    -
    -
    - +
    - +
    - A Default Quality Profile is required
    -
    -
    - -
    - -
    - +
    - + -
    - A Default Root Path is required + + +
    + +
    - +
    -
    - - A Default Minimum Availability is required
    - +
    +
    +
    - +
    @@ -118,4 +136,4 @@
    -
    +
    \ No newline at end of file diff --git a/src/Ombi/ClientApp/app/settings/radarr/radarr.component.ts b/src/Ombi/ClientApp/app/settings/radarr/radarr.component.ts index 30f74e300..d68396a66 100644 --- a/src/Ombi/ClientApp/app/settings/radarr/radarr.component.ts +++ b/src/Ombi/ClientApp/app/settings/radarr/radarr.component.ts @@ -54,7 +54,7 @@ export class RadarrComponent implements OnInit { this.qualities = []; this.qualities.push({ name: "Please Select", id: -1 }); - + this.rootFolders = []; this.rootFolders.push({ path: "Please Select", id: -1 }); this.minimumAvailabilityOptions = [ @@ -93,7 +93,7 @@ export class RadarrComponent implements OnInit { this.notificationService.error("Please check your entered values"); return; } - const settings = form.value; + const settings = form.value; this.testerService.radarrTest(settings).subscribe(x => { if (x === true) { this.notificationService.success("Successfully connected to Radarr!"); @@ -108,12 +108,12 @@ public onSubmit(form: FormGroup) { this.notificationService.error("Please check your entered values"); return; } - if(form.controls.defaultQualityProfile.value === "-1" || form.controls.defaultRootPath.value === "Please Select") { + if (form.controls.defaultQualityProfile.value === "-1" || form.controls.defaultRootPath.value === "Please Select") { this.notificationService.error("Please check your entered values"); return; } - const settings = form.value; + const settings = form.value; this.settingsService.saveRadarr(settings).subscribe(x => { if (x) { this.notificationService.success("Successfully saved Radarr settings"); diff --git a/src/Ombi/ClientApp/app/settings/settings.module.ts b/src/Ombi/ClientApp/app/settings/settings.module.ts index 0069cf262..6fb69dc27 100644 --- a/src/Ombi/ClientApp/app/settings/settings.module.ts +++ b/src/Ombi/ClientApp/app/settings/settings.module.ts @@ -1,14 +1,16 @@ -import { CommonModule } from "@angular/common"; +import { CommonModule } from "@angular/common"; import { NgModule } from "@angular/core"; import { FormsModule, ReactiveFormsModule } from "@angular/forms"; import { RouterModule, Routes } from "@angular/router"; import { NgbAccordionModule, NgbModule } from "@ng-bootstrap/ng-bootstrap"; -import { ClipboardModule } from "ngx-clipboard/dist"; +import { ClipboardModule } from "ngx-clipboard"; import { AuthGuard } from "../auth/auth.guard"; import { AuthService } from "../auth/auth.service"; -import { CouchPotatoService, EmbyService, IssuesService, JobService, MobileService, NotificationMessageService, PlexService, RadarrService, - SonarrService, TesterService, ValidationService } from "../services"; +import { + CouchPotatoService, EmbyService, IssuesService, JobService, LidarrService, MobileService, NotificationMessageService, PlexService, RadarrService, + RequestRetryService, SonarrService, TesterService, ValidationService, +} from "../services"; import { PipeModule } from "../pipes/pipe.module"; import { AboutComponent } from "./about/about.component"; @@ -17,12 +19,15 @@ import { CouchPotatoComponent } from "./couchpotato/couchpotato.component"; import { CustomizationComponent } from "./customization/customization.component"; import { DogNzbComponent } from "./dognzb/dognzb.component"; import { EmbyComponent } from "./emby/emby.component"; +import { FailedRequestsComponent } from "./failedrequests/failedrequests.component"; import { IssuesComponent } from "./issues/issues.component"; import { JobsComponent } from "./jobs/jobs.component"; import { LandingPageComponent } from "./landingpage/landingpage.component"; +import { LidarrComponent } from "./lidarr/lidarr.component"; import { MassEmailComponent } from "./massemail/massemail.component"; import { DiscordComponent } from "./notifications/discord.component"; import { EmailNotificationComponent } from "./notifications/emailnotification.component"; +import { GotifyComponent } from "./notifications/gotify.component"; import { MattermostComponent } from "./notifications/mattermost.component"; import { MobileComponent } from "./notifications/mobile.component"; import { NewsletterComponent } from "./notifications/newsletter.component"; @@ -38,6 +43,7 @@ import { SickRageComponent } from "./sickrage/sickrage.component"; import { SonarrComponent } from "./sonarr/sonarr.component"; import { UpdateComponent } from "./update/update.component"; import { UserManagementComponent } from "./usermanagement/usermanagement.component"; +import { VoteComponent } from "./vote/vote.component"; import { WikiComponent } from "./wiki.component"; import { SettingsMenuComponent } from "./settingsmenu.component"; @@ -58,6 +64,7 @@ const routes: Routes = [ { path: "Slack", component: SlackComponent, canActivate: [AuthGuard] }, { path: "Pushover", component: PushoverComponent, canActivate: [AuthGuard] }, { path: "Pushbullet", component: PushbulletComponent, canActivate: [AuthGuard] }, + { path: "Gotify", component: GotifyComponent, canActivate: [AuthGuard] }, { path: "Mattermost", component: MattermostComponent, canActivate: [AuthGuard] }, { path: "UserManagement", component: UserManagementComponent, canActivate: [AuthGuard] }, { path: "Update", component: UpdateComponent, canActivate: [AuthGuard] }, @@ -71,6 +78,9 @@ const routes: Routes = [ { path: "Mobile", component: MobileComponent, canActivate: [AuthGuard] }, { path: "MassEmail", component: MassEmailComponent, canActivate: [AuthGuard] }, { path: "Newsletter", component: NewsletterComponent, canActivate: [AuthGuard] }, + { path: "Lidarr", component: LidarrComponent, canActivate: [AuthGuard] }, + { path: "Vote", component: VoteComponent, canActivate: [AuthGuard] }, + { path: "FailedRequests", component: FailedRequestsComponent, canActivate: [AuthGuard] }, ]; @NgModule({ @@ -109,6 +119,7 @@ const routes: Routes = [ PushoverComponent, MattermostComponent, PushbulletComponent, + GotifyComponent, UserManagementComponent, UpdateComponent, AboutComponent, @@ -122,6 +133,9 @@ const routes: Routes = [ MobileComponent, MassEmailComponent, NewsletterComponent, + LidarrComponent, + VoteComponent, + FailedRequestsComponent, ], exports: [ RouterModule, @@ -140,6 +154,8 @@ const routes: Routes = [ EmbyService, MobileService, NotificationMessageService, + LidarrService, + RequestRetryService, ], }) diff --git a/src/Ombi/ClientApp/app/settings/settingsmenu.component.html b/src/Ombi/ClientApp/app/settings/settingsmenu.component.html index 6c6bb5c3f..d58c96e2e 100644 --- a/src/Ombi/ClientApp/app/settings/settingsmenu.component.html +++ b/src/Ombi/ClientApp/app/settings/settingsmenu.component.html @@ -12,6 +12,7 @@
  • Issues
  • User Importer
  • Authentication
  • +
  • Vote
  • @@ -21,7 +22,7 @@ @@ -48,6 +49,15 @@ + +
  • Pushover
  • Mattermost
  • Telegram
  • +
  • Gotify
  • @@ -74,6 +85,7 @@