diff --git a/CHANGELOG.md b/CHANGELOG.md index b17a436e7..a23ed6887 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,8 +2,118 @@ ## (unreleased) +### **Fixes** + +- Small memory improvements in the Plex Sync. [Jamie] + +- Fixed the sort issue on the user Management page. Also added sorting to the Movie Requests page. [tidusjar] + +- Downgraded the angular2-jwt library since it has a bug in it. #2064. [tidusjar] + +- Fixed an issue when Plex decideds to reuse the Plex Key for a different media item... #2038. [tidusjar] + +- Fixed an issue where we might show the Imdb link when we do not have a imdbid #1797. [tidusjar] + +- Fixed the issue where we can no longer select Pending Approval in the filters #2057. [tidusjar] + +- Fixed the API key not working when attempting to get requests #2058. [tidusjar] + +- Fixed #2056. [tidusjar] + +- Experimental, set the Webpack base root to the ombi base path if we have it. This should hopefully fix the reverse proxy issues. [Jamie] + + +## v3.0.3000 (2018-03-09) + ### **New Features** +- Added the ability to override root and quality options in Sonarr (#2049) [Jamie] + +- Added Pending Approval into the filters list. [tidusjar] + +- Added the ability to hide requests that have not been made by that user (#2052) [Jamie] + +- Update README.md. [Jamie] + +- Update README.md. [Louis Laureys] + +### **Fixes** + +- Fixed #2042. [Jamie] + + +## v3.0.0 (2018-03-04) + +### **New Features** + +- Update build.cake. [Jamie] + +- Update README.md. [Jamie] + +- Update README.md. [Jamie] + +- Added a similar button to the movie searches. Makes movie discoverablility easier. [tidusjar] + +- Update README.md. [Jamie] + +- Update README.md. [Jamie] + +- Update ISSUE_TEMPLATE.md. [Jamie] + +- Update appveyor.yml. [Jamie] + +- Update ISSUE_TEMPLATE.md. [PotatoQuality] + +- Update ISSUE_TEMPLATE.md. [PotatoQuality] + +- Update README.md. [Jamie] + +- Update README.md. [Jamie] + +- Update README.md. [Jamie] + +- Update README.md. [PotatoQuality] + +- Change the default templates to use {IssueUser} [Jamie] + +- Changed the base url validation. [tidusjar] + +- Added bulk editing (#1941) [Jamie] + +- Change the poster size to w300 #1932. [Jamie] + +- Added a default user agent on all API calls. [tidusjar] + +- Update request.service.ts. [Jamie] + +- Added a filter onto the movies requests page for some inital feedback. [Jamie] + +- Added ordering to the User Management screen. [Jamie] + +- Update README.md. [Jamie] + +- Added custom donation url (#1902) [m4tta] + +- Changed the url scheme to make it easier to parse. [Jamie] + +- Added Norwegian to the translation code, forgot to check this in. [Jamie] + +- Added Norwegian to the language dropdown. [Jamie] + +- Added the stuff needed for omBlur. [tidusjar] + +- Update README.md (#1872) [xnaas] + +- Update README.md. [Jamie] + +- Update plex.component.html. [Jamie] + +- Change plus to list in menu (#1855) [Louis Laureys] + +- Update README.md. [Jamie] + +- Update README.md. [Jamie] + - Added user request limits, We can now set the limit for a user. [tidusjar] - Updated the UI JWT framework. [Jamie] @@ -284,6 +394,270 @@ ### **Fixes** +- New translations en.json (Norwegian) (#2020) [Jamie] + +- Publish 32bit build of windows. [tidusjar] + +- Fixing incorrect filter translation targets (#1987) [Jono Cairns] + +- New Crowdin translations (#2017) [Jamie] + +- Fixed #1997. [tidusjar] + +- We now show the digital release date in the search if available #1962. [tidusjar] + +- Css fixes (#2014) [Louis Laureys] + +- API improvements. [Jamie] + +- Fix #1599 (#2008) [Louis Laureys] + +- Issue button fix (#2006) [Louis Laureys] + +- Fixed #1886 #1865. [Jamie] + +- Fixed the outstanding issue on #1995. [Jamie] + +- Fixed an issue for #1951. [tidusjar] + +- Try and fuzzy match the title and release if we cannot get the tvdb id or imdbid (depends on the media agents in Plex) #1951. [tidusjar] + +- Fixed #1989 #1719. [Jamie] + +- Small changes that might fix #1985 but doubt it. [Jamie] + +- Should fix #1975. [tidusjar] + +- Fixed #1789. [tidusjar] + +- Fixed #1968. [tidusjar] + +- Fixed #1978. [tidusjar] + +- Fixed #1954. [tidusjar] + +- Small changes to the auto updater, let's see how this works. [Jamie] + +- Fixed build. [Jamie] + +- Fixed the update check for the master build. [Jamie] + +- Removed accidently merged files. [Jamie] + +- Create CODE_OF_CONDUCT.md. [Jamie] + +- Windows installation guide link update. [PotatoQuality] + +- Fixed the issue comment issue #1914 also added another variable for issues {IssueUser} which is the user that reported the issue. [Jamie] + +- Fix #1914. [tidusjar] + +- Fixed #1914. [tidusjar] + +- Fixed build and added logging. [TidusJar] + +- New Crowdin translations (#1934) [Jamie] + +- Potential fix for #1942. [Jamie] + +- Quick change to the Emby Availability rule to make it in line slightly with the Plex one. #1950. [Jamie] + +- Turn off mobile notifications. [tidusjar] + +- FIXED PLEX!!!!! [tidusjar] + +- Batch the PlexContentSync and increase the plex episode batch size. [tidusjar] + +- Fixed the migration issue, it's too difficult to migrate the tables. [tidusjar] + +- Fixed #1942. [tidusjar] + +- Fixed checkboxes style. [Jamie] + +- These are not the droids you are looking for. [Jamie] + +- Fixed the wrong translation and see if we can VACUUM the db. [tidusjar] + +- More translations and added a check on the baseurl to ensure it starts with a '/' [Jamie] + +- More translations. [Jamie] + +- Fixed #1878 and added a Request all button when selecting episodes. [Jamie] + +- New translations en.json (Dutch) [Jamie] + +- New translations en.json (German) [Jamie] + +- New translations en.json (German) [Jamie] + +- New translations en.json (Danish) [Jamie] + +- New translations en.json (Norwegian) [Jamie] + +- New translations en.json (Norwegian) [Jamie] + +- New translations en.json (Norwegian) [Jamie] + +- New translations en.json (Italian) [Jamie] + +- New translations en.json (German) [Jamie] + +- New translations en.json (Dutch) [Jamie] + +- New translations en.json (Swedish) [Jamie] + +- New translations en.json (Spanish) [Jamie] + +- New translations en.json (French) [Jamie] + +- New translations en.json (Danish) [Jamie] + +- New translations en.json (Norwegian) [Jamie] + +- New translations en.json (Italian) [Jamie] + +- New translations en.json (German) [Jamie] + +- New translations en.json (Dutch) [Jamie] + +- New translations en.json (Swedish) [Jamie] + +- New translations en.json (Spanish) [Jamie] + +- New translations en.json (French) [Jamie] + +- New translations en.json (Danish) [Jamie] + +- New translations en.json (Danish) [Jamie] + +- New translations en.json (Norwegian) [Jamie] + +- New translations en.json (Italian) [Jamie] + +- New translations en.json (German) [Jamie] + +- New translations en.json (Dutch) [Jamie] + +- New translations en.json (Swedish) [Jamie] + +- New translations en.json (Spanish) [Jamie] + +- New translations en.json (French) [Jamie] + +- New translations en.json (Danish) [Jamie] + +- New translations en.json (Swedish) [Jamie] + +- New translations en.json (Norwegian) [Jamie] + +- Working on the movie matching. Stop dupes #1869. [tidusjar] + +- Delete plex episodes on every run due to a bug, need to spend quite a bit of time on this. [tidusjar] + +- Fixed the issue where we were always adding emby episodes. Also fixed #1933. [tidusjar] + +- New Crowdin translations (#1906) [Jamie] + +- Add plain password for emby login (#1925) [dorian ALKOUM] + +- Fixed #1924. [Jamie] + +- Fixed the issue where I knocked out the ordering of notifications, oops. [tidusjar] + +- #1914 for the issue resolved notification. [Jamie] + +- #1916. [Jamie] + +- Remove the placeholder. [Jamie] + +- Feature arm (#1909) [Jamie] + +- New Crowdin translations (#1897) [Jamie] + +- Fix logo cut off on login screen (#1896) [Louis Laureys] + +- E-Mails: Only add poster table row if img is set (#1899) [Louis Laureys] + +- New Crowdin translations (#1884) [Jamie] + +- Fix mobile layout (#1888) [Louis Laureys] + +- Smal changes to the api. [tidusjar] + +- OmBlur. [tidusjar] + +- Hide the password field if it's not needed #1815. [Jamie] + +- Should fix #1885. [Jamie] + +- Make user management table responsive (#1882) [Louis Laureys] + +- Fixed some stuff for omBlur. [Jamie] + +- Some work... No one take a look at this, it's a suprise. [Jamie] + +- New Crowdin translations (#1858) [Jamie] + +- When requesting Anime, we now mark it correctly as Anime in Sonarr. [tidusjar] + +- Fixed #1879 and added the spans. [tidusjar] + +- Some work on the auto updater #1460. [tidusjar] + +- Removed the potential locking. [tidusjar] + +- Fixed #1863. [tidusjar] + +- Moved the update check code from the External azure service into Ombi at /api/v1/update/BRANCH. [Jamie] + +- Fixed the UI erroring out, also dont show tv with no externals. [tidusjar] + +- More memory management and improvements. [tidusjar] + +- These are not needed, added accidentally (#1860) [Louis Laureys] + +- Some memory management improvements. [tidusjar] + +- Fixed #1857. [tidusjar] + +- Delete old v2 ombi from v3 branch. [tidusjar] + +- New Crowdin translations (#1840) [Jamie] + +- Better login backgrounds! (#1852) [Louis Laureys] + +- Fixed #1851. [tidusjar] + +- Fixed #1826. [tidusjar] + +- Redo change #1848. [tidusjar] + +- Fix the issue for welcome emails not sending. [tidusjar] + +- Fix typo (#1845) [Kyle Lucy] + +- Fix user mentions in Slack notifications (#1846) [Aljosa Asanovic] + +- If Radarr/Sonarr has noticed that the media is available, then mark it as available in the UI. [Jamie] + +- Fixed #1835. [Jamie] + +- Enable Multi MIME and add alt tags to images (#1838) [Louis Laureys] + +- New Crowdin translations (#1816) [Jamie] + +- Fixed #1832. [tidusjar] + +- Switch to use a single HTTPClient rather than a new one every request !dev. [tidusjar] + +- Fix non-admin rights (#1820) [Rob Gökemeijer] + +- Fix duplicated "Requests" element ID on new Issues link (#1817) [Shoghi Cervantes] + +- Add the Issue Reporting functionality (#1811) [Jamie] + +- Removed the forum. [tidusjar] + - #1659 Made the option to ignore notifcations for auto approve. [Jamie] - New Crowdin translations (#1806) [Jamie] diff --git a/README.md b/README.md index 98703a002..3a037145c 100644 --- a/README.md +++ b/README.md @@ -9,17 +9,16 @@ ____ [![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://www.ombi.io/img/patreondonate.svg)](https://patreon.com/tidusjar/Ombi) -[![Paypal](https://www.ombi.io/img/paypaldonate.svg)](https://paypal.me/PlexRequestsNet) ___ [![Report a bug](http://i.imgur.com/xSpw482.png)](https://forums.ombi.io/viewforum.php?f=10) [![Feature request](http://i.imgur.com/mFO0OuX.png)](https://forums.ombi.io/posting.php?mode=post&f=20) -| Service | Master (V2) | Open Beta (V3 - Recommended) | + +| Service | Stable | Develop | |----------|:---------------------------:|:----------------------------:| -| AppVeyor | [![Build status](https://ci.appveyor.com/api/projects/status/hgj8j6lcea7j0yhn/branch/master?svg=true)](https://ci.appveyor.com/project/tidusjar/requestplex/branch/master) | [![Build status](https://ci.appveyor.com/api/projects/status/hgj8j6lcea7j0yhn/branch/DotNetCore?svg=true)](https://ci.appveyor.com/project/tidusjar/requestplex/branch/DotNetCore) | -| Download |[![Download](http://i.imgur.com/odToka3.png)](https://github.com/tidusjar/Ombi/releases) | [![Download](http://i.imgur.com/odToka3.png)](https://ci.appveyor.com/project/tidusjar/requestplex/branch/DotNetCore/artifacts) | +| AppVeyor | [![Build status](https://ci.appveyor.com/api/projects/status/hgj8j6lcea7j0yhn/branch/master?svg=true)](https://ci.appveyor.com/project/tidusjar/requestplex/branch/master) | [![Build status](https://ci.appveyor.com/api/projects/status/hgj8j6lcea7j0yhn/branch/develop?svg=true)](https://ci.appveyor.com/project/tidusjar/requestplex/branch/develop) | +| Download |[![Download](http://i.imgur.com/odToka3.png)](https://github.com/tidusjar/Ombi/releases) | [![Download](http://i.imgur.com/odToka3.png)](https://ci.appveyor.com/project/tidusjar/requestplex/branch/develop/artifacts) | # Features Here are some of the features Ombi V3 has: * Now working without crashes on Linux. @@ -72,10 +71,9 @@ We are planning to bring back these features in V3 but for now you can find a li | Login page | Yes (brand new) | Yes | | Custom Notification Messages | Yes | No | | Sending newsletters | Planned | Yes | -| Send a Mass Email | Planned | Yes | +| Send a Mass Email | Yes | Yes | | SickRage | Yes | Yes | | CouchPotato | Yes | Yes | -| Watcher | Planned | Yes | | DogNzb | Yes | No | | Issues | Yes | Yes | | Headphones | No (support dropped) | Yes | @@ -94,8 +92,9 @@ Search the existing requests to see if your suggestion has already been submitte # Installation -[Click Here](https://github.com/tidusjar/Ombi/wiki/Installation) -[Here for Reverse Proxy Config Examples](https://github.com/tidusjar/Ombi/wiki/Reverse-Proxy-Examples) +[Installation Guide](https://github.com/tidusjar/Ombi/wiki/Installation) +[Here for Reverse Proxy Config Examples](https://github.com/tidusjar/Ombi/wiki/Reverse-Proxy-Examples) +[PlexGuide.com - Ombi Deployment & 101 Demonstration!](https://www.youtube.com/watch?v=QPNlqqkjNJw&feature=youtu.be) # Contributors diff --git a/appveyor.yml b/appveyor.yml index 539a71c44..c513c650e 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -25,6 +25,12 @@ after_build: appveyor PushArtifact "%APPVEYOR_BUILD_FOLDER%\src\Ombi\bin\Release\netcoreapp2.0\linux-arm.tar.gz" + + + appveyor PushArtifact "%APPVEYOR_BUILD_FOLDER%\src\Ombi\bin\Release\netcoreapp2.0\windows-32bit.zip" + + +# appveyor PushArtifact "%APPVEYOR_BUILD_FOLDER%\src\Ombi\bin\Release\netcoreapp2.0\linux-arm64.tar.gz" diff --git a/build.cake b/build.cake index ad3e7d93d..58eadd651 100644 --- a/build.cake +++ b/build.cake @@ -3,7 +3,7 @@ #addin "Cake.Gulp" #addin "Cake.Npm" #addin "SharpZipLib" -#addin "Cake.Compression" +#addin nuget:?package=Cake.Compression&version=0.1.4 #addin "Cake.Incubator" ////////////////////////////////////////////////////////////////////// @@ -26,26 +26,29 @@ 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.0"; + var buildSettings = new DotNetCoreBuildSettings { - Framework = "netcoreapp2.0", + Framework = frameworkVer, Configuration = "Release", OutputDirectory = Directory(buildDir), }; var publishSettings = new DotNetCorePublishSettings { - Framework = "netcoreapp2.0", + Framework = frameworkVer, Configuration = "Release", OutputDirectory = Directory(buildDir), }; -var artifactsFolder = buildDir + "/netcoreapp2.0/"; +var artifactsFolder = buildDir + "/"+frameworkVer+"/"; var windowsArtifactsFolder = artifactsFolder + "win10-x64/published"; -var windows32BitArtifactsFolder = artifactsFolder + "win10-x32/published"; +var windows32BitArtifactsFolder = artifactsFolder + "win10-x86/published"; var osxArtifactsFolder = artifactsFolder + "osx-x64/published"; var linuxArtifactsFolder = artifactsFolder + "linux-x64/published"; var linuxArmArtifactsFolder = artifactsFolder + "linux-arm/published"; +var linuxArm64BitArtifactsFolder = artifactsFolder + "linux-arm64/published"; @@ -104,6 +107,10 @@ Task("SetVersionInfo") { fullVer = fullVer.Replace("_",""); } + if(fullVer.Contains("/")) + { + fullVer = fullVer.Replace("/",""); + } buildSettings.ArgumentCustomization = args => args.Append("/p:SemVer=" + versionInfo.AssemblySemVer); buildSettings.ArgumentCustomization = args => args.Append("/p:FullVer=" + fullVer); @@ -161,35 +168,38 @@ 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"); }); Task("Publish") .IsDependentOn("PrePublish") .IsDependentOn("Publish-Windows") + .IsDependentOn("Publish-Windows-32bit") .IsDependentOn("Publish-OSX") .IsDependentOn("Publish-Linux") .IsDependentOn("Publish-Linux-ARM") + //.IsDependentOn("Publish-Linux-ARM-64Bit") .IsDependentOn("Package"); Task("Publish-Windows") .Does(() => { publishSettings.Runtime = "win10-x64"; - publishSettings.OutputDirectory = Directory(buildDir) + Directory("netcoreapp2.0/win10-x64/published"); + publishSettings.OutputDirectory = Directory(buildDir) + Directory(frameworkVer +"/win10-x64/published"); DotNetCorePublish("./src/Ombi/Ombi.csproj", publishSettings); - CopyFile(buildDir + "/netcoreapp2.0/win10-x64/Swagger.xml", buildDir + "/netcoreapp2.0/win10-x64/published/Swagger.xml"); + CopyFile(buildDir + "/"+frameworkVer+"/win10-x64/Swagger.xml", buildDir + "/"+frameworkVer+"/win10-x64/published/Swagger.xml"); DotNetCorePublish("./src/Ombi.Updater/Ombi.Updater.csproj", publishSettings); }); Task("Publish-Windows-32bit") .Does(() => { - publishSettings.Runtime = "win10-x32"; - publishSettings.OutputDirectory = Directory(buildDir) + Directory("netcoreapp2.0/win10-x32/published"); + publishSettings.Runtime = "win10-x86"; + publishSettings.OutputDirectory = Directory(buildDir) + Directory(frameworkVer+"/win10-x86/published"); DotNetCorePublish("./src/Ombi/Ombi.csproj", publishSettings); - CopyFile(buildDir + "/netcoreapp2.0/win10-x32/Swagger.xml", buildDir + "/netcoreapp2.0/win10-x32/published/Swagger.xml"); + CopyFile(buildDir + "/"+frameworkVer+"/win10-x86/Swagger.xml", buildDir + "/"+frameworkVer+"/win10-x86/published/Swagger.xml"); DotNetCorePublish("./src/Ombi.Updater/Ombi.Updater.csproj", publishSettings); }); @@ -197,10 +207,10 @@ Task("Publish-OSX") .Does(() => { publishSettings.Runtime = "osx-x64"; - publishSettings.OutputDirectory = Directory(buildDir) + Directory("netcoreapp2.0/osx-x64/published"); + publishSettings.OutputDirectory = Directory(buildDir) + Directory(frameworkVer+"/osx-x64/published"); DotNetCorePublish("./src/Ombi/Ombi.csproj", publishSettings); - CopyFile(buildDir + "/netcoreapp2.0/osx-x64/Swagger.xml", buildDir + "/netcoreapp2.0/osx-x64/published/Swagger.xml"); + CopyFile(buildDir + "/"+frameworkVer+"/osx-x64/Swagger.xml", buildDir + "/"+frameworkVer+"/osx-x64/published/Swagger.xml"); DotNetCorePublish("./src/Ombi.Updater/Ombi.Updater.csproj", publishSettings); }); @@ -208,10 +218,10 @@ Task("Publish-Linux") .Does(() => { publishSettings.Runtime = "linux-x64"; - publishSettings.OutputDirectory = Directory(buildDir) + Directory("netcoreapp2.0/linux-x64/published"); + publishSettings.OutputDirectory = Directory(buildDir) + Directory(frameworkVer+"/linux-x64/published"); DotNetCorePublish("./src/Ombi/Ombi.csproj", publishSettings); - CopyFile(buildDir + "/netcoreapp2.0/linux-x64/Swagger.xml", buildDir + "/netcoreapp2.0/linux-x64/published/Swagger.xml"); + CopyFile(buildDir + "/"+frameworkVer+"/linux-x64/Swagger.xml", buildDir + "/"+frameworkVer+"/linux-x64/published/Swagger.xml"); DotNetCorePublish("./src/Ombi.Updater/Ombi.Updater.csproj", publishSettings); }); @@ -219,12 +229,25 @@ Task("Publish-Linux-ARM") .Does(() => { publishSettings.Runtime = "linux-arm"; - publishSettings.OutputDirectory = Directory(buildDir) + Directory("netcoreapp2.0/linux-arm/published"); + publishSettings.OutputDirectory = Directory(buildDir) + Directory(frameworkVer+"/linux-arm/published"); + + DotNetCorePublish("./src/Ombi/Ombi.csproj", publishSettings); + CopyFile( + buildDir + "/"+frameworkVer+"/linux-arm/Swagger.xml", + buildDir + "/"+frameworkVer+"/linux-arm/published/Swagger.xml"); + DotNetCorePublish("./src/Ombi.Updater/Ombi.Updater.csproj", publishSettings); +}); + +Task("Publish-Linux-ARM-64Bit") + .Does(() => +{ + publishSettings.Runtime = "linux-arm64"; + publishSettings.OutputDirectory = Directory(buildDir) + Directory(frameworkVer+"/linux-arm64/published"); DotNetCorePublish("./src/Ombi/Ombi.csproj", publishSettings); CopyFile( - buildDir + "/netcoreapp2.0/linux-arm/Swagger.xml", - buildDir + "/netcoreapp2.0/linux-arm/published/Swagger.xml"); + buildDir + "/"+frameworkVer+"/linux-arm64/Swagger.xml", + buildDir + "/"+frameworkVer+"/linux-arm64/published/Swagger.xml"); DotNetCorePublish("./src/Ombi.Updater/Ombi.Updater.csproj", publishSettings); }); diff --git a/src/Ombi.Api.Plex/Models/Metadata.cs b/src/Ombi.Api.Plex/Models/Metadata.cs index 40c3cd5d0..28bdf002d 100644 --- a/src/Ombi.Api.Plex/Models/Metadata.cs +++ b/src/Ombi.Api.Plex/Models/Metadata.cs @@ -23,7 +23,7 @@ namespace Ombi.Api.Plex.Models public int leafCount { get; set; } public int viewedLeafCount { get; set; } public int childCount { get; set; } - public int addedAt { get; set; } + public long addedAt { get; set; } public int updatedAt { get; set; } public Genre[] Genre { get; set; } //public Role[] Role { get; set; } diff --git a/src/Ombi.Api.Radarr/CommandResult.cs b/src/Ombi.Api.Radarr/CommandResult.cs new file mode 100644 index 000000000..e658a1ede --- /dev/null +++ b/src/Ombi.Api.Radarr/CommandResult.cs @@ -0,0 +1,9 @@ +using System; + +namespace Ombi.Api.Radarr +{ + public class CommandResult + { + public string name { get; set; } + } +} diff --git a/src/Ombi.Api.Radarr/IRadarrApi.cs b/src/Ombi.Api.Radarr/IRadarrApi.cs index 8df55b748..a23f6d3f5 100644 --- a/src/Ombi.Api.Radarr/IRadarrApi.cs +++ b/src/Ombi.Api.Radarr/IRadarrApi.cs @@ -10,6 +10,9 @@ namespace Ombi.Api.Radarr Task> GetProfiles(string apiKey, string baseUrl); Task> GetRootFolders(string apiKey, string baseUrl); Task SystemStatus(string apiKey, string baseUrl); + Task GetMovie(int id, string apiKey, string baseUrl); + Task UpdateMovie(MovieResponse movie, string apiKey, string baseUrl); + Task MovieSearch(int[] movieIds, string apiKey, string baseUrl); Task AddMovie(int tmdbId, string title, int year, int qualityId, string rootPath,string apiKey, string baseUrl, bool searchNow, string minimumAvailability); } } \ No newline at end of file diff --git a/src/Ombi.Api.Radarr/Models/RadarrAddMovie.cs b/src/Ombi.Api.Radarr/Models/RadarrAddMovie.cs index eaf7ee6da..9288058f9 100644 --- a/src/Ombi.Api.Radarr/Models/RadarrAddMovie.cs +++ b/src/Ombi.Api.Radarr/Models/RadarrAddMovie.cs @@ -17,10 +17,7 @@ namespace Ombi.Api.Radarr.Models public bool monitored { get; set; } public int tmdbId { get; set; } public List images { get; set; } - public string cleanTitle { get; set; } - public string imdbId { get; set; } public string titleSlug { get; set; } - public int id { get; set; } public int year { get; set; } public string minimumAvailability { get; set; } } diff --git a/src/Ombi.Api.Radarr/Models/RadarrError.cs b/src/Ombi.Api.Radarr/Models/RadarrError.cs index a42c2ca52..654f83772 100644 --- a/src/Ombi.Api.Radarr/Models/RadarrError.cs +++ b/src/Ombi.Api.Radarr/Models/RadarrError.cs @@ -3,19 +3,10 @@ public class RadarrError { public string message { get; set; } - public string description { get; set; } } public class RadarrErrorResponse { - public string propertyName { get; set; } public string errorMessage { get; set; } - public object attemptedValue { get; set; } - public FormattedMessagePlaceholderValues formattedMessagePlaceholderValues { get; set; } - } - public class FormattedMessagePlaceholderValues - { - public string propertyName { get; set; } - public object propertyValue { get; set; } } } \ No newline at end of file diff --git a/src/Ombi.Api.Radarr/RadarrApi.cs b/src/Ombi.Api.Radarr/RadarrApi.cs index 9ec66eb70..1f897b60b 100644 --- a/src/Ombi.Api.Radarr/RadarrApi.cs +++ b/src/Ombi.Api.Radarr/RadarrApi.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; using System.Net.Http; using System.Threading.Tasks; @@ -53,6 +52,23 @@ namespace Ombi.Api.Radarr return await Api.Request>(request); } + public async Task GetMovie(int id, string apiKey, string baseUrl) + { + var request = new Request($"/api/movie/{id}", baseUrl, HttpMethod.Get); + AddHeaders(request, apiKey); + + return await Api.Request(request); + } + + public async Task UpdateMovie(MovieResponse movie, string apiKey, string baseUrl) + { + var request = new Request($"/api/movie/", baseUrl, HttpMethod.Put); + AddHeaders(request, apiKey); + request.AddJsonBody(movie); + + return await Api.Request(request); + } + public async Task AddMovie(int tmdbId, string title, int year, int qualityId, string rootPath, string apiKey, string baseUrl, bool searchNow, string minimumAvailability) { var request = new Request("/api/movie", baseUrl, HttpMethod.Post); @@ -66,7 +82,7 @@ namespace Ombi.Api.Radarr titleSlug = title, monitored = true, year = year, - minimumAvailability = minimumAvailability, + minimumAvailability = minimumAvailability }; if (searchNow) @@ -81,9 +97,9 @@ namespace Ombi.Api.Radarr request.AddHeader("X-Api-Key", apiKey); request.AddJsonBody(options); + var response = await Api.RequestContent(request); try { - var response = await Api.RequestContent(request); if (response.Contains("\"message\":")) { var error = JsonConvert.DeserializeObject(response); @@ -98,11 +114,24 @@ namespace Ombi.Api.Radarr } catch (JsonSerializationException jse) { - Logger.LogError(LoggingEvents.RadarrApi, jse, "Error When adding movie to Radarr"); + Logger.LogError(LoggingEvents.RadarrApi, jse, "Error When adding movie to Radarr, Reponse: {0}", response); } return null; } + public async Task MovieSearch(int[] movieIds, string apiKey, string baseUrl) + { + var result = await Command(apiKey, baseUrl, new { name = "MoviesSearch", movieIds }); + return result != null; + } + + private async Task Command(string apiKey, string baseUrl, object body) + { + var request = new Request($"/api/Command/", baseUrl, HttpMethod.Post); + request.AddHeader("X-Api-Key", apiKey); + request.AddJsonBody(body); + return await Api.Request(request); + } /// /// Adds the required headers and also the authorization header diff --git a/src/Ombi.Api.Sonarr/Models/CommandResult.cs b/src/Ombi.Api.Sonarr/Models/CommandResult.cs index 0e9565eb8..8680bd1e3 100644 --- a/src/Ombi.Api.Sonarr/Models/CommandResult.cs +++ b/src/Ombi.Api.Sonarr/Models/CommandResult.cs @@ -8,11 +8,6 @@ namespace Ombi.Api.Sonarr.Models public class CommandResult { public string name { get; set; } - public DateTime startedOn { get; set; } - public DateTime stateChangeTime { get; set; } - public bool sendUpdatesToClient { get; set; } - public string state { get; set; } - public int id { get; set; } } } diff --git a/src/Ombi.Api.Sonarr/SonarrApi.cs b/src/Ombi.Api.Sonarr/SonarrApi.cs index 8753203a2..7fd74d2a3 100644 --- a/src/Ombi.Api.Sonarr/SonarrApi.cs +++ b/src/Ombi.Api.Sonarr/SonarrApi.cs @@ -66,8 +66,10 @@ namespace Ombi.Api.Sonarr var request = new Request($"/api/series/{id}", baseUrl, HttpMethod.Get); request.AddHeader("X-Api-Key", apiKey); var result = await Api.Request(request); - result.seasons.ToList().RemoveAt(0); - + if (result?.seasons?.Length > 0) + { + result?.seasons?.ToList().RemoveAt(0); + } return result; } diff --git a/src/Ombi.Core/Authentication/OmbiUserManager.cs b/src/Ombi.Core/Authentication/OmbiUserManager.cs index b9453749d..185677215 100644 --- a/src/Ombi.Core/Authentication/OmbiUserManager.cs +++ b/src/Ombi.Core/Authentication/OmbiUserManager.cs @@ -110,7 +110,8 @@ namespace Ombi.Core.Authentication /// private async Task CheckPlexPasswordAsync(OmbiUser user, string password) { - var result = await _plexApi.SignIn(new UserRequest { password = password, login = user.UserName }); + var login = user.EmailLogin ? user.Email : user.UserName; + var result = await _plexApi.SignIn(new UserRequest { password = password, login = login }); if (result.user?.authentication_token != null) { return true; diff --git a/src/Ombi.Core/Engine/BaseMediaEngine.cs b/src/Ombi.Core/Engine/BaseMediaEngine.cs index 213659700..552b2ac38 100644 --- a/src/Ombi.Core/Engine/BaseMediaEngine.cs +++ b/src/Ombi.Core/Engine/BaseMediaEngine.cs @@ -14,6 +14,8 @@ using Ombi.Store.Repository.Requests; using Ombi.Store.Entities; using Microsoft.AspNetCore.Identity; using Ombi.Core.Authentication; +using Ombi.Core.Settings; +using Ombi.Settings.Settings.Models; namespace Ombi.Core.Engine { @@ -24,14 +26,18 @@ namespace Ombi.Core.Engine private Dictionary _dbTv; protected BaseMediaEngine(IPrincipal identity, IRequestServiceMain requestService, - IRuleEvaluator rules, OmbiUserManager um) : base(identity, um, rules) + IRuleEvaluator rules, OmbiUserManager um, ICacheService cache, ISettingsService ombiSettings) : base(identity, um, rules) { RequestService = requestService; + Cache = cache; + OmbiSettings = ombiSettings; } protected IRequestServiceMain RequestService { get; } protected IMovieRequestRepository MovieRepository => RequestService.MovieRequestService; protected ITvRequestRepository TvRepository => RequestService.TvRequestService; + protected readonly ICacheService Cache; + protected readonly ISettingsService OmbiSettings; protected async Task> GetMovieRequests() { @@ -99,5 +105,30 @@ namespace Ombi.Core.Engine Pending = pendingMovies + pendingTv }; } + + protected async Task HideFromOtherUsers() + { + if (await IsInRole(OmbiRoles.Admin) || await IsInRole(OmbiRoles.PowerUser)) + { + return new HideResult(); + } + var settings = await Cache.GetOrAdd(CacheKeys.OmbiSettings, async () => await OmbiSettings.GetSettingsAsync()); + var result = new HideResult + { + Hide = settings.HideRequestsUsers + }; + if (settings.HideRequestsUsers) + { + var user = await GetUser(); + result.UserId = user.Id; + } + return result; + } + + public class HideResult + { + public bool Hide { get; set; } + public string UserId { get; set; } + } } } \ 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 32f08e2b1..e608ffebb 100644 --- a/src/Ombi.Core/Engine/Interfaces/BaseEngine.cs +++ b/src/Ombi.Core/Engine/Interfaces/BaseEngine.cs @@ -1,4 +1,5 @@ -using Ombi.Core.Rule; +using System; +using Ombi.Core.Rule; using System.Collections.Generic; using System.Security.Principal; using System.Threading.Tasks; @@ -10,6 +11,7 @@ using Microsoft.AspNetCore.Identity; using System.Linq; using Microsoft.EntityFrameworkCore; using Ombi.Core.Authentication; +using Ombi.Helpers; namespace Ombi.Core.Engine.Interfaces { @@ -30,6 +32,13 @@ namespace Ombi.Core.Engine.Interfaces private OmbiUser _user; protected async Task GetUser() { + if (IsApiUser) + { + return new OmbiUser + { + UserName = Username, + }; + } return _user ?? (_user = await UserManager.Users.FirstOrDefaultAsync(x => x.UserName == Username)); } @@ -40,6 +49,10 @@ namespace Ombi.Core.Engine.Interfaces protected async Task IsInRole(string roleName) { + if (IsApiUser && roleName != OmbiRoles.Disabled) + { + return true; + } return await UserManager.IsInRoleAsync(await GetUser(), roleName); } @@ -59,5 +72,7 @@ namespace Ombi.Core.Engine.Interfaces var ruleResults = await Rules.StartSpecificRules(model, rule); return ruleResults; } + + private bool IsApiUser => Username.Equals("Api", StringComparison.CurrentCultureIgnoreCase); } } \ No newline at end of file diff --git a/src/Ombi.Core/Engine/Interfaces/IMovieEngine.cs b/src/Ombi.Core/Engine/Interfaces/IMovieEngine.cs index 1f65b5a15..91b6404db 100644 --- a/src/Ombi.Core/Engine/Interfaces/IMovieEngine.cs +++ b/src/Ombi.Core/Engine/Interfaces/IMovieEngine.cs @@ -17,5 +17,7 @@ namespace Ombi.Core Task> UpcomingMovies(); Task LookupImdbInformation(int theMovieDbId); + + Task> SimilarMovies(int theMovieDbId); } } \ 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 2163784cf..bfeb4fbe0 100644 --- a/src/Ombi.Core/Engine/Interfaces/IMovieRequestEngine.cs +++ b/src/Ombi.Core/Engine/Interfaces/IMovieRequestEngine.cs @@ -17,6 +17,6 @@ namespace Ombi.Core.Engine.Interfaces Task ApproveMovie(MovieRequests request); Task ApproveMovieById(int requestId); Task DenyMovieById(int modelId); - IEnumerable Filter(FilterViewModel vm); + Task> Filter(FilterViewModel vm); } } \ 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 06a419214..53721f792 100644 --- a/src/Ombi.Core/Engine/Interfaces/ITvSearchEngine.cs +++ b/src/Ombi.Core/Engine/Interfaces/ITvSearchEngine.cs @@ -10,9 +10,13 @@ namespace Ombi.Core.Engine.Interfaces Task>> SearchTreeNode(string searchTerm); Task> GetShowInformationTreeNode(int tvdbid); Task GetShowInformation(int tvdbid); - Task>> Popular(); - Task>> Anticipated(); - Task>> MostWatches(); - Task>> Trending(); + 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/MovieRequestEngine.cs b/src/Ombi.Core/Engine/MovieRequestEngine.cs index 2b678b74c..0b376386f 100644 --- a/src/Ombi.Core/Engine/MovieRequestEngine.cs +++ b/src/Ombi.Core/Engine/MovieRequestEngine.cs @@ -1,6 +1,5 @@ using Ombi.Api.TheMovieDb; using Ombi.Core.Models.Requests; -using Ombi.Core.Models.Search; using Ombi.Helpers; using Ombi.Store.Entities; using System; @@ -11,9 +10,12 @@ using System.Security.Principal; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; +using Ombi.Api.TheMovieDb.Models; using Ombi.Core.Authentication; using Ombi.Core.Engine.Interfaces; using Ombi.Core.Rule.Interfaces; +using Ombi.Core.Settings; +using Ombi.Settings.Settings.Models; using Ombi.Store.Entities.Requests; using Ombi.Store.Repository; @@ -23,7 +25,7 @@ namespace Ombi.Core.Engine { public MovieRequestEngine(IMovieDbApi movieApi, IRequestServiceMain requestService, IPrincipal user, INotificationHelper helper, IRuleEvaluator r, IMovieSender sender, ILogger log, - OmbiUserManager manager, IRepository rl) : base(user, requestService, r, manager) + OmbiUserManager manager, IRepository rl, ICacheService cache, ISettingsService ombiSettings) : base(user, requestService, r, manager, cache, ombiSettings) { MovieApi = movieApi; NotificationHelper = helper; @@ -45,7 +47,7 @@ namespace Ombi.Core.Engine /// public async Task RequestMovie(MovieRequestViewModel model) { - var movieInfo = await MovieApi.GetMovieInformation(model.TheMovieDbId); + var movieInfo = await MovieApi.GetMovieInformationWithExtraInfo(model.TheMovieDbId); if (movieInfo == null || movieInfo.Id == 0) { return new RequestEngineResult @@ -78,6 +80,9 @@ namespace Ombi.Core.Engine Background = movieInfo.BackdropPath }; + var usDates = movieInfo.ReleaseDates?.Results?.FirstOrDefault(x => x.IsoCode == "US"); + requestModel.DigitalReleaseDate = usDates?.ReleaseDate?.FirstOrDefault(x => x.Type == ReleaseDateType.Digital)?.ReleaseDate; + var ruleResults = (await RunRequestRules(requestModel)).ToList(); if (ruleResults.Any(x => !x.Success)) { @@ -106,7 +111,7 @@ namespace Ombi.Core.Engine return requestEngineResult; } - + // If there are no providers then it's successful but movie has not been sent } @@ -122,7 +127,16 @@ namespace Ombi.Core.Engine /// public async Task> GetRequests(int count, int position) { - var allRequests = await MovieRepository.GetWithUser().Skip(position).Take(count).ToListAsync(); + var shouldHide = await HideFromOtherUsers(); + List allRequests; + if (shouldHide.Hide) + { + allRequests = await MovieRepository.GetWithUser(shouldHide.UserId).Skip(position).Take(count).ToListAsync(); + } + else + { + allRequests = await MovieRepository.GetWithUser().Skip(position).Take(count).ToListAsync(); + } allRequests.ForEach(x => { x.PosterPath = PosterPathHelper.FixPosterPath(x.PosterPath); @@ -136,7 +150,16 @@ namespace Ombi.Core.Engine /// public async Task> GetRequests() { - var allRequests = await MovieRepository.GetWithUser().ToListAsync(); + var shouldHide = await HideFromOtherUsers(); + List allRequests; + if (shouldHide.Hide) + { + allRequests = await MovieRepository.GetWithUser(shouldHide.UserId).ToListAsync(); + } + else + { + allRequests = await MovieRepository.GetWithUser().ToListAsync(); + } return allRequests; } @@ -147,7 +170,16 @@ namespace Ombi.Core.Engine /// public async Task> SearchMovieRequest(string search) { - var allRequests = await MovieRepository.GetWithUser().ToListAsync(); + var shouldHide = await HideFromOtherUsers(); + List allRequests; + if (shouldHide.Hide) + { + allRequests = await MovieRepository.GetWithUser(shouldHide.UserId).ToListAsync(); + } + else + { + allRequests = await MovieRepository.GetWithUser().ToListAsync(); + } var results = allRequests.Where(x => x.Title.Contains(search, CompareOptions.IgnoreCase)).ToList(); results.ForEach(x => { @@ -155,7 +187,7 @@ namespace Ombi.Core.Engine }); return results; } - + public async Task ApproveMovieById(int requestId) { var request = await MovieRepository.Find(requestId); @@ -175,7 +207,7 @@ namespace Ombi.Core.Engine request.Denied = true; // We are denying a request NotificationHelper.Notify(request, NotificationType.RequestDeclined); - await MovieRepository.Update(request); + await MovieRepository.Update(request); return new RequestEngineResult { @@ -335,9 +367,10 @@ namespace Ombi.Core.Engine return new RequestEngineResult { Result = true, Message = $"{movieName} has been successfully added!" }; } - public IEnumerable Filter(FilterViewModel vm) + public async Task> Filter(FilterViewModel vm) { - var requests = MovieRepository.GetWithUser(); + var shouldHide = await HideFromOtherUsers(); + var requests = shouldHide.Hide ? MovieRepository.GetWithUser(shouldHide.UserId) : MovieRepository.GetWithUser(); switch (vm.AvailabilityFilter) { case FilterType.None: diff --git a/src/Ombi.Core/Engine/MovieSearchEngine.cs b/src/Ombi.Core/Engine/MovieSearchEngine.cs index 4e105d38d..448ff9235 100644 --- a/src/Ombi.Core/Engine/MovieSearchEngine.cs +++ b/src/Ombi.Core/Engine/MovieSearchEngine.cs @@ -12,26 +12,26 @@ using System.Threading.Tasks; 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; namespace Ombi.Core.Engine { public class MovieSearchEngine : BaseMediaEngine, IMovieEngine { public MovieSearchEngine(IPrincipal identity, IRequestServiceMain service, IMovieDbApi movApi, IMapper mapper, - ILogger logger, IRuleEvaluator r, OmbiUserManager um, ICacheService mem) - : base(identity, service, r, um) + ILogger logger, IRuleEvaluator r, OmbiUserManager um, ICacheService mem, ISettingsService s) + : base(identity, service, r, um, mem, s) { MovieApi = movApi; Mapper = mapper; Logger = logger; - MemCache = mem; } private IMovieDbApi MovieApi { get; } private IMapper Mapper { get; } private ILogger Logger { get; } - private ICacheService MemCache { get; } /// /// Lookups the imdb information. @@ -40,7 +40,7 @@ namespace Ombi.Core.Engine /// public async Task LookupImdbInformation(int theMovieDbId) { - var movieInfo = await MovieApi.GetMovieInformationWithVideo(theMovieDbId); + var movieInfo = await MovieApi.GetMovieInformationWithExtraInfo(theMovieDbId); var viewMovie = Mapper.Map(movieInfo); return await ProcessSingleMovie(viewMovie, true); @@ -63,13 +63,29 @@ namespace Ombi.Core.Engine return null; } + /// + /// Get similar movies to the id passed in + /// + /// + /// + public async Task> SimilarMovies(int theMovieDbId) + { + var result = await MovieApi.SimilarMovies(theMovieDbId); + if (result != null) + { + Logger.LogDebug("Search Result: {result}", result); + return await TransformMovieResultsToResponse(result.Take(10)); // Take 10 to stop us overloading the API + } + return null; + } + /// /// Gets popular movies. /// /// public async Task> PopularMovies() { - var result = await MemCache.GetOrAdd(CacheKeys.PopularMovies, async () => await MovieApi.PopularMovies(), DateTime.Now.AddHours(12)); + var result = await Cache.GetOrAdd(CacheKeys.PopularMovies, async () => await MovieApi.PopularMovies(), DateTime.Now.AddHours(12)); if (result != null) { Logger.LogDebug("Search Result: {result}", result); @@ -84,7 +100,7 @@ namespace Ombi.Core.Engine /// public async Task> TopRatedMovies() { - var result = await MemCache.GetOrAdd(CacheKeys.TopRatedMovies, async () => await MovieApi.TopRated(), DateTime.Now.AddHours(12)); + var result = await Cache.GetOrAdd(CacheKeys.TopRatedMovies, async () => await MovieApi.TopRated(), DateTime.Now.AddHours(12)); if (result != null) { Logger.LogDebug("Search Result: {result}", result); @@ -99,7 +115,7 @@ namespace Ombi.Core.Engine /// public async Task> UpcomingMovies() { - var result = await MemCache.GetOrAdd(CacheKeys.UpcomingMovies, async () => await MovieApi.Upcoming(), DateTime.Now.AddHours(12)); + var result = await Cache.GetOrAdd(CacheKeys.UpcomingMovies, async () => await MovieApi.Upcoming(), DateTime.Now.AddHours(12)); if (result != null) { Logger.LogDebug("Search Result: {result}", result); @@ -114,7 +130,7 @@ namespace Ombi.Core.Engine /// public async Task> NowPlayingMovies() { - var result = await MemCache.GetOrAdd(CacheKeys.NowPlayingMovies, async () => await MovieApi.NowPlaying(), DateTime.Now.AddHours(12)); + var result = await Cache.GetOrAdd(CacheKeys.NowPlayingMovies, async () => await MovieApi.NowPlaying(), DateTime.Now.AddHours(12)); if (result != null) { Logger.LogDebug("Search Result: {result}", result); @@ -141,6 +157,8 @@ namespace Ombi.Core.Engine 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; } viewMovie.TheMovieDbId = viewMovie.Id.ToString(); diff --git a/src/Ombi.Core/Engine/TvRequestEngine.cs b/src/Ombi.Core/Engine/TvRequestEngine.cs index 6d8f2a637..f8fc33e9b 100644 --- a/src/Ombi.Core/Engine/TvRequestEngine.cs +++ b/src/Ombi.Core/Engine/TvRequestEngine.cs @@ -17,6 +17,8 @@ using Ombi.Core.Helpers; using Ombi.Core.Rule; using Ombi.Core.Rule.Interfaces; using Ombi.Core.Senders; +using Ombi.Core.Settings; +using Ombi.Settings.Settings.Models; using Ombi.Store.Entities.Requests; using Ombi.Store.Repository; @@ -26,7 +28,7 @@ namespace Ombi.Core.Engine { public TvRequestEngine(ITvMazeApi tvApi, IRequestServiceMain requestService, IPrincipal user, INotificationHelper helper, IRuleEvaluator rule, OmbiUserManager manager, - ITvSender sender, IAuditRepository audit, IRepository rl) : base(user, requestService, rule, manager) + ITvSender sender, IAuditRepository audit, IRepository rl, ISettingsService settings, ICacheService cache) : base(user, requestService, rule, manager, cache, settings) { TvApi = tvApi; NotificationHelper = helper; @@ -128,45 +130,136 @@ namespace Ombi.Core.Engine public async Task> GetRequests(int count, int position) { - var allRequests = await TvRepository.Get() - .Include(x => x.ChildRequests) + 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) + .Skip(position).Take(count).ToListAsync(); + + // Filter out children + + FilterChildren(allRequests, shouldHide); + } + else + { + allRequests = await TvRepository.Get() + .Include(x => x.ChildRequests) .ThenInclude(x => x.SeasonRequests) .ThenInclude(x => x.Episodes) - .Skip(position).Take(count).ToListAsync(); + .Skip(position).Take(count).ToListAsync(); + } + return allRequests; } public async Task>>> GetRequestsTreeNode(int count, int position) { - var allRequests = await TvRepository.Get() - .Include(x => x.ChildRequests) + 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) - .Skip(position).Take(count).ToListAsync(); + .ThenInclude(x => x.Episodes) + .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) + .Skip(position).Take(count).ToListAsync(); + } return ParseIntoTreeNode(allRequests); } public async Task> GetRequests() { - var allRequests = TvRepository.Get(); + var shouldHide = await HideFromOtherUsers(); + IQueryable allRequests; + if (shouldHide.Hide) + { + allRequests = TvRepository.Get(shouldHide.UserId); + + FilterChildren(allRequests, shouldHide); + } + else + { + allRequests = TvRepository.Get(); + } + return await allRequests.ToListAsync(); } + private static void FilterChildren(IEnumerable allRequests, HideResult shouldHide) + { + // Filter out children + foreach (var t in allRequests) + { + for (var j = 0; j < t.ChildRequests.Count; j++) + { + var child = t.ChildRequests[j]; + if (child.RequestedUserId != shouldHide.UserId) + { + t.ChildRequests.RemoveAt(j); + j--; + } + } + } + } + public async Task> GetAllChldren(int tvId) { - return await TvRepository.GetChild().Include(x => x.SeasonRequests).Where(x => x.ParentRequestId == tvId).ToListAsync(); + var shouldHide = await HideFromOtherUsers(); + List allRequests; + if (shouldHide.Hide) + { + allRequests = await TvRepository.GetChild(shouldHide.UserId).Include(x => x.SeasonRequests).Where(x => x.ParentRequestId == tvId).ToListAsync(); + } + else + { + allRequests = await TvRepository.GetChild().Include(x => x.SeasonRequests).Where(x => x.ParentRequestId == tvId).ToListAsync(); + } + + return allRequests; } public async Task> SearchTvRequest(string search) { - var allRequests = TvRepository.Get(); + 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(); return results; } public async Task>>> SearchTvRequestTree(string search) { - var allRequests = TvRepository.Get(); + 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(); return ParseIntoTreeNode(results); } @@ -177,6 +270,13 @@ namespace Ombi.Core.Engine var allRequests = TvRepository.Get(); var results = await allRequests.FirstOrDefaultAsync(x => x.Id == request.Id); + results.TvDbId = request.TvDbId; + results.ImdbId = request.ImdbId; + results.Overview = request.Overview; + results.PosterPath = PosterPathHelper.FixPosterPath(request.PosterPath); + results.QualityOverride = request.QualityOverride; + results.RootFolder = request.RootFolder; + await TvRepository.Update(results); return results; } @@ -395,7 +495,7 @@ namespace Ombi.Core.Engine var result = await TvSender.Send(model); if (result.Success) { - return new RequestEngineResult {Result = true}; + return new RequestEngineResult { Result = true }; } return new RequestEngineResult { @@ -411,7 +511,7 @@ namespace Ombi.Core.Engine RequestType = RequestType.TvShow, }); - return new RequestEngineResult {Result = true}; + return new RequestEngineResult { Result = true }; } } } \ No newline at end of file diff --git a/src/Ombi.Core/Engine/TvSearchEngine.cs b/src/Ombi.Core/Engine/TvSearchEngine.cs index 2fc3e6c12..1664ecfa7 100644 --- a/src/Ombi.Core/Engine/TvSearchEngine.cs +++ b/src/Ombi.Core/Engine/TvSearchEngine.cs @@ -19,6 +19,7 @@ using Ombi.Store.Repository.Requests; using Microsoft.Extensions.Caching.Memory; using Ombi.Core.Authentication; using Ombi.Helpers; +using Ombi.Settings.Settings.Models; namespace Ombi.Core.Engine { @@ -26,8 +27,8 @@ namespace Ombi.Core.Engine { public TvSearchEngine(IPrincipal identity, IRequestServiceMain service, ITvMazeApi tvMaze, IMapper mapper, ISettingsService plexSettings, ISettingsService embySettings, IPlexContentRepository repo, IEmbyContentRepository embyRepo, ITraktApi trakt, IRuleEvaluator r, OmbiUserManager um, - ICacheService memCache) - : base(identity, service, r, um) + ICacheService memCache, ISettingsService s) + : base(identity, service, r, um, memCache, s) { TvMazeApi = tvMaze; Mapper = mapper; @@ -36,7 +37,6 @@ namespace Ombi.Core.Engine PlexContentRepo = repo; TraktApi = trakt; EmbyContentRepo = embyRepo; - MemCache = memCache; } private ITvMazeApi TvMazeApi { get; } @@ -46,7 +46,6 @@ namespace Ombi.Core.Engine private IPlexContentRepository PlexContentRepo { get; } private IEmbyContentRepository EmbyContentRepo { get; } private ITraktApi TraktApi { get; } - private ICacheService MemCache { get; } public async Task> Search(string searchTerm) { @@ -122,33 +121,61 @@ namespace Ombi.Core.Engine return ParseIntoTreeNode(result); } - public async Task>> Popular() + public async Task>> PopularTree() { - var result = await MemCache.GetOrAdd(CacheKeys.PopularTv, async () => await TraktApi.GetPopularShows(), DateTime.Now.AddHours(12)); + 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>> Anticipated() + public async Task> Popular() { - var result = await MemCache.GetOrAdd(CacheKeys.AnticipatedTv, async () => await TraktApi.GetAnticipatedShows(), DateTime.Now.AddHours(12)); - var processed= await ProcessResults(result); + var result = await Cache.GetOrAdd(CacheKeys.PopularTv, async () => await TraktApi.GetPopularShows(), DateTime.Now.AddHours(12)); + var processed = await 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); + return processed; + } - public async Task>> MostWatches() + public async Task>> MostWatchesTree() { - var result = await MemCache.GetOrAdd(CacheKeys.MostWatchesTv, async () => await TraktApi.GetMostWatchesShows(), DateTime.Now.AddHours(12)); + 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); + return processed; + } - public async Task>> Trending() + public async Task>> TrendingTree() { - var result = await MemCache.GetOrAdd(CacheKeys.TrendingTv, async () => await TraktApi.GetTrendingShows(), DateTime.Now.AddHours(12)); + 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); + return processed; + } + private static TreeNode ParseIntoTreeNode(SearchTvShowViewModel result) { return new TreeNode diff --git a/src/Ombi.Core/Helpers/EmailValidator.cs b/src/Ombi.Core/Helpers/EmailValidator.cs index af8d0dfd0..ee15856f7 100644 --- a/src/Ombi.Core/Helpers/EmailValidator.cs +++ b/src/Ombi.Core/Helpers/EmailValidator.cs @@ -1,5 +1,6 @@ using System; using System.Globalization; +using System.Net.Mail; using System.Text.RegularExpressions; namespace Ombi.Core.Helpers @@ -31,12 +32,11 @@ namespace Ombi.Core.Helpers // Return true if strIn is in valid e-mail format. try { - return Regex.IsMatch(strIn, - @"^(?("")("".+?(? SendMassEmail(MassEmailModel model); + } +} \ No newline at end of file diff --git a/src/Ombi.Core/Models/MassEmailModel.cs b/src/Ombi.Core/Models/MassEmailModel.cs new file mode 100644 index 000000000..ad09f0cb9 --- /dev/null +++ b/src/Ombi.Core/Models/MassEmailModel.cs @@ -0,0 +1,40 @@ +#region Copyright +// /************************************************************************ +// Copyright (c) 2018 Jamie Rees +// File: MassEmailModel.cs +// Created By: Jamie Rees +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// ************************************************************************/ +#endregion + +using System.Collections.Generic; +using Ombi.Store.Entities; + +namespace Ombi.Core.Models +{ + public class MassEmailModel + { + public string Subject { get; set; } + public string Body { get; set; } + + public List Users { get; set; } + } +} \ No newline at end of file diff --git a/src/Ombi.Core/Models/Search/SearchMovieViewModel.cs b/src/Ombi.Core/Models/Search/SearchMovieViewModel.cs index 869a2c4b7..4d0d05a49 100644 --- a/src/Ombi.Core/Models/Search/SearchMovieViewModel.cs +++ b/src/Ombi.Core/Models/Search/SearchMovieViewModel.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using Ombi.Api.TheMovieDb.Models; using Ombi.Store.Entities; namespace Ombi.Core.Models.Search @@ -25,5 +26,7 @@ namespace Ombi.Core.Models.Search public int RootPathOverride { get; set; } public int QualityOverride { get; set; } public override RequestType Type => RequestType.Movie; + public ReleaseDatesDto ReleaseDates { get; set; } + public DateTime? DigitalReleaseDate { get; set; } } } \ No newline at end of file diff --git a/src/Ombi.Core/Senders/MassEmailSender.cs b/src/Ombi.Core/Senders/MassEmailSender.cs new file mode 100644 index 000000000..604224b34 --- /dev/null +++ b/src/Ombi.Core/Senders/MassEmailSender.cs @@ -0,0 +1,95 @@ +#region Copyright +// /************************************************************************ +// Copyright (c) 2018 Jamie Rees +// File: MassEmailSender.cs +// Created By: Jamie Rees +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// ************************************************************************/ +#endregion + +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using Ombi.Core.Authentication; +using Ombi.Core.Models; +using Ombi.Core.Settings; +using Ombi.Helpers; +using Ombi.Notifications; +using Ombi.Notifications.Models; +using Ombi.Settings.Settings.Models; +using Ombi.Settings.Settings.Models.Notifications; +using Ombi.Store.Entities; + +namespace Ombi.Core.Senders +{ + public class MassEmailSender : IMassEmailSender + { + public MassEmailSender(IEmailProvider emailProvider, ISettingsService custom, ISettingsService email, + ILogger log, OmbiUserManager manager) + { + _email = emailProvider; + _customizationService = custom; + _emailService = email; + _log = log; + _userManager = manager; + } + + private readonly IEmailProvider _email; + private readonly ISettingsService _customizationService; + private readonly ISettingsService _emailService; + private readonly ILogger _log; + private readonly OmbiUserManager _userManager; + + public async Task SendMassEmail(MassEmailModel model) + { + var customization = await _customizationService.GetSettingsAsync(); + var email = await _emailService.GetSettingsAsync(); + var messagesSent = new List(); + foreach (var user in model.Users) + { + var fullUser = await _userManager.Users.FirstOrDefaultAsync(x => x.Id == user.Id); + if (!fullUser.Email.HasValue()) + { + _log.LogInformation("User {0} has no email, cannot send mass email to this user", fullUser.UserName); + continue; + } + var resolver = new NotificationMessageResolver(); + var curlys = new NotificationMessageCurlys(); + curlys.Setup(fullUser, customization); + var template = new NotificationTemplates() { Message = model.Body, Subject = model.Subject }; + var content = resolver.ParseMessage(template, curlys); + var msg = new NotificationMessage + { + Message = content.Message, + To = fullUser.Email, + Subject = content.Subject + }; + messagesSent.Add(_email.SendAdHoc(msg, email)); + _log.LogInformation("Sent mass email to user {0} @ {1}", fullUser.UserName, fullUser.Email); + } + + await Task.WhenAll(messagesSent); + + return true; + } + } +} \ No newline at end of file diff --git a/src/Ombi.Core/Senders/MovieSender.cs b/src/Ombi.Core/Senders/MovieSender.cs index 5d19b5b06..e57a5bf2a 100644 --- a/src/Ombi.Core/Senders/MovieSender.cs +++ b/src/Ombi.Core/Senders/MovieSender.cs @@ -48,7 +48,7 @@ namespace Ombi.Core.Senders var dogSettings = await DogNzbSettings.GetSettingsAsync(); if (dogSettings.Enabled) { - await SendToDogNzb(model,dogSettings); + await SendToDogNzb(model, dogSettings); return new SenderResult { Success = true, @@ -95,18 +95,40 @@ namespace Ombi.Core.Senders } var rootFolderPath = model.RootPathOverride <= 0 ? settings.DefaultRootPath : await RadarrRootPath(model.RootPathOverride, settings); - var result = await RadarrApi.AddMovie(model.TheMovieDbId, model.Title, model.ReleaseDate.Year, qualityToUse, rootFolderPath, settings.ApiKey, settings.FullUri, !settings.AddOnly, settings.MinimumAvailability); - if (!string.IsNullOrEmpty(result.Error?.message)) + // Check if the movie already exists? Since it could be unmonitored + var movies = await RadarrApi.GetMovies(settings.ApiKey, settings.FullUri); + var existingMovie = movies.FirstOrDefault(x => x.tmdbId == model.TheMovieDbId); + if (existingMovie == null) { - Log.LogError(LoggingEvents.RadarrCacher,result.Error.message); - return new SenderResult { Success = false, Message = result.Error.message, Sent = false }; + var result = await RadarrApi.AddMovie(model.TheMovieDbId, model.Title, model.ReleaseDate.Year, + qualityToUse, rootFolderPath, settings.ApiKey, settings.FullUri, !settings.AddOnly, + settings.MinimumAvailability); + + if (!string.IsNullOrEmpty(result.Error?.message)) + { + Log.LogError(LoggingEvents.RadarrCacher, result.Error.message); + return new SenderResult { Success = false, Message = result.Error.message, Sent = false }; + } + if (!string.IsNullOrEmpty(result.title)) + { + return new SenderResult { Success = true, Sent = false }; + } + return new SenderResult { Success = true, Sent = false }; } - if (!string.IsNullOrEmpty(result.title)) + // We have the movie, check if we can request it or change the status + if (!existingMovie.monitored) { - return new SenderResult { Success = true, Sent = false }; + // let's set it to monitored and search for it + existingMovie.monitored = true; + await RadarrApi.UpdateMovie(existingMovie, settings.ApiKey, settings.FullUri); + // Search for it + await RadarrApi.MovieSearch(new[] { existingMovie.id }, settings.ApiKey, settings.FullUri); + + return new SenderResult { Success = true, Sent = true }; } - return new SenderResult { Success = true, Sent = false }; + + return new SenderResult { Success = false, Sent = false, Message = "Movie is already monitored" }; } private async Task RadarrRootPath(int overrideId, RadarrSettings settings) diff --git a/src/Ombi.Core/Senders/TvSender.cs b/src/Ombi.Core/Senders/TvSender.cs index 303cc9ee1..cdf834fa5 100644 --- a/src/Ombi.Core/Senders/TvSender.cs +++ b/src/Ombi.Core/Senders/TvSender.cs @@ -105,9 +105,8 @@ namespace Ombi.Core.Senders /// /// /// - /// This is for any qualities overriden from the UI /// - public async Task SendToSonarr(ChildRequests model, string qualityId = null) + public async Task SendToSonarr(ChildRequests model) { var s = await SonarrSettings.GetSettingsAsync(); if (!s.Enabled) @@ -118,15 +117,12 @@ namespace Ombi.Core.Senders { return null; } - var qualityProfile = 0; - if (!string.IsNullOrEmpty(qualityId)) // try to parse the passed in quality, otherwise use the settings default quality - { - int.TryParse(qualityId, out qualityProfile); - } - if (qualityProfile <= 0) + int.TryParse(s.QualityProfile, out var qualityToUse); + + if (model.ParentRequest.QualityOverride.HasValue) { - int.TryParse(s.QualityProfile, out qualityProfile); + qualityToUse = model.ParentRequest.QualityOverride.Value; } // Get the root path from the rootfolder selected. @@ -151,7 +147,7 @@ namespace Ombi.Core.Senders monitored = true, seasonFolder = s.SeasonFolders, rootFolderPath = rootFolderPath, - qualityProfileId = qualityProfile, + qualityProfileId = qualityToUse, titleSlug = model.ParentRequest.Title, addOptions = new AddOptions { diff --git a/src/Ombi.DependencyInjection/IocExtensions.cs b/src/Ombi.DependencyInjection/IocExtensions.cs index 472c88c14..7678e3536 100644 --- a/src/Ombi.DependencyInjection/IocExtensions.cs +++ b/src/Ombi.DependencyInjection/IocExtensions.cs @@ -81,6 +81,7 @@ namespace Ombi.DependencyInjection services.AddTransient(); services.AddTransient(); services.AddTransient(); + services.AddTransient(); } public static void RegisterHttp(this IServiceCollection services) { diff --git a/src/Ombi.Helpers/AssemblyHelper.cs b/src/Ombi.Helpers/AssemblyHelper.cs index 5d3bb5bf5..d2c266369 100644 --- a/src/Ombi.Helpers/AssemblyHelper.cs +++ b/src/Ombi.Helpers/AssemblyHelper.cs @@ -9,7 +9,7 @@ namespace Ombi.Helpers var version = Assembly.GetEntryAssembly() .GetCustomAttribute() .InformationalVersion; - return version.Equals("1.0.0") ? "3.0.0-DotNetCore" : version; + return version.Equals("1.0.0") ? "3.0.0-develop" : version; } } } \ No newline at end of file diff --git a/src/Ombi.Mapping/Profiles/MovieProfile.cs b/src/Ombi.Mapping/Profiles/MovieProfile.cs index 29b2f3da2..d7e43fc50 100644 --- a/src/Ombi.Mapping/Profiles/MovieProfile.cs +++ b/src/Ombi.Mapping/Profiles/MovieProfile.cs @@ -42,7 +42,19 @@ namespace Ombi.Mapping.Profiles .ForMember(dest => dest.Runtime, opts => opts.MapFrom(src => src.runtime)) .ForMember(dest => dest.Status, opts => opts.MapFrom(src => src.status)) .ForMember(dest => dest.Tagline, opts => opts.MapFrom(src => src.tagline)) - .ForMember(dest => dest.VoteCount, opts => opts.MapFrom(src => src.vote_count)); + .ForMember(dest => dest.VoteCount, opts => opts.MapFrom(src => src.vote_count)) + .ForMember(dest => dest.ReleaseDates, opts => opts.MapFrom(src => src.release_dates)); + + CreateMap() + .ForMember(x => x.Results, o => o.MapFrom(src => src.results)); + + CreateMap() + .ForMember(x => x.ReleaseDate, o => o.MapFrom(s => s.release_dates)) + .ForMember(x => x.IsoCode, o => o.MapFrom(s => s.iso_3166_1)); + CreateMap() + .ForMember(x => x.ReleaseDate, o => o.MapFrom(s => s.release_date)) + .ForMember(x => x.Type, o => o.MapFrom(s => s.Type)); + CreateMap(); CreateMap().ReverseMap(); diff --git a/src/Ombi.Notifications/NotificationMessageCurlys.cs b/src/Ombi.Notifications/NotificationMessageCurlys.cs index f18f145e5..d958cfc74 100644 --- a/src/Ombi.Notifications/NotificationMessageCurlys.cs +++ b/src/Ombi.Notifications/NotificationMessageCurlys.cs @@ -81,11 +81,14 @@ namespace Ombi.Notifications IssueStatus = opts.Substitutes.TryGetValue("IssueStatus", out val) ? val : string.Empty; IssueSubject = opts.Substitutes.TryGetValue("IssueSubject", out val) ? val : string.Empty; NewIssueComment = opts.Substitutes.TryGetValue("NewIssueComment", out val) ? val : string.Empty; - IssueUser = opts.Substitutes.TryGetValue("IssueUser", out val) ? val : string.Empty; + RequestedUser = opts.Substitutes.TryGetValue("IssueUser", out val) ? val : string.Empty; } // User Defined public string RequestedUser { get; set; } + public string UserName => RequestedUser; + public string IssueUser => RequestedUser; + public string Title { get; set; } public string RequestedDate { get; set; } public string Type { get; set; } @@ -102,7 +105,6 @@ namespace Ombi.Notifications public string IssueStatus { get; set; } public string IssueSubject { get; set; } public string NewIssueComment { get; set; } - public string IssueUser { get; set; } // System Defined private string LongDate => DateTime.Now.ToString("D"); @@ -134,6 +136,7 @@ namespace Ombi.Notifications {nameof(IssueSubject),IssueSubject}, {nameof(NewIssueComment),NewIssueComment}, {nameof(IssueUser),IssueUser}, + {nameof(UserName),UserName}, }; } } \ No newline at end of file diff --git a/src/Ombi.Notifications/NotificationService.cs b/src/Ombi.Notifications/NotificationService.cs index d76f38e69..d3651871f 100644 --- a/src/Ombi.Notifications/NotificationService.cs +++ b/src/Ombi.Notifications/NotificationService.cs @@ -45,7 +45,7 @@ namespace Ombi.Notifications private List NotificationAgents { get; } private ILogger Log { get; } - /// + /// ^ /// Sends a notification to the user. This one is used in normal notification scenarios /// /// The model. diff --git a/src/Ombi.Schedule/Jobs/Emby/EmbyUserImporter.cs b/src/Ombi.Schedule/Jobs/Emby/EmbyUserImporter.cs index a28a56ba2..2dadd4bd4 100644 --- a/src/Ombi.Schedule/Jobs/Emby/EmbyUserImporter.cs +++ b/src/Ombi.Schedule/Jobs/Emby/EmbyUserImporter.cs @@ -94,6 +94,13 @@ namespace Ombi.Schedule.Jobs.Emby var existingEmbyUser = allUsers.FirstOrDefault(x => x.ProviderUserId == embyUser.Id); if (existingEmbyUser == null) { + + if (!embyUser.ConnectUserName.HasValue() && !embyUser.Name.HasValue()) + { + _log.LogInformation("Could not create Emby user since the have no username, PlexUserId: {0}", embyUser.Id); + continue; + } + // Create this users // We do not store a password against the user since they will authenticate via Plex var newUser = new OmbiUser diff --git a/src/Ombi.Schedule/Jobs/Ombi/OmbiAutomaticUpdater.cs b/src/Ombi.Schedule/Jobs/Ombi/OmbiAutomaticUpdater.cs index 42688376a..da3b3305c 100644 --- a/src/Ombi.Schedule/Jobs/Ombi/OmbiAutomaticUpdater.cs +++ b/src/Ombi.Schedule/Jobs/Ombi/OmbiAutomaticUpdater.cs @@ -106,14 +106,23 @@ namespace Ombi.Schedule.Jobs.Ombi { // Let's download the correct zip var desc = RuntimeInformation.OSDescription; - var proce = RuntimeInformation.ProcessArchitecture; + var process = RuntimeInformation.ProcessArchitecture; - Logger.LogDebug(LoggingEvents.Updater, "OS Information: {0} {1}", desc, proce); + Logger.LogDebug(LoggingEvents.Updater, "OS Information: {0} {1}", desc, process); Downloads download; if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { Logger.LogDebug(LoggingEvents.Updater, "We are Windows"); - download = updates.Downloads.FirstOrDefault(x => x.Name.Contains("windows.zip", CompareOptions.IgnoreCase)); + if (process == Architecture.X64) + { + download = updates.Downloads.FirstOrDefault(x => + x.Name.Contains("windows.", CompareOptions.IgnoreCase)); + } + else + { + download = updates.Downloads.FirstOrDefault(x => + x.Name.Contains("windows-32bit", CompareOptions.IgnoreCase)); + } } else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) { @@ -123,13 +132,16 @@ namespace Ombi.Schedule.Jobs.Ombi else { Logger.LogDebug(LoggingEvents.Updater, "We are linux"); - if (RuntimeInformation.OSDescription.Contains("arm", CompareOptions.IgnoreCase)) + if (process == Architecture.Arm) + { + download = updates.Downloads.FirstOrDefault(x => x.Name.Contains("arm.", CompareOptions.IgnoreCase)); + } else if (process == Architecture.Arm64) { - download = updates.Downloads.FirstOrDefault(x => x.Name.Contains("arm", CompareOptions.IgnoreCase)); + download = updates.Downloads.FirstOrDefault(x => x.Name.Contains("arm64.", CompareOptions.IgnoreCase)); } else { - download = updates.Downloads.FirstOrDefault(x => x.Name.Contains("linux", CompareOptions.IgnoreCase)); + download = updates.Downloads.FirstOrDefault(x => x.Name.Contains("linux.", CompareOptions.IgnoreCase)); } } if (download == null) @@ -247,24 +259,16 @@ namespace Ombi.Schedule.Jobs.Ombi sb.Append($"--windowsServiceName \"{settings.WindowsServiceName}\" "); } var sb2 = new StringBuilder(); - var hasStartupArgs = false; if (url?.Value.HasValue() ?? false) { - hasStartupArgs = true; - sb2.Append(url.Value); + sb2.Append($" --host {url.Value}"); } if (storage?.Value.HasValue() ?? false) { - hasStartupArgs = true; - sb2.Append(storage.Value); - } - if (hasStartupArgs) - { - sb.Append($"--startupArgs {sb2.ToString()}"); + sb2.Append($" --storage {storage.Value}"); } return sb.ToString(); - //return string.Join(" ", currentLocation, processName, url?.Value ?? string.Empty, storage?.Value ?? string.Empty); } private void RunScript(UpdateSettings settings, string downloadUrl) diff --git a/src/Ombi.Schedule/Jobs/Plex/PlexAvailabilityChecker.cs b/src/Ombi.Schedule/Jobs/Plex/PlexAvailabilityChecker.cs index c6b876c21..343c4256e 100644 --- a/src/Ombi.Schedule/Jobs/Plex/PlexAvailabilityChecker.cs +++ b/src/Ombi.Schedule/Jobs/Plex/PlexAvailabilityChecker.cs @@ -67,6 +67,16 @@ namespace Ombi.Schedule.Jobs.Plex { seriesEpisodes = plexEpisodes.Where(x => x.Series.TvDbId == tvDbId.ToString()); } + + if (!seriesEpisodes.Any()) + { + // 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()); + + } + foreach (var season in child.SeasonRequests) { foreach (var episode in season.Episodes) diff --git a/src/Ombi.Schedule/Jobs/Plex/PlexContentSync.cs b/src/Ombi.Schedule/Jobs/Plex/PlexContentSync.cs index 3eda1cdad..3c00a7a29 100644 --- a/src/Ombi.Schedule/Jobs/Plex/PlexContentSync.cs +++ b/src/Ombi.Schedule/Jobs/Plex/PlexContentSync.cs @@ -30,6 +30,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Hangfire; +using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; using Ombi.Api.Plex; using Ombi.Api.Plex.Models; @@ -81,7 +82,7 @@ namespace Ombi.Schedule.Jobs.Plex } catch (Exception e) { - Logger.LogWarning(LoggingEvents.Cacher, e, "Exception thrown when attempting to cache the Plex Content"); + Logger.LogWarning(LoggingEvents.PlexContentCacher, e, "Exception thrown when attempting to cache the Plex Content"); } Logger.LogInformation("Starting EP Cacher"); @@ -92,91 +93,145 @@ namespace Ombi.Schedule.Jobs.Plex { foreach (var servers in plexSettings.Servers ?? new List()) { + try + { + Logger.LogInformation("Starting to cache the content on server {0}", servers.Name); + await ProcessServer(servers); + } + catch (Exception e) + { + Logger.LogWarning(LoggingEvents.PlexContentCacher, e, "Exception thrown when attempting to cache the Plex Content in server {0}", servers.Name); + } + } + } - Logger.LogInformation("Getting all content from server {0}", servers.Name); - var allContent = await GetAllContent(servers); - Logger.LogInformation("We found {0} items", allContent.Count); + private async Task ProcessServer(PlexServers servers) + { + Logger.LogInformation("Getting all content from server {0}", servers.Name); + var allContent = await GetAllContent(servers); + Logger.LogInformation("We found {0} items", allContent.Count); - // Let's now process this. - var contentToAdd = new List(); - foreach (var content in allContent) + // Let's now process this. + var contentToAdd = new HashSet(); + foreach (var content in allContent) + { + if (content.viewGroup.Equals(PlexMediaType.Show.ToString(), StringComparison.CurrentCultureIgnoreCase)) { - if (content.viewGroup.Equals(PlexMediaType.Show.ToString(), StringComparison.CurrentCultureIgnoreCase)) + // Process Shows + Logger.LogInformation("Processing TV Shows"); + foreach (var show in content.Metadata ?? new Metadata[] { }) { - // Process Shows - Logger.LogInformation("Processing TV Shows"); - foreach (var show in content.Metadata ?? new Metadata[] { }) + var seasonList = await PlexApi.GetSeasons(servers.PlexAuthToken, servers.FullUri, + show.ratingKey); + var seasonsContent = new List(); + foreach (var season in seasonList.MediaContainer.Metadata) { - var seasonList = await PlexApi.GetSeasons(servers.PlexAuthToken, servers.FullUri, - show.ratingKey); - var seasonsContent = new List(); - foreach (var season in seasonList.MediaContainer.Metadata) + seasonsContent.Add(new PlexSeasonsContent { - seasonsContent.Add(new PlexSeasonsContent - { - ParentKey = season.parentRatingKey, - SeasonKey = season.ratingKey, - SeasonNumber = season.index, - PlexContentId = show.ratingKey - }); + ParentKey = season.parentRatingKey, + SeasonKey = season.ratingKey, + SeasonNumber = season.index, + PlexContentId = show.ratingKey + }); + } + + // Do we already have this item? + // Let's try and match + var existingContent = await Repo.GetFirstContentByCustom(x => x.Title == show.title + && x.ReleaseYear == show.year.ToString() + && x.Type == PlexMediaTypeEntity.Show); + + // Just double check the rating key, since this is our unique constraint + var existingKey = await Repo.GetByKey(show.ratingKey); + + if (existingKey != null) + { + // Damn son. + // Let's check if they match up + var doesMatch = show.title.Equals(existingKey.Title, + StringComparison.CurrentCulture); + if (!doesMatch) + { + // Something fucked up on Plex at somepoint... Damn, rebuild of lib maybe? + // Lets delete the matching key + await Repo.Delete(existingKey); + existingKey = null; } - - // Do we already have this item? - // Let's try and match - var existingContent = await Repo.GetFirstContentByCustom(x => x.Title == show.title - && x.ReleaseYear == show.year.ToString() - && x.Type == PlexMediaTypeEntity.Show); - - if (existingContent == null) + } + + if (existingContent != null) + { + // Just check the key + if (existingKey != null) + { + // The rating key is all good! + } + else { - // Just check the key - var hasSameKey = await Repo.GetByKey(show.ratingKey); - if (hasSameKey != null) + // This means the rating key has changed somehow. + // Should probably delete this and get the new one + var oldKey = existingContent.Key; + Repo.DeleteWithoutSave(existingContent); + + // Because we have changed the rating key, we need to change all children too + var episodeToChange = Repo.GetAllEpisodes().Where(x => x.GrandparentKey == oldKey); + if (episodeToChange.Any()) { - existingContent = hasSameKey; + foreach (var e in episodeToChange) + { + Repo.DeleteWithoutSave(e); + } } - + await Repo.SaveChangesAsync(); + existingContent = null; } - // The ratingKey keeps changing... - //var existingContent = await Repo.GetByKey(show.ratingKey); - if (existingContent != null) + } + // The ratingKey keeps changing... + //var existingContent = await Repo.GetByKey(show.ratingKey); + if (existingContent != null) + { + try { - try + Logger.LogInformation("We already have show {0} checking for new seasons", + existingContent.Title); + // Ok so we have it, let's check if there are any new seasons + var itemAdded = false; + foreach (var season in seasonsContent) { - Logger.LogInformation("We already have show {0} checking for new seasons", existingContent.Title); - // Ok so we have it, let's check if there are any new seasons - var itemAdded = false; - foreach (var season in seasonsContent) - { - var seasonExists = existingContent.Seasons.FirstOrDefault(x => x.SeasonKey == season.SeasonKey); + var seasonExists = + existingContent.Seasons.FirstOrDefault(x => x.SeasonKey == season.SeasonKey); - if (seasonExists != null) - { - // We already have this season - continue; - } - - existingContent.Seasons.Add(season); - itemAdded = true; + if (seasonExists != null) + { + // We already have this season + continue; } - if (itemAdded) await Repo.Update(existingContent); - } - catch (Exception e) - { - Logger.LogError(LoggingEvents.PlexContentCacher, e, "Exception when adding new seasons to title {0}", existingContent.Title); + existingContent.Seasons.Add(season); + itemAdded = true; } + + if (itemAdded) await Repo.Update(existingContent); } - else + catch (Exception e) + { + Logger.LogError(LoggingEvents.PlexContentCacher, e, + "Exception when adding new seasons to title {0}", existingContent.Title); + } + } + else + { + try { - Logger.LogInformation("New show {0}, so add it", show.title); // Get the show metadata... This sucks since the `metadata` var contains all information about the show // 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 providerIds = + PlexHelper.GetProviderIdFromPlexGuid(showMetadata.MediaContainer.Metadata.FirstOrDefault() + .guid); var item = new PlexServerContent { @@ -201,22 +256,60 @@ namespace Ombi.Schedule.Jobs.Plex item.TvDbId = providerIds.TheTvDb; } + // Let's just double check to make sure we do not have it now we have some id's + var existingImdb = false; + var existingMovieDbId = false; + var existingTvDbId = false; + if (item.ImdbId.HasValue()) + { + existingImdb = await Repo.GetAll().AnyAsync(x => + x.ImdbId == item.ImdbId && x.Type == PlexMediaTypeEntity.Show); + } + if (item.TheMovieDbId.HasValue()) + { + existingMovieDbId = await Repo.GetAll().AnyAsync(x => + x.TheMovieDbId == item.TheMovieDbId && x.Type == PlexMediaTypeEntity.Show); + } + if (item.TvDbId.HasValue()) + { + existingTvDbId = await Repo.GetAll().AnyAsync(x => + x.TvDbId == item.TvDbId && x.Type == PlexMediaTypeEntity.Show); + } + if (existingImdb || existingTvDbId || existingMovieDbId) + { + // We already have it! + continue; + } + item.Seasons.ToList().AddRange(seasonsContent); contentToAdd.Add(item); } + catch (Exception e) + { + Logger.LogError(LoggingEvents.PlexContentCacher, e, "Exception when adding tv show {0}", + show.title); + } + } + if (contentToAdd.Count > 500) + { + await Repo.AddRange(contentToAdd); + contentToAdd.Clear(); } } - if (content.viewGroup.Equals(PlexMediaType.Movie.ToString(), StringComparison.CurrentCultureIgnoreCase)) + } + if (content.viewGroup.Equals(PlexMediaType.Movie.ToString(), StringComparison.CurrentCultureIgnoreCase)) + { + Logger.LogInformation("Processing Movies"); + foreach (var movie in content?.Metadata ?? new Metadata[] { }) { - Logger.LogInformation("Processing Movies"); - foreach (var movie in content?.Metadata ?? new Metadata[] { }) - { - // Let's check if we have this movie + // Let's check if we have this movie + try + { var existing = await Repo.GetFirstContentByCustom(x => x.Title == movie.title - && x.ReleaseYear == movie.year.ToString() - && x.Type == PlexMediaTypeEntity.Movie); + && x.ReleaseYear == movie.year.ToString() + && x.Type == PlexMediaTypeEntity.Movie); // The rating key keeps changing //var existing = await Repo.GetByKey(movie.ratingKey); if (existing != null) @@ -263,19 +356,29 @@ namespace Ombi.Schedule.Jobs.Plex } contentToAdd.Add(item); } - } - if (contentToAdd.Count > 500) - { - await Repo.AddRange(contentToAdd); - contentToAdd = new List(); + catch (Exception e) + { + Logger.LogError(LoggingEvents.PlexContentCacher, e, "Exception when adding new Movie {0}", + movie.title); + } + + if (contentToAdd.Count > 500) + { + await Repo.AddRange(contentToAdd); + contentToAdd.Clear(); + } } } - - if (contentToAdd.Any()) + if (contentToAdd.Count > 500) { await Repo.AddRange(contentToAdd); + contentToAdd.Clear(); } + } + if (contentToAdd.Any()) + { + await Repo.AddRange(contentToAdd); } } diff --git a/src/Ombi.Schedule/Jobs/Plex/PlexEpisodeSync.cs b/src/Ombi.Schedule/Jobs/Plex/PlexEpisodeSync.cs index 308c74489..7d0fde4c7 100644 --- a/src/Ombi.Schedule/Jobs/Plex/PlexEpisodeSync.cs +++ b/src/Ombi.Schedule/Jobs/Plex/PlexEpisodeSync.cs @@ -48,8 +48,9 @@ namespace Ombi.Schedule.Jobs.Plex foreach (var server in s.Servers) { await Cache(server); - BackgroundJob.Enqueue(() => _availabilityChecker.Start()); } + + BackgroundJob.Enqueue(() => _availabilityChecker.Start()); } catch (Exception e) { @@ -61,7 +62,6 @@ namespace Ombi.Schedule.Jobs.Plex { if (!Validate(settings)) { - _log.LogWarning("Validation failed"); return; } @@ -100,21 +100,25 @@ namespace Ombi.Schedule.Jobs.Plex { var currentPosition = 0; var resultCount = settings.EpisodeBatchSize == 0 ? 150 : settings.EpisodeBatchSize; + var currentEpisodes = _repo.GetAllEpisodes(); var episodes = await _api.GetAllEpisodes(settings.PlexAuthToken, settings.FullUri, section.key, currentPosition, resultCount); _log.LogInformation(LoggingEvents.PlexEpisodeCacher, $"Total Epsiodes found for {episodes.MediaContainer.librarySectionTitle} = {episodes.MediaContainer.totalSize}"); // Delete all the episodes because we cannot uniquly match an episode to series every time, // see comment below. - await _repo.ExecuteSql("DELETE FROM PlexEpisode"); - await ProcessEpsiodes(episodes); + // 12.03.2017 - I think we should be able to match them now + //await _repo.ExecuteSql("DELETE FROM PlexEpisode"); + + await ProcessEpsiodes(episodes, currentEpisodes); currentPosition += resultCount; while (currentPosition < episodes.MediaContainer.totalSize) { var ep = await _api.GetAllEpisodes(settings.PlexAuthToken, settings.FullUri, section.key, currentPosition, resultCount); - await ProcessEpsiodes(ep); + + await ProcessEpsiodes(ep, currentEpisodes); _log.LogInformation(LoggingEvents.PlexEpisodeCacher, $"Processed {resultCount} more episodes. Total Remaining {episodes.MediaContainer.totalSize - currentPosition}"); currentPosition += resultCount; } @@ -124,43 +128,69 @@ namespace Ombi.Schedule.Jobs.Plex await _repo.SaveChangesAsync(); } - private async Task ProcessEpsiodes(PlexContainer episodes) + private async Task ProcessEpsiodes(PlexContainer episodes, IQueryable currentEpisodes) { var ep = new HashSet(); - - foreach (var episode in episodes?.MediaContainer?.Metadata ?? new Metadata[]{}) + try { - // I don't think we need to get the metadata, we only need to get the metadata if we need the provider id (TheTvDbid). Why do we need it for episodes? - // We have the parent and grandparent rating keys to link up to the season and series - //var metadata = _api.GetEpisodeMetaData(server.PlexAuthToken, server.FullUri, episode.ratingKey); - - // This does seem to work, it looks like we can somehow get different rating, grandparent and parent keys with episodes. Not sure how. - //var epExists = currentEpisodes.Any(x => episode.ratingKey == x.Key && - // episode.grandparentRatingKey == x.GrandparentKey); - //if (epExists) - //{ - // continue; - //} - - ep.Add(new PlexEpisode + foreach (var episode in episodes?.MediaContainer?.Metadata ?? new Metadata[] { }) { - EpisodeNumber = episode.index, - SeasonNumber = episode.parentIndex, - GrandparentKey = episode.grandparentRatingKey, - ParentKey = episode.parentRatingKey, - Key = episode.ratingKey, - Title = episode.title - }); - } + // I don't think we need to get the metadata, we only need to get the metadata if we need the provider id (TheTvDbid). Why do we need it for episodes? + // We have the parent and grandparent rating keys to link up to the season and series + //var metadata = _api.GetEpisodeMetaData(server.PlexAuthToken, server.FullUri, episode.ratingKey); + + // This does seem to work, it looks like we can somehow get different rating, grandparent and parent keys with episodes. Not sure how. + var epExists = currentEpisodes.Any(x => episode.ratingKey == x.Key && + episode.grandparentRatingKey == x.GrandparentKey); + if (epExists) + { + continue; + } - await _repo.AddRange(ep); + // Let's check if we have the parent + var seriesExists = await _repo.GetByKey(episode.grandparentRatingKey); + if (seriesExists == null) + { + // Ok let's try and match it to a title. TODO (This is experimental) + seriesExists = await _repo.GetAll().FirstOrDefaultAsync(x => + x.Title.Equals(episode.grandparentTitle, StringComparison.CurrentCultureIgnoreCase)); + if (seriesExists == null) + { + _log.LogWarning( + "The episode title {0} we cannot find the parent series. The episode grandparentKey = {1}, grandparentTitle = {2}", + episode.title, episode.grandparentRatingKey, episode.grandparentTitle); + continue; + } + + // Set the rating key to the correct one + episode.grandparentRatingKey = seriesExists.Key; + } + + ep.Add(new PlexEpisode + { + EpisodeNumber = episode.index, + SeasonNumber = episode.parentIndex, + GrandparentKey = episode.grandparentRatingKey, + ParentKey = episode.parentRatingKey, + Key = episode.ratingKey, + Title = episode.title + }); + } + + await _repo.AddRange(ep); + } + catch (Exception e) + { + Console.WriteLine(e); + throw; + } } private bool Validate(PlexServers settings) { if (string.IsNullOrEmpty(settings.PlexAuthToken)) { - return false ; + return false; } return true; diff --git a/src/Ombi.Schedule/Jobs/Plex/PlexUserImporter.cs b/src/Ombi.Schedule/Jobs/Plex/PlexUserImporter.cs index 22211ea6d..4f6bf2550 100644 --- a/src/Ombi.Schedule/Jobs/Plex/PlexUserImporter.cs +++ b/src/Ombi.Schedule/Jobs/Plex/PlexUserImporter.cs @@ -75,6 +75,12 @@ namespace Ombi.Schedule.Jobs.Plex var existingPlexUser = allUsers.FirstOrDefault(x => x.ProviderUserId == plexUser.Id); if (existingPlexUser == null) { + + if (!plexUser.Username.HasValue()) + { + _log.LogInformation("Could not create Plex user since the have no username, PlexUserId: {0}", plexUser.Id); + continue; + } // Create this users // We do not store a password against the user since they will authenticate via Plex var newUser = new OmbiUser diff --git a/src/Ombi.Settings/Settings/Models/CustomizationSettings.cs b/src/Ombi.Settings/Settings/Models/CustomizationSettings.cs index eef20573a..16d6245f5 100644 --- a/src/Ombi.Settings/Settings/Models/CustomizationSettings.cs +++ b/src/Ombi.Settings/Settings/Models/CustomizationSettings.cs @@ -9,6 +9,7 @@ namespace Ombi.Settings.Settings.Models { public string ApplicationName { get; set; } public string ApplicationUrl { get; set; } + public bool Mobile { get; set; } public string CustomCssLink { get; set; } public bool EnableCustomDonations { get; set; } public string CustomDonationUrl { get; set; } diff --git a/src/Ombi.Settings/Settings/Models/OmbiSettings.cs b/src/Ombi.Settings/Settings/Models/OmbiSettings.cs index ee9d5943b..bf4d29eb4 100644 --- a/src/Ombi.Settings/Settings/Models/OmbiSettings.cs +++ b/src/Ombi.Settings/Settings/Models/OmbiSettings.cs @@ -7,7 +7,8 @@ public bool Wizard { get; set; } public string ApiKey { get; set; } public bool IgnoreCertificateErrors { get; set; } - public bool DoNotSendNotificationsForAutoApprove {get;set;} + public bool DoNotSendNotificationsForAutoApprove { get; set; } + public bool HideRequestsUsers { 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 545d8dd17..b3e82390a 100644 --- a/src/Ombi.Store/Entities/OmbiUser.cs +++ b/src/Ombi.Store/Entities/OmbiUser.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.ComponentModel.DataAnnotations.Schema; using Microsoft.AspNetCore.Identity; +using Newtonsoft.Json; using Ombi.Helpers; namespace Ombi.Store.Entities @@ -32,5 +33,29 @@ namespace Ombi.Store.Entities [NotMapped] public string UserAlias => string.IsNullOrEmpty(Alias) ? UserName : Alias; + + [NotMapped] + public bool EmailLogin { get; set; } + + [JsonIgnore] + public override string PasswordHash + { + get => base.PasswordHash; + set => base.PasswordHash = value; + } + + [JsonIgnore] + public override string SecurityStamp + { + get => base.SecurityStamp; + set => base.SecurityStamp = value; + } + + [JsonIgnore] + public override string ConcurrencyStamp + { + get => base.ConcurrencyStamp; + set => base.ConcurrencyStamp = value; + } } } \ No newline at end of file diff --git a/src/Ombi.Store/Entities/Requests/FullBaseRequest.cs b/src/Ombi.Store/Entities/Requests/FullBaseRequest.cs index 95f771c63..eb6755f98 100644 --- a/src/Ombi.Store/Entities/Requests/FullBaseRequest.cs +++ b/src/Ombi.Store/Entities/Requests/FullBaseRequest.cs @@ -10,11 +10,14 @@ namespace Ombi.Store.Entities.Requests public string Overview { get; set; } public string PosterPath { get; set; } public DateTime ReleaseDate { get; set; } + public DateTime? DigitalReleaseDate { get; set; } public string Status { get; set; } public string Background { get; set; } [NotMapped] public bool Released => DateTime.UtcNow > ReleaseDate; + [NotMapped] + public bool DigitalRelease => DigitalReleaseDate.HasValue && DigitalReleaseDate > DateTime.UtcNow; } } \ 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 13d6a17ae..88e1c36d7 100644 --- a/src/Ombi.Store/Entities/Requests/TvRequests.cs +++ b/src/Ombi.Store/Entities/Requests/TvRequests.cs @@ -8,6 +8,7 @@ namespace Ombi.Store.Entities.Requests { public int TvDbId { get; set; } public string ImdbId { get; set; } + public int? QualityOverride { get; set; } public int? RootFolder { get; set; } public string Overview { get; set; } public string Title { get; set; } diff --git a/src/Ombi.Store/Migration.txt b/src/Ombi.Store/Migration.txt new file mode 100644 index 000000000..3b54d72f5 --- /dev/null +++ b/src/Ombi.Store/Migration.txt @@ -0,0 +1 @@ +dotnet ef migrations add Inital --context OmbiContext --startup-project ../Ombi/Ombi.csproj \ No newline at end of file diff --git a/src/Ombi.Store/Migrations/20180228114507_DigitalRelease.Designer.cs b/src/Ombi.Store/Migrations/20180228114507_DigitalRelease.Designer.cs new file mode 100644 index 000000000..a7ca036f2 --- /dev/null +++ b/src/Ombi.Store/Migrations/20180228114507_DigitalRelease.Designer.cs @@ -0,0 +1,916 @@ +// +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 Ombi.Store.Context; +using Ombi.Store.Entities; +using Ombi.Store.Entities.Requests; +using System; + +namespace Ombi.Store.Migrations +{ + [DbContext(typeof(OmbiContext))] + [Migration("20180228114507_DigitalRelease")] + partial class DigitalRelease + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.0.0-rtm-26452"); + + 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("ProviderId"); + + b.Property("Title"); + + b.Property("Type"); + + 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("ParentId"); + + b.Property("ProviderId"); + + b.Property("SeasonNumber"); + + b.Property("Title"); + + 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.Requests.ChildRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Approved"); + + b.Property("Available"); + + b.Property("Denied"); + + b.Property("DeniedReason"); + + b.Property("IssueId"); + + 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("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("ImdbId"); + + b.Property("Overview"); + + b.Property("PosterPath"); + + 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.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.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/20180228114507_DigitalRelease.cs b/src/Ombi.Store/Migrations/20180228114507_DigitalRelease.cs new file mode 100644 index 000000000..196bfc28a --- /dev/null +++ b/src/Ombi.Store/Migrations/20180228114507_DigitalRelease.cs @@ -0,0 +1,25 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using System; +using System.Collections.Generic; + +namespace Ombi.Store.Migrations +{ + public partial class DigitalRelease : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "DigitalReleaseDate", + table: "MovieRequests", + type: "TEXT", + nullable: true); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "DigitalReleaseDate", + table: "MovieRequests"); + } + } +} diff --git a/src/Ombi.Store/Migrations/20180307131304_SonarrOverrides.Designer.cs b/src/Ombi.Store/Migrations/20180307131304_SonarrOverrides.Designer.cs new file mode 100644 index 000000000..d23aab9ce --- /dev/null +++ b/src/Ombi.Store/Migrations/20180307131304_SonarrOverrides.Designer.cs @@ -0,0 +1,918 @@ +// +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 Ombi.Store.Context; +using Ombi.Store.Entities; +using Ombi.Store.Entities.Requests; +using System; + +namespace Ombi.Store.Migrations +{ + [DbContext(typeof(OmbiContext))] + [Migration("20180307131304_SonarrOverrides")] + partial class SonarrOverrides + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.0.0-rtm-26452"); + + 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("ProviderId"); + + b.Property("Title"); + + b.Property("Type"); + + 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("ParentId"); + + b.Property("ProviderId"); + + b.Property("SeasonNumber"); + + b.Property("Title"); + + 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.Requests.ChildRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Approved"); + + b.Property("Available"); + + b.Property("Denied"); + + b.Property("DeniedReason"); + + b.Property("IssueId"); + + 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("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("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.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.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/20180307131304_SonarrOverrides.cs b/src/Ombi.Store/Migrations/20180307131304_SonarrOverrides.cs new file mode 100644 index 000000000..1028f0314 --- /dev/null +++ b/src/Ombi.Store/Migrations/20180307131304_SonarrOverrides.cs @@ -0,0 +1,25 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using System; +using System.Collections.Generic; + +namespace Ombi.Store.Migrations +{ + public partial class SonarrOverrides : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "QualityOverride", + table: "TvRequests", + type: "INTEGER", + nullable: true); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "QualityOverride", + table: "TvRequests"); + } + } +} diff --git a/src/Ombi.Store/Migrations/OmbiContextModelSnapshot.cs b/src/Ombi.Store/Migrations/OmbiContextModelSnapshot.cs index 6d2501703..35c5755da 100644 --- a/src/Ombi.Store/Migrations/OmbiContextModelSnapshot.cs +++ b/src/Ombi.Store/Migrations/OmbiContextModelSnapshot.cs @@ -553,6 +553,8 @@ namespace Ombi.Store.Migrations b.Property("DeniedReason"); + b.Property("DigitalReleaseDate"); + b.Property("ImdbId"); b.Property("IssueId"); @@ -619,6 +621,8 @@ namespace Ombi.Store.Migrations b.Property("PosterPath"); + b.Property("QualityOverride"); + b.Property("ReleaseDate"); b.Property("RootFolder"); diff --git a/src/Ombi.Store/Repository/IPlexContentRepository.cs b/src/Ombi.Store/Repository/IPlexContentRepository.cs index cb244dcd2..2fef89be2 100644 --- a/src/Ombi.Store/Repository/IPlexContentRepository.cs +++ b/src/Ombi.Store/Repository/IPlexContentRepository.cs @@ -19,5 +19,8 @@ namespace Ombi.Store.Repository Task AddRange(IEnumerable content); IEnumerable GetWhereContentByCustom(Expression> predicate); Task GetFirstContentByCustom(Expression> predicate); + Task DeleteEpisode(PlexEpisode content); + void DeleteWithoutSave(PlexServerContent content); + void DeleteWithoutSave(PlexEpisode content); } } \ No newline at end of file diff --git a/src/Ombi.Store/Repository/PlexContentRepository.cs b/src/Ombi.Store/Repository/PlexContentRepository.cs index 35bf56057..56fec441a 100644 --- a/src/Ombi.Store/Repository/PlexContentRepository.cs +++ b/src/Ombi.Store/Repository/PlexContentRepository.cs @@ -103,12 +103,29 @@ namespace Ombi.Store.Repository return Db.PlexEpisode.Include(x => x.Series).AsQueryable(); } + public void DeleteWithoutSave(PlexServerContent content) + { + Db.PlexServerContent.Remove(content); + } + + public void DeleteWithoutSave(PlexEpisode content) + { + Db.PlexEpisode.Remove(content); + } + public async Task Add(PlexEpisode content) { await Db.PlexEpisode.AddAsync(content); await Db.SaveChangesAsync(); return content; } + + public async Task DeleteEpisode(PlexEpisode content) + { + Db.PlexEpisode.Remove(content); + await Db.SaveChangesAsync(); + } + public async Task GetEpisodeByKey(int key) { return await Db.PlexEpisode.FirstOrDefaultAsync(x => x.Key == key); diff --git a/src/Ombi.Store/Repository/Requests/IMovieRequestRepository.cs b/src/Ombi.Store/Repository/Requests/IMovieRequestRepository.cs index e25b2b168..275937360 100644 --- a/src/Ombi.Store/Repository/Requests/IMovieRequestRepository.cs +++ b/src/Ombi.Store/Repository/Requests/IMovieRequestRepository.cs @@ -11,5 +11,7 @@ namespace Ombi.Store.Repository.Requests Task Update(MovieRequests request); Task Save(); IQueryable GetWithUser(); + IQueryable GetWithUser(string userId); + IQueryable GetAll(string userId); } } \ No newline at end of file diff --git a/src/Ombi.Store/Repository/Requests/ITvRequestRepository.cs b/src/Ombi.Store/Repository/Requests/ITvRequestRepository.cs index 46d2c0bf6..749b67c73 100644 --- a/src/Ombi.Store/Repository/Requests/ITvRequestRepository.cs +++ b/src/Ombi.Store/Repository/Requests/ITvRequestRepository.cs @@ -14,11 +14,13 @@ namespace Ombi.Store.Repository.Requests Task Delete(TvRequests request); Task DeleteChild(ChildRequests request); IQueryable Get(); + IQueryable Get(string userId); Task GetRequestAsync(int tvDbId); TvRequests GetRequest(int tvDbId); Task Update(TvRequests request); Task UpdateChild(ChildRequests request); IQueryable GetChild(); + IQueryable GetChild(string userId); Task Save(); Task DeleteChildRange(IEnumerable request); } diff --git a/src/Ombi.Store/Repository/Requests/MovieRequestRepository.cs b/src/Ombi.Store/Repository/Requests/MovieRequestRepository.cs index 19a89a835..78c3da7dd 100644 --- a/src/Ombi.Store/Repository/Requests/MovieRequestRepository.cs +++ b/src/Ombi.Store/Repository/Requests/MovieRequestRepository.cs @@ -33,6 +33,11 @@ namespace Ombi.Store.Repository.Requests } + public IQueryable GetAll(string userId) + { + return GetWithUser().Where(x => x.RequestedUserId == userId); + } + public MovieRequests GetRequest(int theMovieDbId) { return Db.MovieRequests.Where(x => x.TheMovieDbId == theMovieDbId) @@ -48,6 +53,16 @@ namespace Ombi.Store.Repository.Requests .AsQueryable(); } + + public IQueryable GetWithUser(string userId) + { + return Db.MovieRequests + .Where(x => x.RequestedUserId == userId) + .Include(x => x.RequestedUser) + .ThenInclude(x => x.NotificationUserIds) + .AsQueryable(); + } + public async Task Update(MovieRequests request) { if (Db.Entry(request).State == EntityState.Detached) diff --git a/src/Ombi.Store/Repository/Requests/TvRequestRepository.cs b/src/Ombi.Store/Repository/Requests/TvRequestRepository.cs index 216058d67..28a141908 100644 --- a/src/Ombi.Store/Repository/Requests/TvRequestRepository.cs +++ b/src/Ombi.Store/Repository/Requests/TvRequestRepository.cs @@ -48,6 +48,18 @@ namespace Ombi.Store.Repository.Requests .ThenInclude(x => x.Episodes) .AsQueryable(); } + + public IQueryable Get(string userId) + { + return Db.TvRequests + .Include(x => x.ChildRequests) + .ThenInclude(x => x.RequestedUser) + .Include(x => x.ChildRequests) + .ThenInclude(x => x.SeasonRequests) + .ThenInclude(x => x.Episodes) + .Where(x => x.ChildRequests.Any(a => a.RequestedUserId == userId)) + .AsQueryable(); + } public IQueryable GetChild() { return Db.ChildRequests @@ -58,6 +70,17 @@ namespace Ombi.Store.Repository.Requests .AsQueryable(); } + public IQueryable GetChild(string userId) + { + return Db.ChildRequests + .Where(x => x.RequestedUserId == userId) + .Include(x => x.RequestedUser) + .Include(x => x.ParentRequest) + .Include(x => x.SeasonRequests) + .ThenInclude(x => x.Episodes) + .AsQueryable(); + } + public async Task Save() { await Db.SaveChangesAsync(); diff --git a/src/Ombi.TheMovieDbApi/IMovieDbApi.cs b/src/Ombi.TheMovieDbApi/IMovieDbApi.cs index d39e2e6a7..dd0d0e92c 100644 --- a/src/Ombi.TheMovieDbApi/IMovieDbApi.cs +++ b/src/Ombi.TheMovieDbApi/IMovieDbApi.cs @@ -8,11 +8,12 @@ namespace Ombi.Api.TheMovieDb public interface IMovieDbApi { Task GetMovieInformation(int movieId); - Task GetMovieInformationWithVideo(int movieId); + Task GetMovieInformationWithExtraInfo(int movieId); Task> NowPlaying(); Task> PopularMovies(); Task> SearchMovie(string searchTerm); Task> TopRated(); Task> Upcoming(); + Task> SimilarMovies(int movieId); } } \ No newline at end of file diff --git a/src/Ombi.TheMovieDbApi/Models/MovieResponse.cs b/src/Ombi.TheMovieDbApi/Models/MovieResponse.cs index 17aceca5d..c707f92ed 100644 --- a/src/Ombi.TheMovieDbApi/Models/MovieResponse.cs +++ b/src/Ombi.TheMovieDbApi/Models/MovieResponse.cs @@ -24,6 +24,10 @@ // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // ************************************************************************/ #endregion + +using System; +using System.Collections.Generic; + namespace Ombi.TheMovieDbApi.Models { @@ -54,5 +58,26 @@ namespace Ombi.TheMovieDbApi.Models public bool video { get; set; } public float vote_average { get; set; } public int vote_count { get; set; } + public ReleaseDates release_dates { get; set; } + } + + public class ReleaseDates + { + public List results { get; set; } + } + + public class ReleaseResults + { + public string iso_3166_1 { get; set; } + public List release_dates { get; set; } + } + + public class ReleaseDate + { + public string Certification { get; set; } + public string iso_639_1 { get; set; } + public string note { get; set; } + public DateTime release_date { get; set; } + public int Type { get; set; } } } \ No newline at end of file diff --git a/src/Ombi.TheMovieDbApi/Models/MovieResponseDTO.cs b/src/Ombi.TheMovieDbApi/Models/MovieResponseDTO.cs index 0fd27a23f..4df8083ba 100644 --- a/src/Ombi.TheMovieDbApi/Models/MovieResponseDTO.cs +++ b/src/Ombi.TheMovieDbApi/Models/MovieResponseDTO.cs @@ -1,4 +1,7 @@ -namespace Ombi.Api.TheMovieDb.Models +using System; +using System.Collections.Generic; + +namespace Ombi.Api.TheMovieDb.Models { public class MovieResponseDto { @@ -23,5 +26,33 @@ public bool Video { get; set; } public float VoteAverage { get; set; } public int VoteCount { get; set; } + public ReleaseDatesDto ReleaseDates { get; set; } + } + + public class ReleaseDatesDto + { + public List Results { get; set; } + } + + public class ReleaseResultsDto + { + public string IsoCode { get; set; } + public List ReleaseDate { get; set; } + } + + public class ReleaseDateDto + { + public DateTime ReleaseDate { get; set; } + public ReleaseDateType Type { get; set; } + } + + public enum ReleaseDateType + { + Premiere = 1, + TheatricalLimited = 2, + Theatrical = 3, + Digital = 4, + Physical = 5, + Tv = 6 } } \ No newline at end of file diff --git a/src/Ombi.TheMovieDbApi/TheMovieDbApi.cs b/src/Ombi.TheMovieDbApi/TheMovieDbApi.cs index df3de6d4e..1fbfe9aaf 100644 --- a/src/Ombi.TheMovieDbApi/TheMovieDbApi.cs +++ b/src/Ombi.TheMovieDbApi/TheMovieDbApi.cs @@ -30,11 +30,20 @@ namespace Ombi.Api.TheMovieDb return Mapper.Map(result); } - public async Task GetMovieInformationWithVideo(int movieId) + public async Task> SimilarMovies(int movieId) + { + var request = new Request($"movie/{movieId}/similar", BaseUri, HttpMethod.Get); + request.FullUri = request.FullUri.AddQueryParameter("api_key", ApiToken); + + var result = await Api.Request>(request); + return Mapper.Map>(result.results); + } + + public async Task GetMovieInformationWithExtraInfo(int movieId) { 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"); + request.FullUri = request.FullUri.AddQueryParameter("append_to_response", "videos,release_dates"); var result = await Api.Request(request); return Mapper.Map(result); } diff --git a/src/Ombi.Updater/Installer.cs b/src/Ombi.Updater/Installer.cs index 5a84ecde7..4827d45f9 100644 --- a/src/Ombi.Updater/Installer.cs +++ b/src/Ombi.Updater/Installer.cs @@ -3,6 +3,7 @@ using System.Diagnostics; using System.IO; using System.Linq; using System.Runtime.InteropServices; +using System.Text; using System.Threading; using Microsoft.Extensions.Logging; @@ -33,27 +34,27 @@ namespace Ombi.Updater } // Make sure the process has been killed - while (p.FindProcessByName(opt.ProcessName).Any()) + while (p.FindProcessByName(opt.ProcessName).Any()) + { + Thread.Sleep(500); + _log.LogDebug("Found another process called {0}, KILLING!", opt.ProcessName); + var proc = p.FindProcessByName(opt.ProcessName).FirstOrDefault(); + if (proc != null) { - 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($"[{proc.Id}] - {proc.Name} - Path: {proc.StartPath}"); + opt.OmbiProcessId = proc.Id; + p.Kill(opt); } - - _log.LogDebug("Starting to move the files"); - MoveFiles(opt); - _log.LogDebug("Files replaced"); - // Start Ombi - StartOmbi(opt); } - private void StartOmbi(StartupOptions options) + _log.LogDebug("Starting to move the files"); + MoveFiles(opt); + _log.LogDebug("Files replaced"); + // Start Ombi + StartOmbi(opt); + } + + private void StartOmbi(StartupOptions options) { _log.LogDebug("Starting ombi"); var fileName = "Ombi.exe"; @@ -71,19 +72,29 @@ namespace Ombi.Updater Arguments = $"/C net start \"{options.WindowsServiceName}\"" }; - using (var process = new Process{StartInfo = startInfo}) + using (var process = new Process { StartInfo = startInfo }) { process.Start(); } } else { + var startupArgsBuilder = new StringBuilder(); + if (!string.IsNullOrEmpty(options.Host)) + { + startupArgsBuilder.Append($"--host {options.Host} "); + } + if (!string.IsNullOrEmpty(options.Storage)) + { + startupArgsBuilder.Append($"--storage {options.Storage}"); + } + var start = new ProcessStartInfo { UseShellExecute = false, FileName = Path.Combine(options.ApplicationPath, fileName), WorkingDirectory = options.ApplicationPath, - Arguments = options.StartupArgs + Arguments = startupArgsBuilder.ToString() }; using (var proc = new Process { StartInfo = start }) { diff --git a/src/Ombi.Updater/Ombi.Updater.csproj b/src/Ombi.Updater/Ombi.Updater.csproj index af866b8fe..9f5612367 100644 --- a/src/Ombi.Updater/Ombi.Updater.csproj +++ b/src/Ombi.Updater/Ombi.Updater.csproj @@ -2,7 +2,7 @@ Exe - win10-x64;win10-x32;osx-x64;ubuntu-x64;debian.8-x64;centos.7-x64;linux-x64; + win10-x64;win10-x86;osx-x64;ubuntu-x64;debian.8-x64;centos.7-x64;linux-x64;linux-arm;linux-arm64; netcoreapp2.0 3.0.0.0 3.0.0.0 diff --git a/src/Ombi.Updater/Program.cs b/src/Ombi.Updater/Program.cs index 26a1d180f..612d9d3fb 100644 --- a/src/Ombi.Updater/Program.cs +++ b/src/Ombi.Updater/Program.cs @@ -1,14 +1,10 @@ using System; -using System.Diagnostics; using System.IO; -using System.Linq; using CommandLine; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Serilog; -using Serilog.Events; -using ILogger = Serilog.ILogger; namespace Ombi.Updater { @@ -78,8 +74,10 @@ namespace Ombi.Updater public string ApplicationPath { get; set; } [Option("processId", Required = false)] public int OmbiProcessId { get; set; } - [Option("startupArgs", Required = false)] - public string StartupArgs { get; set; } + [Option("host", Required = false)] + public string Host { get; set; } + [Option("storage", Required = false)] + public string Storage { get; set; } [Option("windowsServiceName", Required = false)] public string WindowsServiceName { get; set; } diff --git a/src/Ombi/ClientApp/app/app.component.html b/src/Ombi/ClientApp/app/app.component.html index 16efe8a4c..a540882e8 100644 --- a/src/Ombi/ClientApp/app/app.component.html +++ b/src/Ombi/ClientApp/app/app.component.html @@ -92,7 +92,7 @@ {{ 'NavigationBar.UpdateDetails' | translate }} -
  • +
  • {{ 'NavigationBar.OpenMobileApp' | translate }}
  • diff --git a/src/Ombi/ClientApp/app/app.component.ts b/src/Ombi/ClientApp/app/app.component.ts index fff816537..a1841109a 100644 --- a/src/Ombi/ClientApp/app/app.component.ts +++ b/src/Ombi/ClientApp/app/app.component.ts @@ -1,4 +1,5 @@ -import { Component, OnInit } from "@angular/core"; +import { PlatformLocation } from "@angular/common"; +import { Component, OnInit } from "@angular/core"; import { NavigationStart, Router } from "@angular/router"; import { TranslateService } from "@ngx-translate/core"; import { AuthService } from "./auth/auth.service"; @@ -22,7 +23,6 @@ export class AppComponent implements OnInit { public updateAvailable: boolean; public currentUrl: string; public userAccessToken: string; - public showMobileLink = false; private checkedForUpdate: boolean; @@ -32,7 +32,14 @@ export class AppComponent implements OnInit { private readonly settingsService: SettingsService, private readonly jobService: JobService, public readonly translate: TranslateService, - private readonly identityService: IdentityService) { + private readonly identityService: IdentityService, + private readonly platformLocation: PlatformLocation) { + + const base = this.platformLocation.getBaseHrefFromDOM(); + if (base.length > 1) { + __webpack_public_path__ = base + "/dist/"; + } + this.translate.addLangs(["en", "de", "fr","da","es","it","nl","sv","no"]); // this language will be used as a fallback when a translation isn't found in the current language this.translate.setDefaultLang("en"); diff --git a/src/Ombi/ClientApp/app/interfaces/IRequestModel.ts b/src/Ombi/ClientApp/app/interfaces/IRequestModel.ts index 9f822540c..31a98e8e2 100644 --- a/src/Ombi/ClientApp/app/interfaces/IRequestModel.ts +++ b/src/Ombi/ClientApp/app/interfaces/IRequestModel.ts @@ -1,28 +1,5 @@ import { IUser } from "./IUser"; -export interface IMediaBase { - imdbId: string; - id: number; - providerId: number; - title: string; - overview: string; - posterPath: string; - releaseDate: Date; - status: string; - requestedDate: Date; - approved: boolean; - type: RequestType; - requested: boolean; - available: boolean; - otherMessage: string; - adminNote: string; - requestedUser: string; - issueId: number; - denied: boolean; - deniedReason: string; - released: boolean; -} - export enum RequestType { movie = 1, tvShow = 2, @@ -34,7 +11,9 @@ export interface IMovieRequests extends IFullBaseRequest { theMovieDbId: number; rootPathOverride: number; qualityOverride: number; + digitalReleaseDate: Date; + // For the UI rootPathOverrideTitle: string; qualityOverrideTitle: string; } @@ -84,6 +63,11 @@ export interface ITvRequests { releaseDate: Date; status: string; childRequests: IChildRequests[]; + qualityOverride: number; + + // For UI display + qualityOverrideTitle: string; + rootPathOverrideTitle: string; } export interface IChildRequests extends IBaseRequest { diff --git a/src/Ombi/ClientApp/app/interfaces/ISearchMovieResult.ts b/src/Ombi/ClientApp/app/interfaces/ISearchMovieResult.ts index 77ec87ed5..c5301d2b0 100644 --- a/src/Ombi/ClientApp/app/interfaces/ISearchMovieResult.ts +++ b/src/Ombi/ClientApp/app/interfaces/ISearchMovieResult.ts @@ -22,6 +22,7 @@ available: boolean; plexUrl: string; quality: string; + digitalReleaseDate: Date; // for the UI requestProcessing: boolean; diff --git a/src/Ombi/ClientApp/app/interfaces/ISettings.ts b/src/Ombi/ClientApp/app/interfaces/ISettings.ts index c7bb3ac37..1ba2c4843 100644 --- a/src/Ombi/ClientApp/app/interfaces/ISettings.ts +++ b/src/Ombi/ClientApp/app/interfaces/ISettings.ts @@ -14,6 +14,7 @@ export interface IOmbiSettings extends ISettings { apiKey: string; ignoreCertificateErrors: boolean; doNotSendNotificationsForAutoApprove: boolean; + hideRequestsUsers: boolean; } export interface IUpdateSettings extends ISettings { @@ -96,6 +97,7 @@ export interface ICustomizationSettings extends ISettings { applicationName: string; applicationUrl: string; logo: string; + mobile: boolean; customCssLink: string; enableCustomDonations: boolean; customDonationUrl: string; diff --git a/src/Ombi/ClientApp/app/interfaces/IUser.ts b/src/Ombi/ClientApp/app/interfaces/IUser.ts index 454bd8457..69bc49d0f 100644 --- a/src/Ombi/ClientApp/app/interfaces/IUser.ts +++ b/src/Ombi/ClientApp/app/interfaces/IUser.ts @@ -49,3 +49,14 @@ export interface IMobileUsersViewModel { username: string; devices: number; } + +export interface IMassEmailUserModel { + user: IUser; + selected: boolean; +} + +export interface IMassEmailModel { + subject: string; + body: string; + users: IUser[]; +} diff --git a/src/Ombi/ClientApp/app/issues/issuestable.component.html b/src/Ombi/ClientApp/app/issues/issuestable.component.html index f1b686c19..f98d6eb0e 100644 --- a/src/Ombi/ClientApp/app/issues/issuestable.component.html +++ b/src/Ombi/ClientApp/app/issues/issuestable.component.html @@ -13,15 +13,15 @@ - + - + - + - + diff --git a/src/Ombi/ClientApp/app/landingpage/landingpage.component.scss b/src/Ombi/ClientApp/app/landingpage/landingpage.component.scss index b444ef92f..8cb255d73 100644 --- a/src/Ombi/ClientApp/app/landingpage/landingpage.component.scss +++ b/src/Ombi/ClientApp/app/landingpage/landingpage.component.scss @@ -1,4 +1,13 @@ -div.centered { +@media only screen and (max-width: 992px) { + div.centered { + max-height: 100%; + overflow-y: auto; + width: 100%; + padding: 50% 12.5%; + } +} + +div.centered { position: fixed; top: 50%; left: 50%; diff --git a/src/Ombi/ClientApp/app/request-grid/request-card.component.ts b/src/Ombi/ClientApp/app/request-grid/request-card.component.ts index d430e3aa0..aa267ecdb 100644 --- a/src/Ombi/ClientApp/app/request-grid/request-card.component.ts +++ b/src/Ombi/ClientApp/app/request-grid/request-card.component.ts @@ -1,11 +1,11 @@ -import { Component, Input } from "@angular/core"; +// import { Component, Input } from "@angular/core"; -import { IMediaBase } from "../interfaces"; +// import { IMediaBase } from "../interfaces"; -@Component({ - selector: "request-card", - templateUrl: "./request-card.component.html", -}) -export class RequestCardComponent { - @Input() public request: IMediaBase; -} +// @Component({ +// selector: "request-card", +// templateUrl: "./request-card.component.html", +// }) +// export class RequestCardComponent { +// @Input() public request: IMediaBase; +// } diff --git a/src/Ombi/ClientApp/app/requests/movierequests.component.html b/src/Ombi/ClientApp/app/requests/movierequests.component.html index 7b5f0b56b..a4bdf0aed 100644 --- a/src/Ombi/ClientApp/app/requests/movierequests.component.html +++ b/src/Ombi/ClientApp/app/requests/movierequests.component.html @@ -3,8 +3,50 @@ @@ -17,7 +59,7 @@
    -
    +
    @@ -66,7 +108,8 @@
    -
    {{ 'Requests.ReleaseDate' | translate }} {{request.releaseDate | date}}
    +
    {{ 'Requests.TheatricalRelease' | translate: {date: request.releaseDate | date: 'mediumDate'} }}
    +
    {{ 'Requests.DigitalRelease' | translate: {date: request.digitalReleaseDate | date: 'mediumDate'} }}
    {{ 'Requests.RequestDate' | translate }} {{request.requestedDate | date}}

    @@ -168,7 +211,7 @@ -

    {{ 'Filter.Filter' | translate }}

    +

    {{ 'Requests.Filter' | translate }}


    {{ 'Filter.FilterHeaderAvailability' | translate }}

    @@ -198,7 +241,14 @@
    +
    +
    + + +
    +
    + + {{ 'Filter.ClearFilter' | translate }} \ No newline at end of file diff --git a/src/Ombi/ClientApp/app/requests/movierequests.component.ts b/src/Ombi/ClientApp/app/requests/movierequests.component.ts index cce804818..fc695c898 100644 --- a/src/Ombi/ClientApp/app/requests/movierequests.component.ts +++ b/src/Ombi/ClientApp/app/requests/movierequests.component.ts @@ -36,6 +36,9 @@ export class MovieRequestsComponent implements OnInit { public filter: IFilter; public filterType = FilterType; + public order: string = "requestedDate"; + public reverse = false; + private currentlyLoaded: number; private amountToLoad: number; @@ -172,6 +175,14 @@ export class MovieRequestsComponent implements OnInit { }); } + public setOrder(value: string) { + if (this.order === value) { + this.reverse = !this.reverse; + } + + this.order = value; + } + private loadRequests(amountToLoad: number, currentlyLoaded: number) { this.requestService.getMovieRequests(amountToLoad, currentlyLoaded + 1) .subscribe(x => { diff --git a/src/Ombi/ClientApp/app/requests/requests.module.ts b/src/Ombi/ClientApp/app/requests/requests.module.ts index 6eccd82ac..7bddee71c 100644 --- a/src/Ombi/ClientApp/app/requests/requests.module.ts +++ b/src/Ombi/ClientApp/app/requests/requests.module.ts @@ -2,6 +2,7 @@ import { RouterModule, Routes } from "@angular/router"; import { NgbModule } from "@ng-bootstrap/ng-bootstrap"; +import { OrderModule } from "ngx-order-pipe"; import { InfiniteScrollModule } from "ngx-infinite-scroll"; @@ -14,7 +15,7 @@ import { TvRequestsComponent } from "./tvrequests.component"; import { SidebarModule, TreeTableModule } from "primeng/primeng"; -import { IdentityService, RadarrService, RequestService } from "../services"; +import { IdentityService, RadarrService, RequestService, SonarrService } from "../services"; import { AuthGuard } from "../auth/auth.guard"; @@ -34,6 +35,7 @@ const routes: Routes = [ TreeTableModule, SharedModule, SidebarModule, + OrderModule, ], declarations: [ RequestComponent, @@ -48,7 +50,8 @@ const routes: Routes = [ IdentityService, RequestService, RadarrService, - ], + SonarrService, + ], }) export class RequestsModule { } diff --git a/src/Ombi/ClientApp/app/requests/tvrequests.component.html b/src/Ombi/ClientApp/app/requests/tvrequests.component.html index ab5a9d94e..a602d27e1 100644 --- a/src/Ombi/ClientApp/app/requests/tvrequests.component.html +++ b/src/Ombi/ClientApp/app/requests/tvrequests.component.html @@ -51,17 +51,61 @@
    Release Date: {{node.data.releaseDate | date}}
    +
    +
    {{ 'Requests.QualityOverride' | translate }} + {{node.data.qualityOverrideTitle}} +
    +
    {{ 'Requests.RootFolderOverride' | translate }} + {{node.data.rootPathOverrideTitle}} +
    +
    +
    +
    + +
    + + + +
    + + +
    + + + +
    + +
    - +
    diff --git a/src/Ombi/ClientApp/app/requests/tvrequests.component.ts b/src/Ombi/ClientApp/app/requests/tvrequests.component.ts index 411db1200..7a1e37595 100644 --- a/src/Ombi/ClientApp/app/requests/tvrequests.component.ts +++ b/src/Ombi/ClientApp/app/requests/tvrequests.component.ts @@ -11,10 +11,10 @@ import "rxjs/add/operator/distinctUntilChanged"; import "rxjs/add/operator/map"; import { AuthService } from "../auth/auth.service"; -import { RequestService } from "../services"; +import { NotificationService, RequestService, SonarrService } from "../services"; import { TreeNode } from "primeng/primeng"; -import { IIssueCategory, ITvRequests } from "../interfaces"; +import { IIssueCategory, ISonarrProfile, ISonarrRootFolder, ITvRequests } from "../interfaces"; @Component({ selector: "tv-requests", @@ -34,13 +34,18 @@ export class TvRequestsComponent implements OnInit { @Input() public issuesEnabled: boolean; public issueProviderId: string; + public sonarrProfiles: ISonarrProfile[] = []; + public sonarrRootFolders: ISonarrRootFolder[] = []; + private currentlyLoaded: number; private amountToLoad: number; constructor(private requestService: RequestService, private auth: AuthService, private sanitizer: DomSanitizer, - private imageService: ImageService) { + private imageService: ImageService, + private sonarrService: SonarrService, + private notificationService: NotificationService) { this.searchChanged .debounceTime(600) // Wait Xms after the last event before emitting last event .distinctUntilChanged() // only emit if value is different from previous value @@ -54,9 +59,11 @@ export class TvRequestsComponent implements OnInit { .subscribe(m => { this.tvRequests = m; this.tvRequests.forEach((val) => this.loadBackdrop(val)); + this.tvRequests.forEach((val) => this.setOverride(val.data)); }); }); } + public openClosestTab(el: any) { const rowclass = "undefined ng-star-inserted"; el = el.toElement || el.relatedTarget || el.target; @@ -83,11 +90,18 @@ export class TvRequestsComponent implements OnInit { } public ngOnInit() { + + const profile = {name:"test",id:1 }; + const folder = {path:"testpath", id:1}; + + this.sonarrProfiles.push(profile); + this.sonarrRootFolders.push(folder); this.amountToLoad = 1000; this.currentlyLoaded = 1000; this.tvRequests = []; - this.loadInit(); this.isAdmin = this.auth.hasRole("admin") || this.auth.hasRole("poweruser"); + + this.loadInit(); } public loadMore() { @@ -117,14 +131,72 @@ export class TvRequestsComponent implements OnInit { this.ngOnInit(); } + public selectRootFolder(searchResult: ITvRequests, rootFolderSelected: ISonarrRootFolder, event: any) { + event.preventDefault(); + searchResult.rootFolder = rootFolderSelected.id; + this.setOverride(searchResult); + this.updateRequest(searchResult); + } + + public selectQualityProfile(searchResult: ITvRequests, profileSelected: ISonarrProfile, event: any) { + event.preventDefault(); + searchResult.qualityOverride = profileSelected.id; + this.setOverride(searchResult); + this.updateRequest(searchResult); + } + + private setOverride(req: ITvRequests): void { + this.setQualityOverrides(req); + this.setRootFolderOverrides(req); + } + + private updateRequest(request: ITvRequests) { + this.requestService.updateTvRequest(request) + .subscribe(x => { + this.notificationService.success("Request Updated"); + this.setOverride(x); + request = x; + }); + } + + private setQualityOverrides(req: ITvRequests): void { + if (this.sonarrProfiles) { + const profile = this.sonarrProfiles.filter((p) => { + return p.id === req.qualityOverride; + }); + if (profile.length > 0) { + req.qualityOverrideTitle = profile[0].name; + } + } + } + private setRootFolderOverrides(req: ITvRequests): void { + if (this.sonarrRootFolders) { + const path = this.sonarrRootFolders.filter((folder) => { + return folder.id === req.rootFolder; + }); + if (path.length > 0) { + req.rootPathOverrideTitle = path[0].path; + } + } + } + private loadInit() { this.requestService.getTvRequestsTree(this.amountToLoad, 0) .subscribe(x => { this.tvRequests = x; this.tvRequests.forEach((val, index) => { 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); + } } private resetSearch() { diff --git a/src/Ombi/ClientApp/app/search/moviesearch.component.html b/src/Ombi/ClientApp/app/search/moviesearch.component.html index d43622c44..58d8415fb 100644 --- a/src/Ombi/ClientApp/app/search/moviesearch.component.html +++ b/src/Ombi/ClientApp/app/search/moviesearch.component.html @@ -41,8 +41,9 @@

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

    - - {{ 'Search.ReleaseDate' | translate }} {{result.releaseDate | date: 'dd/MM/yyyy'}} + + {{ 'Search.TheatricalRelease' | translate: {date: result.releaseDate | date: 'mediumDate'} }} + {{ 'Search.DigitalDate' | translate: {date: result.digitalReleaseDate | date: 'mediumDate'} }} @@ -79,7 +80,8 @@ {{ 'Common.Request' | translate }} - + +
    View On Plex diff --git a/src/Ombi/ClientApp/app/search/moviesearch.component.ts b/src/Ombi/ClientApp/app/search/moviesearch.component.ts index 6be32becc..819463c4f 100644 --- a/src/Ombi/ClientApp/app/search/moviesearch.component.ts +++ b/src/Ombi/ClientApp/app/search/moviesearch.component.ts @@ -146,6 +146,15 @@ export class MovieSearchComponent implements OnInit { this.issueProviderId = req.id.toString(); } + public similarMovies(theMovieDbId: number) { + this.clearResults(); + this.searchService.similarMovies(theMovieDbId) + .subscribe(x => { + this.movieResults = x; + this.getExtraInfo(); + }); + } + private getExtraInfo() { this.movieResults.forEach((val, index) => { diff --git a/src/Ombi/ClientApp/app/search/tvsearch.component.html b/src/Ombi/ClientApp/app/search/tvsearch.component.html index 9632f7cbe..fb1729e50 100644 --- a/src/Ombi/ClientApp/app/search/tvsearch.component.html +++ b/src/Ombi/ClientApp/app/search/tvsearch.component.html @@ -62,7 +62,7 @@
    - +

    {{node.data.title}} ({{node.data.firstAired | date: 'yyyy'}})

    diff --git a/src/Ombi/ClientApp/app/services/applications/sonarr.service.ts b/src/Ombi/ClientApp/app/services/applications/sonarr.service.ts index 20b67fd9e..b6328ad83 100644 --- a/src/Ombi/ClientApp/app/services/applications/sonarr.service.ts +++ b/src/Ombi/ClientApp/app/services/applications/sonarr.service.ts @@ -20,4 +20,11 @@ export class SonarrService extends ServiceHelpers { public getQualityProfiles(settings: ISonarrSettings): Observable { return this.http.post(`${this.url}/Profiles/`, JSON.stringify(settings), {headers: this.headers}); } + + public getRootFoldersWithoutSettings(): Observable { + return this.http.get(`${this.url}/RootFolders/`, {headers: this.headers}); + } + public getQualityProfilesWithoutSettings(): Observable { + return this.http.get(`${this.url}/Profiles/`, {headers: this.headers}); + } } diff --git a/src/Ombi/ClientApp/app/services/index.ts b/src/Ombi/ClientApp/app/services/index.ts index bc4af577e..e69de29bb 100644 --- a/src/Ombi/ClientApp/app/services/index.ts +++ b/src/Ombi/ClientApp/app/services/index.ts @@ -1,15 +0,0 @@ -export * from "./applications"; -export * from "./helpers"; -export * from "./identity.service"; -export * from "./image.service"; -export * from "./landingpage.service"; -export * from "./notification.service"; -export * from "./request.service"; -export * from "./search.service"; -export * from "./service.helpers"; -export * from "./settings.service"; -export * from "./status.service"; -export * from "./job.service"; -export * from "./issues.service"; -export * from "./mobile.service"; -export * from "./recentlyAdded.service"; diff --git a/src/Ombi/ClientApp/app/services/notificationMessage.service.ts b/src/Ombi/ClientApp/app/services/notificationMessage.service.ts new file mode 100644 index 000000000..239bcf17c --- /dev/null +++ b/src/Ombi/ClientApp/app/services/notificationMessage.service.ts @@ -0,0 +1,19 @@ +import { PlatformLocation } from "@angular/common"; +import { Injectable } from "@angular/core"; + +import { HttpClient } from "@angular/common/http"; +import { Observable } from "rxjs/Rx"; + +import { IMassEmailModel } from "./../interfaces"; + +import { ServiceHelpers } from "./service.helpers"; + +@Injectable() +export class NotificationMessageService extends ServiceHelpers { + constructor(http: HttpClient, public platformLocation: PlatformLocation) { + super(http, "/api/v1/notifications/", platformLocation); + } + public sendMassEmail(model: IMassEmailModel): Observable { + return this.http.post(`${this.url}massemail/`, JSON.stringify(model) ,{headers: this.headers}); + } +} diff --git a/src/Ombi/ClientApp/app/services/search.service.ts b/src/Ombi/ClientApp/app/services/search.service.ts index 859f01240..522b7dddf 100644 --- a/src/Ombi/ClientApp/app/services/search.service.ts +++ b/src/Ombi/ClientApp/app/services/search.service.ts @@ -19,6 +19,9 @@ export class SearchService extends ServiceHelpers { public searchMovie(searchTerm: string): Observable { return this.http.get(`${this.url}/Movie/` + searchTerm); } + public similarMovies(theMovieDbId: number): Observable { + return this.http.get(`${this.url}/Movie/${theMovieDbId}/similar`); + } public popularMovies(): Observable { return this.http.get(`${this.url}/Movie/Popular`); @@ -54,15 +57,15 @@ export class SearchService extends ServiceHelpers { } public popularTv(): Observable { - return this.http.get(`${this.url}/Tv/popular`, {headers: this.headers}); + return this.http.get(`${this.url}/Tv/popular/tree`, {headers: this.headers}); } public mostWatchedTv(): Observable { - return this.http.get(`${this.url}/Tv/mostwatched`, {headers: this.headers}); + return this.http.get(`${this.url}/Tv/mostwatched/tree`, {headers: this.headers}); } public anticipatedTv(): Observable { - return this.http.get(`${this.url}/Tv/anticipated`, {headers: this.headers}); + return this.http.get(`${this.url}/Tv/anticipated/tree`, {headers: this.headers}); } public trendingTv(): Observable { - return this.http.get(`${this.url}/Tv/trending`, {headers: this.headers}); + return this.http.get(`${this.url}/Tv/trending/tree`, {headers: this.headers}); } } diff --git a/src/Ombi/ClientApp/app/settings/customization/customization.component.html b/src/Ombi/ClientApp/app/settings/customization/customization.component.html index c6a1b713f..2808b359d 100644 --- a/src/Ombi/ClientApp/app/settings/customization/customization.component.html +++ b/src/Ombi/ClientApp/app/settings/customization/customization.component.html @@ -3,6 +3,12 @@
    Customization +
    +
    + Advanced + +
    +
    @@ -20,6 +26,13 @@
    +
    +
    + + +
    +
    +
    diff --git a/src/Ombi/ClientApp/app/settings/customization/customization.component.ts b/src/Ombi/ClientApp/app/settings/customization/customization.component.ts index 3de71b7c5..c4cb32675 100644 --- a/src/Ombi/ClientApp/app/settings/customization/customization.component.ts +++ b/src/Ombi/ClientApp/app/settings/customization/customization.component.ts @@ -11,6 +11,7 @@ export class CustomizationComponent implements OnInit { public settings: ICustomizationSettings; public themes: IThemes[]; + public advanced: boolean; constructor(private settingsService: SettingsService, private notificationService: NotificationService) { } diff --git a/src/Ombi/ClientApp/app/settings/massemail/massemail.component.html b/src/Ombi/ClientApp/app/settings/massemail/massemail.component.html new file mode 100644 index 000000000..c0191b322 --- /dev/null +++ b/src/Ombi/ClientApp/app/settings/massemail/massemail.component.html @@ -0,0 +1,50 @@ + + + +
    + Mass Email + +
    +
    + + Hey! We need a subject! +
    + +
    + +
    + +
    + +
    + May appear differently on email clients +
    +
    +
    +
    +
    + +
    +
    +
    +
    + + +
    +
    + + +
    +
    + +
    +
    + + +
    +
    +
    + + + +
    \ No newline at end of file diff --git a/src/Ombi/ClientApp/app/settings/massemail/massemail.component.ts b/src/Ombi/ClientApp/app/settings/massemail/massemail.component.ts new file mode 100644 index 000000000..a80f7adfe --- /dev/null +++ b/src/Ombi/ClientApp/app/settings/massemail/massemail.component.ts @@ -0,0 +1,75 @@ +import { Component, OnInit } from "@angular/core"; + +import { IMassEmailModel, IMassEmailUserModel } from "../../interfaces"; +import { IdentityService, NotificationMessageService, NotificationService, SettingsService } from "../../services"; + +@Component({ + templateUrl: "./massemail.component.html", +}) +export class MassEmailComponent implements OnInit { + + public users: IMassEmailUserModel[] = []; + public message: string; + public subject: string; + + public missingSubject = false; + + public emailEnabled: boolean; + + constructor(private readonly notification: NotificationService, + private readonly identityService: IdentityService, + private readonly notificationMessageService: NotificationMessageService, + private readonly settingsService: SettingsService) { + } + + public ngOnInit(): void { + this.identityService.getUsers().subscribe(x => { + x.forEach(u => { + this.users.push({ + user: u, + selected: false, + }); + }); + }); + this.settingsService.getEmailSettingsEnabled().subscribe(x => this.emailEnabled = x); + } + + public selectAllUsers() { + this.users.forEach(u => u.selected = !u.selected); + } + + public selectSingleUser(user: IMassEmailUserModel) { + user.selected = !user.selected; + } + + public send() { + if(!this.subject) { + this.missingSubject = true; + return; + } + if(!this.emailEnabled) { + this.notification.error("You have not yet setup your email notifications, do that first!"); + return; + } + this.missingSubject = false; + // Where(x => x.selected).Select(x => x.user) + const selectedUsers = this.users.filter(u => { + return u.selected; + }).map(u => u.user); + + if(selectedUsers.length <=0) { + this.notification.error("You need to select at least one user to send the email"); + return; + } + + const model = { + users: selectedUsers, + subject: this.subject, + body: this.message, + }; + this.notification.info("Sending","Sending mass email... Please wait"); + this.notificationMessageService.sendMassEmail(model).subscribe(x => { + this.notification.success("We have sent the mass email to the users selected!"); + }); + } +} diff --git a/src/Ombi/ClientApp/app/settings/ombi/ombi.component.html b/src/Ombi/ClientApp/app/settings/ombi/ombi.component.html index 27ac016c6..3be162a9d 100644 --- a/src/Ombi/ClientApp/app/settings/ombi/ombi.component.html +++ b/src/Ombi/ClientApp/app/settings/ombi/ombi.component.html @@ -53,6 +53,14 @@
    +
    +
    + + +
    +
    + +
    diff --git a/src/Ombi/ClientApp/app/settings/ombi/ombi.component.ts b/src/Ombi/ClientApp/app/settings/ombi/ombi.component.ts index 1adfb97e2..d1832d7de 100644 --- a/src/Ombi/ClientApp/app/settings/ombi/ombi.component.ts +++ b/src/Ombi/ClientApp/app/settings/ombi/ombi.component.ts @@ -24,6 +24,7 @@ export class OmbiComponent implements OnInit { ignoreCertificateErrors: [x.ignoreCertificateErrors], baseUrl: [x.baseUrl], doNotSendNotificationsForAutoApprove: [x.doNotSendNotificationsForAutoApprove], + hideRequestsUsers: [x.hideRequestsUsers], }); }); } @@ -41,7 +42,7 @@ export class OmbiComponent implements OnInit { } const result = form.value; - if(result.baseUrl.length > 0) { + 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/settings.module.ts b/src/Ombi/ClientApp/app/settings/settings.module.ts index 4a3da70e8..f10df8448 100644 --- a/src/Ombi/ClientApp/app/settings/settings.module.ts +++ b/src/Ombi/ClientApp/app/settings/settings.module.ts @@ -7,7 +7,8 @@ import { ClipboardModule } from "ngx-clipboard/dist"; import { AuthGuard } from "../auth/auth.guard"; import { AuthService } from "../auth/auth.service"; -import { CouchPotatoService, EmbyService, IssuesService, JobService, MobileService, PlexService, RadarrService, SonarrService, TesterService, ValidationService } from "../services"; +import { CouchPotatoService, EmbyService, IssuesService, JobService, MobileService, NotificationMessageService, PlexService, RadarrService, + SonarrService, TesterService, ValidationService } from "../services"; import { PipeModule } from "../pipes/pipe.module"; import { AboutComponent } from "./about/about.component"; @@ -19,6 +20,7 @@ import { EmbyComponent } from "./emby/emby.component"; import { IssuesComponent } from "./issues/issues.component"; import { JobsComponent } from "./jobs/jobs.component"; import { LandingPageComponent } from "./landingpage/landingpage.component"; +import { MassEmailComponent } from "./massemail/massemail.component"; import { DiscordComponent } from "./notifications/discord.component"; import { EmailNotificationComponent } from "./notifications/emailnotification.component"; import { MattermostComponent } from "./notifications/mattermost.component"; @@ -66,6 +68,7 @@ const routes: Routes = [ { path: "Issues", component: IssuesComponent, canActivate: [AuthGuard] }, { path: "Authentication", component: AuthenticationComponent, canActivate: [AuthGuard] }, { path: "Mobile", component: MobileComponent, canActivate: [AuthGuard] }, + { path: "MassEmail", component: MassEmailComponent, canActivate: [AuthGuard] }, ]; @NgModule({ @@ -114,6 +117,7 @@ const routes: Routes = [ IssuesComponent, AuthenticationComponent, MobileComponent, + MassEmailComponent, ], exports: [ RouterModule, @@ -131,6 +135,7 @@ const routes: Routes = [ PlexService, EmbyService, MobileService, + NotificationMessageService, ], }) diff --git a/src/Ombi/ClientApp/app/settings/settingsmenu.component.html b/src/Ombi/ClientApp/app/settings/settingsmenu.component.html index e1b6d8140..1ac8da4c8 100644 --- a/src/Ombi/ClientApp/app/settings/settingsmenu.component.html +++ b/src/Ombi/ClientApp/app/settings/settingsmenu.component.html @@ -55,6 +55,7 @@