diff --git a/CHANGELOG.md b/CHANGELOG.md index 61f6343f2..1e6153b88 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,18 @@ # Changelog +## (unreleased) + +### **New Features** + +- Added the ability to override root and quality options in Sonarr (#2049) [Jamie] + +- Added Pending Approval into the filters list. [tidusjar] + +### **Fixes** + +- Fixed #2042. [Jamie] + + ## v3.0.0 (2018-03-04) ### **New Features** diff --git a/src/Ombi.Core/Engine/TvRequestEngine.cs b/src/Ombi.Core/Engine/TvRequestEngine.cs index 73696102f..f8fc33e9b 100644 --- a/src/Ombi.Core/Engine/TvRequestEngine.cs +++ b/src/Ombi.Core/Engine/TvRequestEngine.cs @@ -270,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; } 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, - @"^(?("")("".+?(? /// /// - /// 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.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/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.Store/Entities/OmbiUser.cs b/src/Ombi.Store/Entities/OmbiUser.cs index 28df10ac6..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 @@ -35,5 +36,26 @@ namespace Ombi.Store.Entities [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/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/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 bbd671d11..35c5755da 100644 --- a/src/Ombi.Store/Migrations/OmbiContextModelSnapshot.cs +++ b/src/Ombi.Store/Migrations/OmbiContextModelSnapshot.cs @@ -621,6 +621,8 @@ namespace Ombi.Store.Migrations b.Property("PosterPath"); + b.Property("QualityOverride"); + b.Property("ReleaseDate"); b.Property("RootFolder"); diff --git a/src/Ombi/ClientApp/app/interfaces/IRequestModel.ts b/src/Ombi/ClientApp/app/interfaces/IRequestModel.ts index d4345b185..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, @@ -36,6 +13,7 @@ export interface IMovieRequests extends IFullBaseRequest { qualityOverride: number; digitalReleaseDate: Date; + // For the UI rootPathOverrideTitle: string; qualityOverrideTitle: string; } @@ -85,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/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 390575908..c00d908cf 100644 --- a/src/Ombi/ClientApp/app/requests/movierequests.component.html +++ b/src/Ombi/ClientApp/app/requests/movierequests.component.html @@ -3,8 +3,10 @@
- + +
diff --git a/src/Ombi/ClientApp/app/requests/movierequests.component.ts b/src/Ombi/ClientApp/app/requests/movierequests.component.ts index cce804818..72c229f1c 100644 --- a/src/Ombi/ClientApp/app/requests/movierequests.component.ts +++ b/src/Ombi/ClientApp/app/requests/movierequests.component.ts @@ -36,6 +36,8 @@ export class MovieRequestsComponent implements OnInit { public filter: IFilter; public filterType = FilterType; + public sortDisplay: boolean; + private currentlyLoaded: number; private amountToLoad: number; diff --git a/src/Ombi/ClientApp/app/requests/requests.module.ts b/src/Ombi/ClientApp/app/requests/requests.module.ts index 6eccd82ac..6bbb3ca01 100644 --- a/src/Ombi/ClientApp/app/requests/requests.module.ts +++ b/src/Ombi/ClientApp/app/requests/requests.module.ts @@ -14,7 +14,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"; @@ -48,7 +48,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/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/Controllers/External/SonarrController.cs b/src/Ombi/Controllers/External/SonarrController.cs index 119010f81..1929c2798 100644 --- a/src/Ombi/Controllers/External/SonarrController.cs +++ b/src/Ombi/Controllers/External/SonarrController.cs @@ -46,5 +46,27 @@ namespace Ombi.Controllers.External { return await SonarrApi.GetRootFolders(settings.ApiKey, settings.FullUri); } + + /// + /// Gets the Sonarr profiles. + /// + /// + [HttpGet("Profiles")] + public async Task> GetProfiles() + { + var settings = await SonarrSettings.GetSettingsAsync(); + return await SonarrApi.GetProfiles(settings.ApiKey, settings.FullUri); + } + + /// + /// Gets the Sonarr root folders. + /// + /// + [HttpGet("RootFolders")] + public async Task> GetRootFolders() + { + var settings = await SonarrSettings.GetSettingsAsync(); + return await SonarrApi.GetRootFolders(settings.ApiKey, settings.FullUri); + } } } \ No newline at end of file diff --git a/src/Ombi/wwwroot/translations/en.json b/src/Ombi/wwwroot/translations/en.json index a64c1eaff..e4c944dd0 100644 --- a/src/Ombi/wwwroot/translations/en.json +++ b/src/Ombi/wwwroot/translations/en.json @@ -121,8 +121,8 @@ "RequestDate": "Request Date:", "QualityOverride": "Quality Override:", "RootFolderOverride": "Root Folder Override:", - "ChangeRootFolder":"Change Root Folder", - "ChangeQualityProfile":"Change Quality Profile", + "ChangeRootFolder":"Root Folder", + "ChangeQualityProfile":"Quality Profile", "MarkUnavailable":"Mark Unavailable", "MarkAvailable":"Mark Available", "Remove":"Remove", @@ -133,6 +133,7 @@ "GridStatus":"Status", "ReportIssue":"Report Issue", "Filter":"Filter", + "Sort":"Sort", "SeasonNumberHeading":"Season: {seasonNumber}" }, "Issues":{