diff --git a/music-placeholder.psd b/music-placeholder.psd new file mode 100644 index 000000000..5715f850a Binary files /dev/null and b/music-placeholder.psd differ diff --git a/src/Ombi.Api.Lidarr/ILidarrApi.cs b/src/Ombi.Api.Lidarr/ILidarrApi.cs new file mode 100644 index 000000000..38724f668 --- /dev/null +++ b/src/Ombi.Api.Lidarr/ILidarrApi.cs @@ -0,0 +1,25 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using Ombi.Api.Lidarr.Models; + +namespace Ombi.Api.Lidarr +{ + public interface ILidarrApi + { + Task> AlbumLookup(string searchTerm, string apiKey, string baseUrl); + Task> ArtistLookup(string searchTerm, string apiKey, string baseUrl); + Task> GetProfiles(string apiKey, string baseUrl); + Task> GetRootFolders(string apiKey, string baseUrl); + Task GetArtist(int artistId, string apiKey, string baseUrl); + Task GetArtistByForeignId(string foreignArtistId, string apiKey, string baseUrl); + Task GetAlbumsByArtist(string foreignArtistId); + Task GetAlbumByForeignId(string foreignArtistId, string apiKey, string baseUrl); + Task> GetArtists(string apiKey, string baseUrl); + Task> GetAllAlbums(string apiKey, string baseUrl); + Task AddArtist(ArtistAdd artist, string apiKey, string baseUrl); + Task MontiorAlbum(int albumId, string apiKey, string baseUrl); + Task> GetAllAlbumsByArtistId(int artistId, string apiKey, string baseUrl); + Task> GetMetadataProfile(string apiKey, string baseUrl); + Task> GetLanguageProfile(string apiKey, string baseUrl); + } +} \ No newline at end of file diff --git a/src/Ombi.Api.Lidarr/LidarrApi.cs b/src/Ombi.Api.Lidarr/LidarrApi.cs new file mode 100644 index 000000000..cf358699c --- /dev/null +++ b/src/Ombi.Api.Lidarr/LidarrApi.cs @@ -0,0 +1,155 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Ombi.Api.Lidarr.Models; + +namespace Ombi.Api.Lidarr +{ + public class LidarrApi : ILidarrApi + { + public LidarrApi(ILogger logger, IApi api) + { + Api = api; + Logger = logger; + } + + private IApi Api { get; } + private ILogger Logger { get; } + + private const string ApiVersion = "/api/v1"; + + public Task> GetProfiles(string apiKey, string baseUrl) + { + var request = new Request($"{ApiVersion}/qualityprofile", baseUrl, HttpMethod.Get); + + AddHeaders(request, apiKey); + return Api.Request>(request); + } + + public Task> GetRootFolders(string apiKey, string baseUrl) + { + var request = new Request($"{ApiVersion}/rootfolder", baseUrl, HttpMethod.Get); + + AddHeaders(request, apiKey); + return Api.Request>(request); + } + + public async Task> ArtistLookup(string searchTerm, string apiKey, string baseUrl) + { + var request = new Request($"{ApiVersion}/Artist/lookup", baseUrl, HttpMethod.Get); + request.AddQueryString("term", searchTerm); + + AddHeaders(request, apiKey); + return await Api.Request>(request); + } + + public Task> AlbumLookup(string searchTerm, string apiKey, string baseUrl) + { + var request = new Request($"{ApiVersion}/Album/lookup", baseUrl, HttpMethod.Get); + request.AddQueryString("term", searchTerm); + + AddHeaders(request, apiKey); + return Api.Request>(request); + } + + public Task GetArtist(int artistId, string apiKey, string baseUrl) + { + var request = new Request($"{ApiVersion}/artist/{artistId}", baseUrl, HttpMethod.Get); + + AddHeaders(request, apiKey); + return Api.Request(request); + } + + public async Task GetArtistByForeignId(string foreignArtistId, string apiKey, string baseUrl) + { + var request = new Request($"{ApiVersion}/artist/lookup", baseUrl, HttpMethod.Get); + + request.AddQueryString("term", $"lidarr:{foreignArtistId}"); + AddHeaders(request, apiKey); + return (await Api.Request>(request)).FirstOrDefault(); + } + + public async Task GetAlbumByForeignId(string foreignArtistId, string apiKey, string baseUrl) + { + var request = new Request($"{ApiVersion}/album/lookup", baseUrl, HttpMethod.Get); + + request.AddQueryString("term", $"lidarr:{foreignArtistId}"); + AddHeaders(request, apiKey); + var albums = await Api.Request>(request); + return albums.FirstOrDefault(); + } + + public Task GetAlbumsByArtist(string foreignArtistId) + { + var request = new Request(string.Empty, $"https://api.lidarr.audio/api/v0.3/artist/{foreignArtistId}", + HttpMethod.Get) {IgnoreBaseUrlAppend = true}; + return Api.Request(request); + } + + public Task> GetArtists(string apiKey, string baseUrl) + { + var request = new Request($"{ApiVersion}/artist", baseUrl, HttpMethod.Get); + + AddHeaders(request, apiKey); + return Api.Request>(request); + } + + public Task> GetAllAlbums(string apiKey, string baseUrl) + { + var request = new Request($"{ApiVersion}/album", baseUrl, HttpMethod.Get); + + AddHeaders(request, apiKey); + return Api.Request>(request); + } + + public Task AddArtist(ArtistAdd artist, string apiKey, string baseUrl) + { + var request = new Request($"{ApiVersion}/artist", baseUrl, HttpMethod.Post); + request.AddJsonBody(artist); + AddHeaders(request, apiKey); + return Api.Request(request); + } + + public async Task MontiorAlbum(int albumId, string apiKey, string baseUrl) + { + var request = new Request($"{ApiVersion}/album/monitor", baseUrl, HttpMethod.Put); + request.AddJsonBody(new + { + albumIds = new[] { albumId }, + monitored = true + }); + AddHeaders(request, apiKey); + return (await Api.Request>(request)).FirstOrDefault(); + } + + public Task> GetAllAlbumsByArtistId(int artistId, string apiKey, string baseUrl) + { + var request = new Request($"{ApiVersion}/album", baseUrl, HttpMethod.Get); + request.AddQueryString("artistId", artistId.ToString()); + AddHeaders(request, apiKey); + return Api.Request>(request); + } + + public Task> GetLanguageProfile(string apiKey, string baseUrl) + { + var request = new Request($"{ApiVersion}/languageprofile", baseUrl, HttpMethod.Get); + AddHeaders(request, apiKey); + return Api.Request>(request); + } + + public Task> GetMetadataProfile(string apiKey, string baseUrl) + { + var request = new Request($"{ApiVersion}/metadataprofile", baseUrl, HttpMethod.Get); + AddHeaders(request, apiKey); + return Api.Request>(request); + } + + private void AddHeaders(Request request, string key) + { + request.AddHeader("X-Api-Key", key); + } + } +} diff --git a/src/Ombi.Api.Lidarr/Models/AlbumByArtistResponse.cs b/src/Ombi.Api.Lidarr/Models/AlbumByArtistResponse.cs new file mode 100644 index 000000000..62f19651f --- /dev/null +++ b/src/Ombi.Api.Lidarr/Models/AlbumByArtistResponse.cs @@ -0,0 +1,34 @@ +namespace Ombi.Api.Lidarr.Models +{ + public class AlbumByArtistResponse + { + public Album[] Albums { get; set; } + public string ArtistName { get; set; } + public string Disambiguation { get; set; } + public string Id { get; set; } + public Image[] Images { get; set; } + public Link[] Links { get; set; } + public string Overview { get; set; } + public Rating Rating { get; set; } + public string SortName { get; set; } + public string Status { get; set; } + public string Type { get; set; } + } + + public class Rating + { + public int Count { get; set; } + public decimal Value { get; set; } + } + + public class Album + { + public string Disambiguation { get; set; } + public string Id { get; set; } + public string ReleaseDate { get; set; } + public string[] ReleaseStatuses { get; set; } + public string[] SecondaryTypes { get; set; } + public string Title { get; set; } + public string Type { get; set; } + } +} \ No newline at end of file diff --git a/src/Ombi.Api.Lidarr/Models/AlbumLookup.cs b/src/Ombi.Api.Lidarr/Models/AlbumLookup.cs new file mode 100644 index 000000000..b2394eb5f --- /dev/null +++ b/src/Ombi.Api.Lidarr/Models/AlbumLookup.cs @@ -0,0 +1,25 @@ +using System; + +namespace Ombi.Api.Lidarr.Models +{ + public class AlbumLookup + { + public string title { get; set; } + public int artistId { get; set; } + public string foreignAlbumId { get; set; } + public bool monitored { get; set; } + public int profileId { get; set; } + public int duration { get; set; } + public string albumType { get; set; } + public string[] secondaryTypes { get; set; } + public int mediumCount { get; set; } + public Ratings ratings { get; set; } + public DateTime releaseDate { get; set; } + //public object[] releases { get; set; } + public object[] genres { get; set; } + //public object[] media { get; set; } + public Artist artist { get; set; } + public Image[] images { get; set; } + public string remoteCover { get; set; } + } +} \ No newline at end of file diff --git a/src/Ombi.Api.Lidarr/Models/AlbumResponse.cs b/src/Ombi.Api.Lidarr/Models/AlbumResponse.cs new file mode 100644 index 000000000..f9d35c43b --- /dev/null +++ b/src/Ombi.Api.Lidarr/Models/AlbumResponse.cs @@ -0,0 +1,27 @@ +using System; + +namespace Ombi.Api.Lidarr.Models +{ + public class AlbumResponse + { + public string title { get; set; } + public string disambiguation { get; set; } + public int artistId { get; set; } + public string foreignAlbumId { get; set; } + public bool monitored { get; set; } + public int profileId { get; set; } + public int duration { get; set; } + public string albumType { get; set; } + public object[] secondaryTypes { get; set; } + public int mediumCount { get; set; } + public Ratings ratings { get; set; } + public DateTime releaseDate { get; set; } + public Currentrelease currentRelease { get; set; } + public Release[] releases { get; set; } + public object[] genres { get; set; } + public Medium[] media { get; set; } + public Image[] images { get; set; } + public Statistics statistics { get; set; } + public int id { get; set; } + } +} \ No newline at end of file diff --git a/src/Ombi.Api.Lidarr/Models/Artist.cs b/src/Ombi.Api.Lidarr/Models/Artist.cs new file mode 100644 index 000000000..bc6afc20e --- /dev/null +++ b/src/Ombi.Api.Lidarr/Models/Artist.cs @@ -0,0 +1,25 @@ +using System; + +namespace Ombi.Api.Lidarr.Models +{ + public class Artist + { + public string status { get; set; } + public bool ended { get; set; } + public string artistName { get; set; } + public string foreignArtistId { get; set; } + public int tadbId { get; set; } + public int discogsId { get; set; } + public object[] links { get; set; } + public object[] images { get; set; } + public int qualityProfileId { get; set; } + public int languageProfileId { get; set; } + public int metadataProfileId { get; set; } + public bool albumFolder { get; set; } + public bool monitored { get; set; } + public object[] genres { get; set; } + public object[] tags { get; set; } + public DateTime added { get; set; } + public Statistics statistics { get; set; } + } +} \ No newline at end of file diff --git a/src/Ombi.Api.Lidarr/Models/ArtistAdd.cs b/src/Ombi.Api.Lidarr/Models/ArtistAdd.cs new file mode 100644 index 000000000..65aec3ac8 --- /dev/null +++ b/src/Ombi.Api.Lidarr/Models/ArtistAdd.cs @@ -0,0 +1,49 @@ +using System; +using System.Net.Mime; + +namespace Ombi.Api.Lidarr.Models +{ + public class ArtistAdd + { + public string status { get; set; } + public bool ended { get; set; } + public string artistName { get; set; } + public string foreignArtistId { get; set; } + public int tadbId { get; set; } + public int discogsId { get; set; } + public string overview { get; set; } + public string disambiguation { get; set; } + public Link[] links { get; set; } + public Image[] images { get; set; } + public string remotePoster { get; set; } + public int qualityProfileId { get; set; } + public int languageProfileId { get; set; } + public int metadataProfileId { get; set; } + public bool albumFolder { get; set; } + public bool monitored { get; set; } + public string cleanName { get; set; } + public string sortName { get; set; } + public object[] tags { get; set; } + public DateTime added { get; set; } + public Ratings ratings { get; set; } + public Statistics statistics { get; set; } + public Addoptions addOptions { get; set; } + public string rootFolderPath { get; set; } + } + + public class Addoptions + { + /// + /// Future = 1 + /// Missing = 2 + /// Existing = 3 + /// First = 5 + /// Latest = 4 + /// None = 6 + /// + public int selectedOption { get; set; } + public bool monitored { get; set; } + public bool searchForMissingAlbums { get; set; } + public string[] AlbumsToMonitor { get; set; } // Uses the MusicBrainzAlbumId! + } +} \ No newline at end of file diff --git a/src/Ombi.Api.Lidarr/Models/ArtistLookup.cs b/src/Ombi.Api.Lidarr/Models/ArtistLookup.cs new file mode 100644 index 000000000..aa454c0a0 --- /dev/null +++ b/src/Ombi.Api.Lidarr/Models/ArtistLookup.cs @@ -0,0 +1,32 @@ +using System; +using System.Net.Mime; + +namespace Ombi.Api.Lidarr.Models +{ + public class ArtistLookup + { + public string status { get; set; } + public bool ended { get; set; } + public string artistName { get; set; } + public string foreignArtistId { get; set; } + public int tadbId { get; set; } + public int discogsId { get; set; } + public string overview { get; set; } + public string artistType { get; set; } + public string disambiguation { get; set; } + public Link[] links { get; set; } + public Image[] images { get; set; } + public string remotePoster { get; set; } + public int qualityProfileId { get; set; } + public int languageProfileId { get; set; } + public int metadataProfileId { get; set; } + public bool albumFolder { get; set; } + public bool monitored { get; set; } + public string cleanName { get; set; } + public string sortName { get; set; } + public object[] tags { get; set; } + public DateTime added { get; set; } + public Ratings ratings { get; set; } + public Statistics statistics { get; set; } + } +} \ No newline at end of file diff --git a/src/Ombi.Api.Lidarr/Models/ArtistResult.cs b/src/Ombi.Api.Lidarr/Models/ArtistResult.cs new file mode 100644 index 000000000..32b3aaab5 --- /dev/null +++ b/src/Ombi.Api.Lidarr/Models/ArtistResult.cs @@ -0,0 +1,93 @@ +using System; + +namespace Ombi.Api.Lidarr.Models +{ + + public class ArtistResult + { + public string status { get; set; } + public bool ended { get; set; } + public DateTime lastInfoSync { get; set; } + public string artistName { get; set; } + public string foreignArtistId { get; set; } + public int tadbId { get; set; } + public int discogsId { get; set; } + public string overview { get; set; } + public string artistType { get; set; } + public string disambiguation { get; set; } + public Link[] links { get; set; } + public Nextalbum nextAlbum { get; set; } + public Image[] images { get; set; } + public string path { get; set; } + public int qualityProfileId { get; set; } + public int languageProfileId { get; set; } + public int metadataProfileId { get; set; } + public bool albumFolder { get; set; } + public bool monitored { get; set; } + public object[] genres { get; set; } + public string cleanName { get; set; } + public string sortName { get; set; } + public object[] tags { get; set; } + public DateTime added { get; set; } + public Ratings ratings { get; set; } + public Statistics statistics { get; set; } + public int id { get; set; } + } + + public class Nextalbum + { + public string foreignAlbumId { get; set; } + public int artistId { get; set; } + public string title { get; set; } + public string disambiguation { get; set; } + public string cleanTitle { get; set; } + public DateTime releaseDate { get; set; } + public int profileId { get; set; } + public int duration { get; set; } + public bool monitored { get; set; } + public object[] images { get; set; } + public object[] genres { get; set; } + public Medium[] media { get; set; } + public DateTime lastInfoSync { get; set; } + public DateTime added { get; set; } + public string albumType { get; set; } + public object[] secondaryTypes { get; set; } + public Ratings ratings { get; set; } + public Release[] releases { get; set; } + public Currentrelease currentRelease { get; set; } + public int id { get; set; } + } + + public class Currentrelease + { + public string id { get; set; } + public string title { get; set; } + public DateTime releaseDate { get; set; } + public int trackCount { get; set; } + public int mediaCount { get; set; } + public string disambiguation { get; set; } + public string[] country { get; set; } + public string format { get; set; } + public string[] label { get; set; } + } + + public class Medium + { + public int number { get; set; } + public string name { get; set; } + public string format { get; set; } + } + + public class Release + { + public string id { get; set; } + public string title { get; set; } + public DateTime releaseDate { get; set; } + public int trackCount { get; set; } + public int mediaCount { get; set; } + public string disambiguation { get; set; } + public string[] country { get; set; } + public string format { get; set; } + public string[] label { get; set; } + } +} \ No newline at end of file diff --git a/src/Ombi.Api.Lidarr/Models/Image.cs b/src/Ombi.Api.Lidarr/Models/Image.cs new file mode 100644 index 000000000..172a13fe9 --- /dev/null +++ b/src/Ombi.Api.Lidarr/Models/Image.cs @@ -0,0 +1,8 @@ +namespace Ombi.Api.Lidarr.Models +{ + public class Image + { + public string coverType { get; set; } + public string url { get; set; } + } +} \ No newline at end of file diff --git a/src/Ombi.Api.Lidarr/Models/LanguageProfiles.cs b/src/Ombi.Api.Lidarr/Models/LanguageProfiles.cs new file mode 100644 index 000000000..f503fe33f --- /dev/null +++ b/src/Ombi.Api.Lidarr/Models/LanguageProfiles.cs @@ -0,0 +1,8 @@ +namespace Ombi.Api.Lidarr.Models +{ + public class LanguageProfiles + { + public string name { get; set; } + public int id { get; set; } + } +} \ No newline at end of file diff --git a/src/Ombi.Api.Lidarr/Models/LidarrProfile.cs b/src/Ombi.Api.Lidarr/Models/LidarrProfile.cs new file mode 100644 index 000000000..19ebda5a6 --- /dev/null +++ b/src/Ombi.Api.Lidarr/Models/LidarrProfile.cs @@ -0,0 +1,23 @@ +using System.Collections.Generic; + +namespace Ombi.Api.Lidarr.Models +{ + public class Quality + { + public int id { get; set; } + public string name { get; set; } + } + + public class Item + { + public Quality quality { get; set; } + public bool allowed { get; set; } + } + + public class LidarrProfile +{ + public string name { get; set; } + public List items { get; set; } + public int id { get; set; } + } +} \ No newline at end of file diff --git a/src/Ombi.Api.Lidarr/Models/LidarrRootFolder.cs b/src/Ombi.Api.Lidarr/Models/LidarrRootFolder.cs new file mode 100644 index 000000000..a3a252f04 --- /dev/null +++ b/src/Ombi.Api.Lidarr/Models/LidarrRootFolder.cs @@ -0,0 +1,11 @@ +namespace Ombi.Api.Lidarr.Models +{ + public class LidarrRootFolder + { + public string path { get; set; } + public long freeSpace { get; set; } + public object[] unmappedFolders { get; set; } + public int id { get; set; } + } + +} \ No newline at end of file diff --git a/src/Ombi.Api.Lidarr/Models/Link.cs b/src/Ombi.Api.Lidarr/Models/Link.cs new file mode 100644 index 000000000..492ac0426 --- /dev/null +++ b/src/Ombi.Api.Lidarr/Models/Link.cs @@ -0,0 +1,8 @@ +namespace Ombi.Api.Lidarr.Models +{ + public class Link + { + public string url { get; set; } + public string name { get; set; } + } +} \ No newline at end of file diff --git a/src/Ombi.Api.Lidarr/Models/MetadataProfile.cs b/src/Ombi.Api.Lidarr/Models/MetadataProfile.cs new file mode 100644 index 000000000..bda3333f1 --- /dev/null +++ b/src/Ombi.Api.Lidarr/Models/MetadataProfile.cs @@ -0,0 +1,8 @@ +namespace Ombi.Api.Lidarr.Models +{ + public class MetadataProfile + { + public string name { get; set; } + public int id { get; set; } + } +} \ No newline at end of file diff --git a/src/Ombi.Api.Lidarr/Models/Ratings.cs b/src/Ombi.Api.Lidarr/Models/Ratings.cs new file mode 100644 index 000000000..f2aac4203 --- /dev/null +++ b/src/Ombi.Api.Lidarr/Models/Ratings.cs @@ -0,0 +1,8 @@ +namespace Ombi.Api.Lidarr.Models +{ + public class Ratings + { + public int votes { get; set; } + public decimal value { get; set; } + } +} \ No newline at end of file diff --git a/src/Ombi.Api.Lidarr/Models/Statistics.cs b/src/Ombi.Api.Lidarr/Models/Statistics.cs new file mode 100644 index 000000000..5d8eb4275 --- /dev/null +++ b/src/Ombi.Api.Lidarr/Models/Statistics.cs @@ -0,0 +1,12 @@ +namespace Ombi.Api.Lidarr.Models +{ + public class Statistics + { + public int albumCount { get; set; } + public int trackFileCount { get; set; } + public int trackCount { get; set; } + public int totalTrackCount { get; set; } + public int sizeOnDisk { get; set; } + public decimal percentOfEpisodes { get; set; } + } +} \ No newline at end of file diff --git a/src/Ombi.Api.Lidarr/Ombi.Api.Lidarr.csproj b/src/Ombi.Api.Lidarr/Ombi.Api.Lidarr.csproj new file mode 100644 index 000000000..a3651df3c --- /dev/null +++ b/src/Ombi.Api.Lidarr/Ombi.Api.Lidarr.csproj @@ -0,0 +1,11 @@ + + + + netstandard2.0 + + + + + + + diff --git a/src/Ombi.Api/Request.cs b/src/Ombi.Api/Request.cs index cfd284f54..fd888d0d2 100644 --- a/src/Ombi.Api/Request.cs +++ b/src/Ombi.Api/Request.cs @@ -28,6 +28,7 @@ namespace Ombi.Api public bool IgnoreErrors { get; set; } public bool Retry { get; set; } public List StatusCodeToRetry { get; set; } = new List(); + public bool IgnoreBaseUrlAppend { get; set; } public Action OnBeforeDeserialization { get; set; } @@ -38,7 +39,7 @@ namespace Ombi.Api var sb = new StringBuilder(); if (!string.IsNullOrEmpty(BaseUrl)) { - sb.Append(!BaseUrl.EndsWith("/") ? string.Format("{0}/", BaseUrl) : BaseUrl); + sb.Append(!BaseUrl.EndsWith("/") && !IgnoreBaseUrlAppend ? string.Format("{0}/", BaseUrl) : BaseUrl); } sb.Append(Endpoint.StartsWith("/") ? Endpoint.Remove(0, 1) : Endpoint); return sb.ToString(); diff --git a/src/Ombi.Core.Tests/Rule/Search/ExistingRequestRuleTests.cs b/src/Ombi.Core.Tests/Rule/Search/ExistingRequestRuleTests.cs index a706472dd..e32c8e996 100644 --- a/src/Ombi.Core.Tests/Rule/Search/ExistingRequestRuleTests.cs +++ b/src/Ombi.Core.Tests/Rule/Search/ExistingRequestRuleTests.cs @@ -19,12 +19,14 @@ namespace Ombi.Core.Tests.Rule.Search MovieMock = new Mock(); TvMock = new Mock(); - Rule = new ExistingRule(MovieMock.Object, TvMock.Object); + MusicMock = new Mock(); + Rule = new ExistingRule(MovieMock.Object, TvMock.Object, MusicMock.Object); } private ExistingRule Rule { get; set; } private Mock MovieMock { get; set; } private Mock TvMock { get; set; } + private Mock MusicMock { get; set; } [Test] diff --git a/src/Ombi.Core/Engine/BaseMediaEngine.cs b/src/Ombi.Core/Engine/BaseMediaEngine.cs index 2eab74b75..cb0047b96 100644 --- a/src/Ombi.Core/Engine/BaseMediaEngine.cs +++ b/src/Ombi.Core/Engine/BaseMediaEngine.cs @@ -36,6 +36,7 @@ namespace Ombi.Core.Engine protected IRequestServiceMain RequestService { get; } protected IMovieRequestRepository MovieRepository => RequestService.MovieRequestService; protected ITvRequestRepository TvRepository => RequestService.TvRequestService; + protected IMusicRequestRepository MusicRepository => RequestService.MusicRequestRepository; protected readonly ICacheService Cache; protected readonly ISettingsService OmbiSettings; protected readonly IRepository _subscriptionRepository; diff --git a/src/Ombi.Core/Engine/IMusicRequestEngine.cs b/src/Ombi.Core/Engine/IMusicRequestEngine.cs new file mode 100644 index 000000000..d81ccb429 --- /dev/null +++ b/src/Ombi.Core/Engine/IMusicRequestEngine.cs @@ -0,0 +1,24 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using Ombi.Core.Models.Requests; +using Ombi.Core.Models.UI; +using Ombi.Store.Entities.Requests; + +namespace Ombi.Core.Engine +{ + public interface IMusicRequestEngine + { + TaskApproveAlbum(AlbumRequest request); + Task ApproveAlbumById(int requestId); + Task DenyAlbumById(int modelId); + Task> GetRequests(); + Task> GetRequests(int count, int position, OrderFilterModel orderFilter); + Task GetTotal(); + Task MarkAvailable(int modelId); + Task MarkUnavailable(int modelId); + Task RemoveAlbumRequest(int requestId); + Task RequestAlbum(MusicAlbumRequestViewModel model); + Task> SearchAlbumRequest(string search); + Task UserHasRequest(string userId); + } +} \ No newline at end of file diff --git a/src/Ombi.Core/Engine/Interfaces/IMusicSearchEngine.cs b/src/Ombi.Core/Engine/Interfaces/IMusicSearchEngine.cs new file mode 100644 index 000000000..03294982a --- /dev/null +++ b/src/Ombi.Core/Engine/Interfaces/IMusicSearchEngine.cs @@ -0,0 +1,16 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using Ombi.Api.Lidarr.Models; +using Ombi.Core.Models.Search; + +namespace Ombi.Core.Engine +{ + public interface IMusicSearchEngine + { + Task GetAlbumArtist(string foreignArtistId); + Task GetArtist(int artistId); + Task> GetArtistAlbums(string foreignArtistId); + Task> SearchAlbum(string search); + Task> SearchArtist(string search); + } +} \ No newline at end of file diff --git a/src/Ombi.Core/Engine/IRecentlyAddedEngine.cs b/src/Ombi.Core/Engine/Interfaces/IRecentlyAddedEngine.cs similarity index 100% rename from src/Ombi.Core/Engine/IRecentlyAddedEngine.cs rename to src/Ombi.Core/Engine/Interfaces/IRecentlyAddedEngine.cs diff --git a/src/Ombi.Core/Engine/IUserStatsEngine.cs b/src/Ombi.Core/Engine/Interfaces/IUserStatsEngine.cs similarity index 100% rename from src/Ombi.Core/Engine/IUserStatsEngine.cs rename to src/Ombi.Core/Engine/Interfaces/IUserStatsEngine.cs diff --git a/src/Ombi.Core/Engine/MovieRequestEngine.cs b/src/Ombi.Core/Engine/MovieRequestEngine.cs index f73c6fda1..f0a4e1f29 100644 --- a/src/Ombi.Core/Engine/MovieRequestEngine.cs +++ b/src/Ombi.Core/Engine/MovieRequestEngine.cs @@ -336,6 +336,7 @@ namespace Ombi.Core.Engine }; } + request.MarkedAsApproved = DateTime.Now; request.Approved = true; request.Denied = false; await MovieRepository.Update(request); diff --git a/src/Ombi.Core/Engine/MusicRequestEngine.cs b/src/Ombi.Core/Engine/MusicRequestEngine.cs new file mode 100644 index 000000000..a27b17c38 --- /dev/null +++ b/src/Ombi.Core/Engine/MusicRequestEngine.cs @@ -0,0 +1,457 @@ +using Ombi.Api.TheMovieDb; +using Ombi.Core.Models.Requests; +using Ombi.Helpers; +using Ombi.Store.Entities; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Security.Principal; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using Ombi.Api.Lidarr; +using Ombi.Core.Authentication; +using Ombi.Core.Engine.Interfaces; +using Ombi.Core.Models.UI; +using Ombi.Core.Rule.Interfaces; +using Ombi.Core.Senders; +using Ombi.Core.Settings; +using Ombi.Settings.Settings.Models; +using Ombi.Settings.Settings.Models.External; +using Ombi.Store.Entities.Requests; +using Ombi.Store.Repository; + +namespace Ombi.Core.Engine +{ + public class MusicRequestEngine : BaseMediaEngine, IMusicRequestEngine + { + public MusicRequestEngine(IRequestServiceMain requestService, IPrincipal user, + INotificationHelper helper, IRuleEvaluator r, ILogger log, + OmbiUserManager manager, IRepository rl, ICacheService cache, + ISettingsService ombiSettings, IRepository sub, ILidarrApi lidarr, + ISettingsService lidarrSettings, IMusicSender sender) + : base(user, requestService, r, manager, cache, ombiSettings, sub) + { + NotificationHelper = helper; + _musicSender = sender; + Logger = log; + _requestLog = rl; + _lidarrApi = lidarr; + _lidarrSettings = lidarrSettings; + } + + private INotificationHelper NotificationHelper { get; } + //private IMovieSender Sender { get; } + private ILogger Logger { get; } + private readonly IRepository _requestLog; + private readonly ISettingsService _lidarrSettings; + private readonly ILidarrApi _lidarrApi; + private readonly IMusicSender _musicSender; + + /// + /// Requests the Album. + /// + /// The model. + /// + public async Task RequestAlbum(MusicAlbumRequestViewModel model) + { + var s = await _lidarrSettings.GetSettingsAsync(); + var album = await _lidarrApi.GetAlbumByForeignId(model.ForeignAlbumId, s.ApiKey, s.FullUri); + if (album == null) + { + return new RequestEngineResult + { + Result = false, + Message = "There was an issue adding this album!", + ErrorMessage = "Please try again later" + }; + } + + var userDetails = await GetUser(); + + var requestModel = new AlbumRequest + { + ForeignAlbumId = model.ForeignAlbumId, + ArtistName = album.artist?.artistName, + ReleaseDate = album.releaseDate, + RequestedDate = DateTime.Now, + RequestType = RequestType.Album, + Rating = album.ratings?.value ?? 0m, + RequestedUserId = userDetails.Id, + Title = album.title, + Disk = album.images?.FirstOrDefault(x => x.coverType.Equals("disc"))?.url, + Cover = album.images?.FirstOrDefault(x => x.coverType.Equals("cover"))?.url, + ForeignArtistId = album?.artist?.foreignArtistId ?? string.Empty + }; + if (requestModel.Cover.IsNullOrEmpty()) + { + requestModel.Cover = album.remoteCover; + } + + var ruleResults = (await RunRequestRules(requestModel)).ToList(); + if (ruleResults.Any(x => !x.Success)) + { + return new RequestEngineResult + { + ErrorMessage = ruleResults.FirstOrDefault(x => x.Message.HasValue()).Message + }; + } + + if (requestModel.Approved) // The rules have auto approved this + { + var requestEngineResult = await AddAlbumRequest(requestModel); + if (requestEngineResult.Result) + { + var result = await ApproveAlbum(requestModel); + if (result.IsError) + { + Logger.LogWarning("Tried auto sending Album but failed. Message: {0}", result.Message); + return new RequestEngineResult + { + Message = result.Message, + ErrorMessage = result.Message, + Result = false + }; + } + + return requestEngineResult; + } + + // If there are no providers then it's successful but album has not been sent + } + + return await AddAlbumRequest(requestModel); + } + + + /// + /// Gets the requests. + /// + /// The count. + /// The position. + /// The order/filter type. + /// + public async Task> GetRequests(int count, int position, + OrderFilterModel orderFilter) + { + var shouldHide = await HideFromOtherUsers(); + IQueryable allRequests; + if (shouldHide.Hide) + { + allRequests = + MusicRepository.GetWithUser(shouldHide + .UserId); //.Skip(position).Take(count).OrderByDescending(x => x.ReleaseDate).ToListAsync(); + } + else + { + allRequests = + MusicRepository + .GetWithUser(); //.Skip(position).Take(count).OrderByDescending(x => x.ReleaseDate).ToListAsync(); + } + + switch (orderFilter.AvailabilityFilter) + { + case FilterType.None: + break; + case FilterType.Available: + allRequests = allRequests.Where(x => x.Available); + break; + case FilterType.NotAvailable: + allRequests = allRequests.Where(x => !x.Available); + break; + default: + throw new ArgumentOutOfRangeException(); + } + + switch (orderFilter.StatusFilter) + { + case FilterType.None: + break; + case FilterType.Approved: + allRequests = allRequests.Where(x => x.Approved); + break; + case FilterType.Processing: + allRequests = allRequests.Where(x => x.Approved && !x.Available); + break; + case FilterType.PendingApproval: + allRequests = allRequests.Where(x => !x.Approved && !x.Available && !(x.Denied ?? false)); + break; + default: + throw new ArgumentOutOfRangeException(); + } + + var total = allRequests.Count(); + + var requests = await (OrderAlbums(allRequests, orderFilter.OrderType)).Skip(position).Take(count) + .ToListAsync(); + + requests.ForEach(async x => + { + await CheckForSubscription(shouldHide, x); + }); + return new RequestsViewModel + { + Collection = requests, + Total = total + }; + } + + private IQueryable OrderAlbums(IQueryable allRequests, OrderType type) + { + switch (type) + { + case OrderType.RequestedDateAsc: + return allRequests.OrderBy(x => x.RequestedDate); + case OrderType.RequestedDateDesc: + return allRequests.OrderByDescending(x => x.RequestedDate); + case OrderType.TitleAsc: + return allRequests.OrderBy(x => x.Title); + case OrderType.TitleDesc: + return allRequests.OrderByDescending(x => x.Title); + default: + throw new ArgumentOutOfRangeException(nameof(type), type, null); + } + } + + public async Task GetTotal() + { + var shouldHide = await HideFromOtherUsers(); + if (shouldHide.Hide) + { + return await MusicRepository.GetWithUser(shouldHide.UserId).CountAsync(); + } + else + { + return await MusicRepository.GetWithUser().CountAsync(); + } + } + + /// + /// Gets the requests. + /// + /// + public async Task> GetRequests() + { + var shouldHide = await HideFromOtherUsers(); + List allRequests; + if (shouldHide.Hide) + { + allRequests = await MusicRepository.GetWithUser(shouldHide.UserId).ToListAsync(); + } + else + { + allRequests = await MusicRepository.GetWithUser().ToListAsync(); + } + + allRequests.ForEach(async x => + { + await CheckForSubscription(shouldHide, x); + }); + return allRequests; + } + + private async Task CheckForSubscription(HideResult shouldHide, AlbumRequest x) + { + if (shouldHide.UserId == x.RequestedUserId) + { + x.ShowSubscribe = false; + } + else + { + x.ShowSubscribe = true; + var sub = await _subscriptionRepository.GetAll().FirstOrDefaultAsync(s => + s.UserId == shouldHide.UserId && s.RequestId == x.Id && s.RequestType == RequestType.Album); + x.Subscribed = sub != null; + } + } + + /// + /// Searches the album request. + /// + /// The search. + /// + public async Task> SearchAlbumRequest(string search) + { + var shouldHide = await HideFromOtherUsers(); + List allRequests; + if (shouldHide.Hide) + { + allRequests = await MusicRepository.GetWithUser(shouldHide.UserId).ToListAsync(); + } + else + { + allRequests = await MusicRepository.GetWithUser().ToListAsync(); + } + + var results = allRequests.Where(x => x.Title.Contains(search, CompareOptions.IgnoreCase)).ToList(); + results.ForEach(async x => + { + await CheckForSubscription(shouldHide, x); + }); + return results; + } + + public async Task ApproveAlbumById(int requestId) + { + var request = await MusicRepository.Find(requestId); + return await ApproveAlbum(request); + } + + public async Task DenyAlbumById(int modelId) + { + var request = await MusicRepository.Find(modelId); + if (request == null) + { + return new RequestEngineResult + { + ErrorMessage = "Request does not exist" + }; + } + + request.Denied = true; + // We are denying a request + NotificationHelper.Notify(request, NotificationType.RequestDeclined); + await MusicRepository.Update(request); + + return new RequestEngineResult + { + Message = "Request successfully deleted", + }; + } + + public async Task ApproveAlbum(AlbumRequest request) + { + if (request == null) + { + return new RequestEngineResult + { + ErrorMessage = "Request does not exist" + }; + } + + request.MarkedAsApproved = DateTime.Now; + request.Approved = true; + request.Denied = false; + await MusicRepository.Update(request); + + + var canNotify = await RunSpecificRule(request, SpecificRules.CanSendNotification); + if (canNotify.Success) + { + NotificationHelper.Notify(request, NotificationType.RequestApproved); + } + + if (request.Approved) + { + var result = await _musicSender.Send(request); + if (result.Success && result.Sent) + { + return new RequestEngineResult + { + Result = true + }; + } + + if (!result.Success) + { + Logger.LogWarning("Tried auto sending album but failed. Message: {0}", result.Message); + return new RequestEngineResult + { + Message = result.Message, + ErrorMessage = result.Message, + Result = false + }; + } + + // If there are no providers then it's successful but movie has not been sent + } + + return new RequestEngineResult + { + Result = true + }; + } + + /// + /// Removes the Album request. + /// + /// The request identifier. + /// + public async Task RemoveAlbumRequest(int requestId) + { + var request = await MusicRepository.GetAll().FirstOrDefaultAsync(x => x.Id == requestId); + await MusicRepository.Delete(request); + } + + public async Task UserHasRequest(string userId) + { + return await MusicRepository.GetAll().AnyAsync(x => x.RequestedUserId == userId); + } + + public async Task MarkUnavailable(int modelId) + { + var request = await MusicRepository.Find(modelId); + if (request == null) + { + return new RequestEngineResult + { + ErrorMessage = "Request does not exist" + }; + } + + request.Available = false; + await MusicRepository.Update(request); + + return new RequestEngineResult + { + Message = "Request is now unavailable", + Result = true + }; + } + + public async Task MarkAvailable(int modelId) + { + var request = await MusicRepository.Find(modelId); + if (request == null) + { + return new RequestEngineResult + { + ErrorMessage = "Request does not exist" + }; + } + + request.Available = true; + request.MarkedAsAvailable = DateTime.Now; + NotificationHelper.Notify(request, NotificationType.RequestAvailable); + await MusicRepository.Update(request); + + return new RequestEngineResult + { + Message = "Request is now available", + Result = true + }; + } + + private async Task AddAlbumRequest(AlbumRequest model) + { + await MusicRepository.Add(model); + + var result = await RunSpecificRule(model, SpecificRules.CanSendNotification); + if (result.Success) + { + NotificationHelper.NewRequest(model); + } + + await _requestLog.Add(new RequestLog + { + UserId = (await GetUser()).Id, + RequestDate = DateTime.UtcNow, + RequestId = model.Id, + RequestType = RequestType.Album, + }); + + return new RequestEngineResult { Result = true, Message = $"{model.Title} has been successfully added!" }; + } + } +} \ No newline at end of file diff --git a/src/Ombi.Core/Engine/MusicSearchEngine.cs b/src/Ombi.Core/Engine/MusicSearchEngine.cs new file mode 100644 index 000000000..d0e577801 --- /dev/null +++ b/src/Ombi.Core/Engine/MusicSearchEngine.cs @@ -0,0 +1,219 @@ +using System; +using AutoMapper; +using Microsoft.Extensions.Logging; +using Ombi.Api.TheMovieDb; +using Ombi.Api.TheMovieDb.Models; +using Ombi.Core.Models.Requests; +using Ombi.Core.Models.Search; +using System.Collections.Generic; +using System.Linq; +using System.Security.Principal; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Ombi.Core.Rule.Interfaces; +using Microsoft.Extensions.Caching.Memory; +using Ombi.Api.Lidarr; +using Ombi.Api.Lidarr.Models; +using Ombi.Core.Authentication; +using Ombi.Core.Settings; +using Ombi.Helpers; +using Ombi.Settings.Settings.Models; +using Ombi.Settings.Settings.Models.External; +using Ombi.Store.Entities; +using Ombi.Store.Repository; + +namespace Ombi.Core.Engine +{ + public class MusicSearchEngine : BaseMediaEngine, IMusicSearchEngine + { + public MusicSearchEngine(IPrincipal identity, IRequestServiceMain service, ILidarrApi lidarrApi, IMapper mapper, + ILogger logger, IRuleEvaluator r, OmbiUserManager um, ICacheService mem, ISettingsService s, IRepository sub, + ISettingsService lidarrSettings) + : base(identity, service, r, um, mem, s, sub) + { + _lidarrApi = lidarrApi; + _lidarrSettings = lidarrSettings; + Mapper = mapper; + Logger = logger; + } + + private readonly ILidarrApi _lidarrApi; + private IMapper Mapper { get; } + private ILogger Logger { get; } + private readonly ISettingsService _lidarrSettings; + + /// + /// Searches the specified album. + /// + /// The search. + /// + public async Task> SearchAlbum(string search) + { + var settings = await GetSettings(); + var result = await _lidarrApi.AlbumLookup(search, settings.ApiKey, settings.FullUri); + var vm = new List(); + foreach (var r in result) + { + vm.Add(await MapIntoAlbumVm(r, settings)); + } + + return vm; + } + + /// + /// Searches the specified artist + /// + /// The search. + /// + public async Task> SearchArtist(string search) + { + var settings = await GetSettings(); + var result = await _lidarrApi.ArtistLookup(search, settings.ApiKey, settings.FullUri); + + var vm = new List(); + foreach (var r in result) + { + vm.Add(await MapIntoArtistVm(r)); + } + + return vm; + } + + /// + /// Returns all albums by the specified artist + /// + /// + /// + public async Task> GetArtistAlbums(string foreignArtistId) + { + var settings = await GetSettings(); + var result = await _lidarrApi.GetAlbumsByArtist(foreignArtistId); + // We do not want any Singles (This will include EP's) + var albumsOnly = + result.Albums.Where(x => !x.Type.Equals("Single", StringComparison.InvariantCultureIgnoreCase)); + var vm = new List(); + foreach (var album in albumsOnly) + { + vm.Add(await MapIntoAlbumVm(album, result.Id, result.ArtistName, settings)); + } + return vm; + } + + /// + /// Returns the artist that produced the album + /// + /// + /// + public async Task GetAlbumArtist(string foreignArtistId) + { + var settings = await GetSettings(); + return await _lidarrApi.GetArtistByForeignId(foreignArtistId, settings.ApiKey, settings.FullUri); + } + + public async Task GetArtist(int artistId) + { + var settings = await GetSettings(); + return await _lidarrApi.GetArtist(artistId, settings.ApiKey, settings.FullUri); + } + + private async Task MapIntoArtistVm(ArtistLookup a) + { + var vm = new SearchArtistViewModel + { + ArtistName = a.artistName, + ArtistType = a.artistType, + Banner = a.images?.FirstOrDefault(x => x.coverType.Equals("banner"))?.url, + Logo = a.images?.FirstOrDefault(x => x.coverType.Equals("logo"))?.url, + CleanName = a.cleanName, + Disambiguation = a.disambiguation, + ForignArtistId = a.foreignArtistId, + Links = a.links, + Overview = a.overview, + }; + + var poster = a.images?.FirstOrDefault(x => x.coverType.Equals("poaster")); + if (poster == null) + { + vm.Poster = a.remotePoster; + } + + + await Rules.StartSpecificRules(vm, SpecificRules.LidarrArtist); + + return vm; + } + + private async Task MapIntoAlbumVm(AlbumLookup a, LidarrSettings settings) + { + var vm = new SearchAlbumViewModel + { + ForeignAlbumId = a.foreignAlbumId, + Monitored = a.monitored, + Rating = a.ratings?.value ?? 0m, + ReleaseDate = a.releaseDate, + Title = a.title, + Disk = a.images?.FirstOrDefault(x => x.coverType.Equals("disc"))?.url + }; + if (a.artistId > 0) + { + //TODO THEY HAVE FIXED THIS IN DEV + // The JSON is different for some stupid reason + // Need to lookup the artist now and all the images -.-" + var artist = await _lidarrApi.GetArtist(a.artistId, settings.ApiKey, settings.FullUri); + vm.ArtistName = artist.artistName; + vm.ForeignArtistId = artist.foreignArtistId; + } + else + { + vm.ForeignArtistId = a.artist?.foreignArtistId; + vm.ArtistName = a.artist?.artistName; + } + + vm.Cover = a.images?.FirstOrDefault(x => x.coverType.Equals("cover"))?.url; + if (vm.Cover.IsNullOrEmpty()) + { + vm.Cover = a.remoteCover; + } + + await Rules.StartSpecificRules(vm, SpecificRules.LidarrAlbum); + + await RunSearchRules(vm); + + return vm; + } + + private async Task MapIntoAlbumVm(Album a, string artistId, string artistName, LidarrSettings settings) + { + var fullAlbum = await _lidarrApi.GetAlbumByForeignId(a.Id, settings.ApiKey, settings.FullUri); + var vm = new SearchAlbumViewModel + { + ForeignAlbumId = a.Id, + Monitored = fullAlbum.monitored, + Rating = fullAlbum.ratings?.value ?? 0m, + ReleaseDate = fullAlbum.releaseDate, + Title = a.Title, + Disk = fullAlbum.images?.FirstOrDefault(x => x.coverType.Equals("disc"))?.url, + ForeignArtistId = artistId, + ArtistName = artistName, + Cover = fullAlbum.images?.FirstOrDefault(x => x.coverType.Equals("cover"))?.url + }; + + if (vm.Cover.IsNullOrEmpty()) + { + vm.Cover = fullAlbum.remoteCover; + } + + await Rules.StartSpecificRules(vm, SpecificRules.LidarrAlbum); + + await RunSearchRules(vm); + + return vm; + } + + private LidarrSettings _settings; + private async Task GetSettings() + { + return _settings ?? (_settings = await _lidarrSettings.GetSettingsAsync()); + } + } +} \ No newline at end of file diff --git a/src/Ombi.Core/Helpers/NotificationHelper.cs b/src/Ombi.Core/Helpers/NotificationHelper.cs index 9bd890c21..1615b24f7 100644 --- a/src/Ombi.Core/Helpers/NotificationHelper.cs +++ b/src/Ombi.Core/Helpers/NotificationHelper.cs @@ -40,6 +40,18 @@ namespace Ombi.Core BackgroundJob.Enqueue(() => NotificationService.Publish(notificationModel)); } + public void NewRequest(AlbumRequest model) + { + var notificationModel = new NotificationOptions + { + RequestId = model.Id, + DateTime = DateTime.Now, + NotificationType = NotificationType.NewRequest, + RequestType = model.RequestType + }; + BackgroundJob.Enqueue(() => NotificationService.Publish(notificationModel)); + } + public void Notify(MovieRequests model, NotificationType type) { @@ -66,5 +78,19 @@ namespace Ombi.Core }; BackgroundJob.Enqueue(() => NotificationService.Publish(notificationModel)); } + + public void Notify(AlbumRequest model, NotificationType type) + { + var notificationModel = new NotificationOptions + { + RequestId = model.Id, + DateTime = DateTime.Now, + NotificationType = type, + RequestType = model.RequestType, + Recipient = model.RequestedUser?.Email ?? string.Empty + }; + + BackgroundJob.Enqueue(() => NotificationService.Publish(notificationModel)); + } } } \ No newline at end of file diff --git a/src/Ombi.Core/Models/Requests/IRequestServiceMain.cs b/src/Ombi.Core/Models/Requests/IRequestServiceMain.cs index 8a269054f..0e68a38e9 100644 --- a/src/Ombi.Core/Models/Requests/IRequestServiceMain.cs +++ b/src/Ombi.Core/Models/Requests/IRequestServiceMain.cs @@ -7,5 +7,6 @@ namespace Ombi.Core.Models.Requests { IMovieRequestRepository MovieRequestService { get; } ITvRequestRepository TvRequestService { get; } + IMusicRequestRepository MusicRequestRepository { get; } } } \ No newline at end of file diff --git a/src/Ombi.Core/Models/Requests/MusicArtistRequestViewModel.cs b/src/Ombi.Core/Models/Requests/MusicArtistRequestViewModel.cs new file mode 100644 index 000000000..4c3426c9e --- /dev/null +++ b/src/Ombi.Core/Models/Requests/MusicArtistRequestViewModel.cs @@ -0,0 +1,7 @@ +namespace Ombi.Core.Models.Requests +{ + public class MusicAlbumRequestViewModel + { + public string ForeignAlbumId { get; set; } + } +} \ No newline at end of file diff --git a/src/Ombi.Core/Models/Requests/RequestService.cs b/src/Ombi.Core/Models/Requests/RequestService.cs index 049666440..6f7431baa 100644 --- a/src/Ombi.Core/Models/Requests/RequestService.cs +++ b/src/Ombi.Core/Models/Requests/RequestService.cs @@ -5,13 +5,15 @@ namespace Ombi.Core.Models.Requests { public class RequestService : IRequestServiceMain { - public RequestService(ITvRequestRepository tv, IMovieRequestRepository movie) + public RequestService(ITvRequestRepository tv, IMovieRequestRepository movie, IMusicRequestRepository music) { TvRequestService = tv; MovieRequestService = movie; + MusicRequestRepository = music; } public ITvRequestRepository TvRequestService { get; } + public IMusicRequestRepository MusicRequestRepository { get; } public IMovieRequestRepository MovieRequestService { get; } } } \ No newline at end of file diff --git a/src/Ombi.Core/Models/Search/SearchAlbumViewModel.cs b/src/Ombi.Core/Models/Search/SearchAlbumViewModel.cs new file mode 100644 index 000000000..a494a3cb5 --- /dev/null +++ b/src/Ombi.Core/Models/Search/SearchAlbumViewModel.cs @@ -0,0 +1,23 @@ +using System; +using Ombi.Store.Entities; + +namespace Ombi.Core.Models.Search +{ + public class SearchAlbumViewModel : SearchViewModel + { + public string Title { get; set; } + public string ForeignAlbumId { get; set; } + public bool Monitored { get; set; } + public string AlbumType { get; set; } + public decimal Rating { get; set; } + public DateTime ReleaseDate { get; set; } + public string ArtistName { get; set; } + public string ForeignArtistId { get; set; } + public string Cover { get; set; } + public string Disk { get; set; } + public decimal PercentOfTracks { get; set; } + public override RequestType Type => RequestType.Album; + public bool PartiallyAvailable => PercentOfTracks != 100 && PercentOfTracks > 0; + public bool FullyAvailable => PercentOfTracks == 100; + } +} \ No newline at end of file diff --git a/src/Ombi.Core/Models/Search/SearchArtistViewModel.cs b/src/Ombi.Core/Models/Search/SearchArtistViewModel.cs new file mode 100644 index 000000000..b736df529 --- /dev/null +++ b/src/Ombi.Core/Models/Search/SearchArtistViewModel.cs @@ -0,0 +1,19 @@ +using Ombi.Api.Lidarr.Models; + +namespace Ombi.Core.Models.Search +{ + public class SearchArtistViewModel + { + public string ArtistName { get; set; } + public string ForignArtistId { get; set; } + public string Overview { get; set; } + public string Disambiguation { get; set; } + public string Banner { get; set; } + public string Poster { get; set; } + public string Logo { get; set; } + public bool Monitored { get; set; } + public string ArtistType { get; set; } + public string CleanName { get; set; } + public Link[] Links { get; set; } // Couldn't be bothered to map it + } +} \ No newline at end of file diff --git a/src/Ombi.Core/Ombi.Core.csproj b/src/Ombi.Core/Ombi.Core.csproj index 104db24fc..c2aeb1fd0 100644 --- a/src/Ombi.Core/Ombi.Core.csproj +++ b/src/Ombi.Core/Ombi.Core.csproj @@ -14,7 +14,6 @@ - @@ -22,6 +21,7 @@ + diff --git a/src/Ombi.Core/Rule/Interfaces/SpecificRules.cs b/src/Ombi.Core/Rule/Interfaces/SpecificRules.cs index 522ba8a95..d432f87be 100644 --- a/src/Ombi.Core/Rule/Interfaces/SpecificRules.cs +++ b/src/Ombi.Core/Rule/Interfaces/SpecificRules.cs @@ -3,5 +3,7 @@ public enum SpecificRules { CanSendNotification, + LidarrArtist, + LidarrAlbum, } } \ No newline at end of file diff --git a/src/Ombi.Core/Rule/Rules/Request/AutoApproveRule.cs b/src/Ombi.Core/Rule/Rules/Request/AutoApproveRule.cs index 7eecd62f2..a55868db8 100644 --- a/src/Ombi.Core/Rule/Rules/Request/AutoApproveRule.cs +++ b/src/Ombi.Core/Rule/Rules/Request/AutoApproveRule.cs @@ -29,6 +29,8 @@ namespace Ombi.Core.Rule.Rules.Request obj.Approved = true; if (obj.RequestType == RequestType.TvShow && User.IsInRole(OmbiRoles.AutoApproveTv)) obj.Approved = true; + if (obj.RequestType == RequestType.Album && User.IsInRole(OmbiRoles.AutoApproveMusic)) + obj.Approved = true; return Task.FromResult(Success()); // We don't really care, we just don't set the obj to approve } } diff --git a/src/Ombi.Core/Rule/Rules/Request/CanRequestRule.cs b/src/Ombi.Core/Rule/Rules/Request/CanRequestRule.cs index e9729ec35..1cdf03955 100644 --- a/src/Ombi.Core/Rule/Rules/Request/CanRequestRule.cs +++ b/src/Ombi.Core/Rule/Rules/Request/CanRequestRule.cs @@ -23,13 +23,23 @@ namespace Ombi.Core.Rule.Rules if (obj.RequestType == RequestType.Movie) { - if (User.IsInRole(OmbiRoles.RequestMovie)) + if (User.IsInRole(OmbiRoles.RequestMovie) || User.IsInRole(OmbiRoles.AutoApproveMovie)) return Task.FromResult(Success()); return Task.FromResult(Fail("You do not have permissions to Request a Movie")); } - if (User.IsInRole(OmbiRoles.RequestTv)) - return Task.FromResult(Success()); + if (obj.RequestType == RequestType.TvShow) + { + if (User.IsInRole(OmbiRoles.RequestTv) || User.IsInRole(OmbiRoles.AutoApproveTv)) + return Task.FromResult(Success()); + } + + if (obj.RequestType == RequestType.Album) + { + if (User.IsInRole(OmbiRoles.RequestMusic) || User.IsInRole(OmbiRoles.AutoApproveMusic)) + return Task.FromResult(Success()); + } + return Task.FromResult(Fail("You do not have permissions to Request a TV Show")); } } diff --git a/src/Ombi.Core/Rule/Rules/Request/RequestLimitRule.cs b/src/Ombi.Core/Rule/Rules/Request/RequestLimitRule.cs index a19ac1df8..53971bb3e 100644 --- a/src/Ombi.Core/Rule/Rules/Request/RequestLimitRule.cs +++ b/src/Ombi.Core/Rule/Rules/Request/RequestLimitRule.cs @@ -54,6 +54,7 @@ namespace Ombi.Core.Rule.Rules.Request var movieLimit = user.MovieRequestLimit; var episodeLimit = user.EpisodeRequestLimit; + var musicLimit = user.MusicRequestLimit; var requestLog = _requestLog.GetAll().Where(x => x.UserId == obj.RequestedUserId); if (obj.RequestType == RequestType.Movie) @@ -71,7 +72,7 @@ namespace Ombi.Core.Rule.Rules.Request return Fail("You have exceeded your Movie request quota!"); } } - else + else if (obj.RequestType == RequestType.TvShow) { if (episodeLimit <= 0) return Success(); @@ -94,8 +95,22 @@ namespace Ombi.Core.Rule.Rules.Request { return Fail("You have exceeded your Episode request quota!"); } + } else if (obj.RequestType == RequestType.Album) + { + if (musicLimit <= 0) + return Success(); + + var albumLogs = requestLog.Where(x => x.RequestType == RequestType.Album); + + // Count how many requests in the past 7 days + var count = await albumLogs.CountAsync(x => x.RequestDate >= DateTime.UtcNow.AddDays(-7)); + count += 1; // Since we are including this request + if (count > musicLimit) + { + return Fail("You have exceeded your Album request quota!"); + } } - return Success(); + return Success(); } } } diff --git a/src/Ombi.Core/Rule/Rules/Search/ExistingRule.cs b/src/Ombi.Core/Rule/Rules/Search/ExistingRule.cs index 6ca0b966b..965fcdfaf 100644 --- a/src/Ombi.Core/Rule/Rules/Search/ExistingRule.cs +++ b/src/Ombi.Core/Rule/Rules/Search/ExistingRule.cs @@ -11,20 +11,22 @@ namespace Ombi.Core.Rule.Rules.Search { public class ExistingRule : BaseSearchRule, IRules { - public ExistingRule(IMovieRequestRepository movie, ITvRequestRepository tv) + public ExistingRule(IMovieRequestRepository movie, ITvRequestRepository tv, IMusicRequestRepository music) { Movie = movie; Tv = tv; + Music = music; } private IMovieRequestRepository Movie { get; } + private IMusicRequestRepository Music { get; } private ITvRequestRepository Tv { get; } - public Task Execute(SearchViewModel obj) + public async Task Execute(SearchViewModel obj) { if (obj.Type == RequestType.Movie) { - var movieRequests = Movie.GetRequest(obj.Id); + var movieRequests = await Movie.GetRequestAsync(obj.Id); if (movieRequests != null) // Do we already have a request for this? { @@ -33,11 +35,11 @@ namespace Ombi.Core.Rule.Rules.Search obj.Approved = movieRequests.Approved; obj.Available = movieRequests.Available; - return Task.FromResult(Success()); + return Success(); } - return Task.FromResult(Success()); + return Success(); } - else + if (obj.Type == RequestType.TvShow) { //var tvRequests = Tv.GetRequest(obj.Id); //if (tvRequests != null) // Do we already have a request for this? @@ -50,7 +52,7 @@ namespace Ombi.Core.Rule.Rules.Search // return Task.FromResult(Success()); //} - var request = (SearchTvShowViewModel) obj; + var request = (SearchTvShowViewModel)obj; var tvRequests = Tv.GetRequest(obj.Id); if (tvRequests != null) // Do we already have a request for this? { @@ -94,8 +96,24 @@ namespace Ombi.Core.Rule.Rules.Search request.PartlyAvailable = true; } - return Task.FromResult(Success()); + return Success(); } + if (obj.Type == RequestType.Album) + { + var album = (SearchAlbumViewModel) obj; + var albumRequest = await Music.GetRequestAsync(album.ForeignAlbumId); + if (albumRequest != null) // Do we already have a request for this? + { + obj.Requested = true; + obj.RequestId = albumRequest.Id; + obj.Approved = albumRequest.Approved; + obj.Available = albumRequest.Available; + + return Success(); + } + return Success(); + } + return Success(); } } } \ No newline at end of file diff --git a/src/Ombi.Core/Rule/Rules/Search/LidarrAlbumCacheRule.cs b/src/Ombi.Core/Rule/Rules/Search/LidarrAlbumCacheRule.cs new file mode 100644 index 000000000..97a27d47f --- /dev/null +++ b/src/Ombi.Core/Rule/Rules/Search/LidarrAlbumCacheRule.cs @@ -0,0 +1,36 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using Ombi.Core.Models.Search; +using Ombi.Core.Rule.Interfaces; +using Ombi.Store.Entities; +using Ombi.Store.Repository; + +namespace Ombi.Core.Rule.Rules.Search +{ + public class LidarrAlbumCacheRule : SpecificRule, ISpecificRule + { + public LidarrAlbumCacheRule(IRepository db) + { + _db = db; + } + + private readonly IRepository _db; + + public Task Execute(object objec) + { + var obj = (SearchAlbumViewModel) objec; + // Check if it's in Lidarr + var result = _db.GetAll().FirstOrDefault(x => x.ForeignAlbumId.Equals(obj.ForeignAlbumId, StringComparison.InvariantCultureIgnoreCase)); + if (result != null) + { + obj.PercentOfTracks = result.PercentOfTracks; + obj.Monitored = true; // It's in Lidarr so it's monitored + } + + return Task.FromResult(Success()); + } + + public override SpecificRules Rule => SpecificRules.LidarrAlbum; + } +} \ No newline at end of file diff --git a/src/Ombi.Core/Rule/Rules/Search/LidarrArtistCacheRule.cs b/src/Ombi.Core/Rule/Rules/Search/LidarrArtistCacheRule.cs new file mode 100644 index 000000000..db472a951 --- /dev/null +++ b/src/Ombi.Core/Rule/Rules/Search/LidarrArtistCacheRule.cs @@ -0,0 +1,35 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using Ombi.Core.Models.Search; +using Ombi.Core.Rule.Interfaces; +using Ombi.Store.Entities; +using Ombi.Store.Repository; + +namespace Ombi.Core.Rule.Rules.Search +{ + public class LidarrArtistCacheRule : SpecificRule, ISpecificRule + { + public LidarrArtistCacheRule(IRepository db) + { + _db = db; + } + + private readonly IRepository _db; + + public Task Execute(object objec) + { + var obj = (SearchArtistViewModel) objec; + // Check if it's in Lidarr + var result = _db.GetAll().FirstOrDefault(x => x.ForeignArtistId.Equals(obj.ForignArtistId, StringComparison.InvariantCultureIgnoreCase)); + if (result != null) + { + obj.Monitored = true; // It's in Lidarr so it's monitored + } + + return Task.FromResult(Success()); + } + + public override SpecificRules Rule => SpecificRules.LidarrArtist; + } +} \ No newline at end of file diff --git a/src/Ombi.Core/Rule/Rules/Specific/SendNotificationRule.cs b/src/Ombi.Core/Rule/Rules/Specific/SendNotificationRule.cs index 50ac607cb..3f9e2f159 100644 --- a/src/Ombi.Core/Rule/Rules/Specific/SendNotificationRule.cs +++ b/src/Ombi.Core/Rule/Rules/Specific/SendNotificationRule.cs @@ -42,6 +42,13 @@ namespace Ombi.Core.Rule.Rules.Specific sendNotification = !await UserManager.IsInRoleAsync(requestedUser, OmbiRoles.AutoApproveTv); } } + else if (req.RequestType == RequestType.Album) + { + if (settings.DoNotSendNotificationsForAutoApprove) + { + sendNotification = !await UserManager.IsInRoleAsync(requestedUser, OmbiRoles.AutoApproveMusic); + } + } if (await UserManager.IsInRoleAsync(requestedUser, OmbiRoles.Admin)) { diff --git a/src/Ombi.Core/Senders/IMusicSender.cs b/src/Ombi.Core/Senders/IMusicSender.cs new file mode 100644 index 000000000..abeec5c29 --- /dev/null +++ b/src/Ombi.Core/Senders/IMusicSender.cs @@ -0,0 +1,10 @@ +using System.Threading.Tasks; +using Ombi.Store.Entities.Requests; + +namespace Ombi.Core.Senders +{ + public interface IMusicSender + { + Task Send(AlbumRequest model); + } +} \ No newline at end of file diff --git a/src/Ombi.Core/Senders/INotificationHelper.cs b/src/Ombi.Core/Senders/INotificationHelper.cs index efc45020c..4ba47d761 100644 --- a/src/Ombi.Core/Senders/INotificationHelper.cs +++ b/src/Ombi.Core/Senders/INotificationHelper.cs @@ -8,7 +8,9 @@ namespace Ombi.Core { void NewRequest(FullBaseRequest model); void NewRequest(ChildRequests model); + void NewRequest(AlbumRequest model); void Notify(MovieRequests model, NotificationType type); void Notify(ChildRequests model, NotificationType type); + void Notify(AlbumRequest model, NotificationType type); } } \ No newline at end of file diff --git a/src/Ombi.Core/Senders/MusicSender.cs b/src/Ombi.Core/Senders/MusicSender.cs new file mode 100644 index 000000000..5e1e44126 --- /dev/null +++ b/src/Ombi.Core/Senders/MusicSender.cs @@ -0,0 +1,126 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using Ombi.Api.Lidarr; +using Ombi.Api.Lidarr.Models; +using Ombi.Api.Radarr; +using Ombi.Core.Settings; +using Ombi.Helpers; +using Ombi.Settings.Settings.Models.External; +using Ombi.Store.Entities.Requests; +using Serilog; + +namespace Ombi.Core.Senders +{ + public class MusicSender : IMusicSender + { + public MusicSender(ISettingsService lidarr, ILidarrApi lidarrApi) + { + _lidarrSettings = lidarr; + _lidarrApi = lidarrApi; + } + + private readonly ISettingsService _lidarrSettings; + private readonly ILidarrApi _lidarrApi; + + public async Task Send(AlbumRequest model) + { + var settings = await _lidarrSettings.GetSettingsAsync(); + if (settings.Enabled) + { + return await SendToLidarr(model, settings); + } + + return new SenderResult { Success = false, Sent = false, Message = "Lidarr is not enabled" }; + } + + private async Task SendToLidarr(AlbumRequest model, LidarrSettings settings) + { + var qualityToUse = int.Parse(settings.DefaultQualityProfile); + //if (model.QualityOverride > 0) + //{ + // qualityToUse = model.QualityOverride; + //} + + var rootFolderPath = /*model.RootPathOverride <= 0 ?*/ settings.DefaultRootPath /*: await RadarrRootPath(model.RootPathOverride, settings)*/; + + // Need to get the artist + var artist = await _lidarrApi.GetArtistByForeignId(model.ForeignArtistId, settings.ApiKey, settings.FullUri); + + if (artist == null || artist.id <= 0) + { + // Create artist + var newArtist = new ArtistAdd + { + foreignArtistId = model.ForeignArtistId, + addOptions = new Addoptions + { + monitored = true, + searchForMissingAlbums = false, + selectedOption = 6, // None + AlbumsToMonitor = new[] {model.ForeignAlbumId} + }, + added = DateTime.Now, + monitored = true, + albumFolder = settings.AlbumFolder, + artistName = model.ArtistName, + cleanName = model.ArtistName.ToLowerInvariant().RemoveSpaces(), + images = new Image[] { }, + languageProfileId = settings.LanguageProfileId, + links = new Link[] {}, + metadataProfileId = settings.MetadataProfileId, + qualityProfileId = qualityToUse, + rootFolderPath = rootFolderPath, + }; + + var result = await _lidarrApi.AddArtist(newArtist, settings.ApiKey, settings.FullUri); + if (result != null && result.id > 0) + { + // Setup the albums + return new SenderResult { Message = "Album has been requested!", Sent = true, Success = true }; + } + } + else + { + await SetupAlbum(model, artist, settings); + } + + return new SenderResult { Success = false, Sent = false, Message = "Album is already monitored" }; + } + + private async Task SetupAlbum(AlbumRequest model, ArtistResult artist, LidarrSettings settings) + { + // Get the album id + var albums = await _lidarrApi.GetAllAlbumsByArtistId(artist.id, settings.ApiKey, settings.FullUri); + var album = albums.FirstOrDefault(x => + x.foreignAlbumId.Equals(model.ForeignAlbumId, StringComparison.InvariantCultureIgnoreCase)); + var maxRetryCount = 10; // 5 seconds + var currentRetry = 0; + while (!albums.Any() || album == null) + { + if (currentRetry >= maxRetryCount) + { + break; + } + currentRetry++; + await Task.Delay(500); + albums = await _lidarrApi.GetAllAlbumsByArtistId(artist.id, settings.ApiKey, settings.FullUri); + album = albums.FirstOrDefault(x => + x.foreignAlbumId.Equals(model.ForeignAlbumId, StringComparison.InvariantCultureIgnoreCase)); + } + // Get the album we want. + + if (album == null) + { + return new SenderResult { Message = "Could not find album in Lidarr", Sent = false, Success = false }; + } + + var result = await _lidarrApi.MontiorAlbum(album.id, settings.ApiKey, settings.FullUri); + if (result.monitored) + { + return new SenderResult {Message = "Album has been requested!", Sent = true, Success = true}; + } + return new SenderResult { Message = "Could not set album to monitored", Sent = false, Success = false }; + } + } +} \ No newline at end of file diff --git a/src/Ombi.DependencyInjection/IocExtensions.cs b/src/Ombi.DependencyInjection/IocExtensions.cs index 2644fa9c7..334b94675 100644 --- a/src/Ombi.DependencyInjection/IocExtensions.cs +++ b/src/Ombi.DependencyInjection/IocExtensions.cs @@ -32,6 +32,7 @@ using Ombi.Api.CouchPotato; using Ombi.Api.DogNzb; using Ombi.Api.FanartTv; using Ombi.Api.Github; +using Ombi.Api.Lidarr; using Ombi.Api.Mattermost; using Ombi.Api.Notifications; using Ombi.Api.Pushbullet; @@ -53,9 +54,11 @@ using PlexContentCacher = Ombi.Schedule.Jobs.Plex; using Ombi.Api.Telegram; using Ombi.Core.Authentication; using Ombi.Core.Processor; +using Ombi.Schedule.Jobs.Lidarr; using Ombi.Schedule.Jobs.Plex.Interfaces; using Ombi.Schedule.Jobs.SickRage; using Ombi.Schedule.Processor; +using Ombi.Store.Entities; namespace Ombi.DependencyInjection { @@ -82,7 +85,10 @@ namespace Ombi.DependencyInjection services.AddTransient(); services.AddTransient(); services.AddTransient(); + services.AddTransient(); + services.AddTransient(); services.AddTransient(); + services.AddTransient(); services.AddTransient(); services.AddTransient(); } @@ -117,6 +123,7 @@ namespace Ombi.DependencyInjection services.AddTransient(); services.AddTransient(); services.AddTransient(); + services.AddTransient(); } public static void RegisterStore(this IServiceCollection services) { @@ -131,6 +138,7 @@ namespace Ombi.DependencyInjection services.AddTransient(); services.AddTransient(); + services.AddTransient(); services.AddTransient(); services.AddTransient(); services.AddTransient(); @@ -180,6 +188,9 @@ namespace Ombi.DependencyInjection services.AddTransient(); services.AddTransient(); services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); } } } diff --git a/src/Ombi.DependencyInjection/Ombi.DependencyInjection.csproj b/src/Ombi.DependencyInjection/Ombi.DependencyInjection.csproj index 2e7f984a7..6fe083fe3 100644 --- a/src/Ombi.DependencyInjection/Ombi.DependencyInjection.csproj +++ b/src/Ombi.DependencyInjection/Ombi.DependencyInjection.csproj @@ -21,6 +21,7 @@ + diff --git a/src/Ombi.Helpers/CacheKeys.cs b/src/Ombi.Helpers/CacheKeys.cs index e6c482f7b..f7a40d321 100644 --- a/src/Ombi.Helpers/CacheKeys.cs +++ b/src/Ombi.Helpers/CacheKeys.cs @@ -18,6 +18,8 @@ namespace Ombi.Helpers public const string NowPlayingMovies = nameof(NowPlayingMovies); public const string RadarrRootProfiles = nameof(RadarrRootProfiles); public const string RadarrQualityProfiles = nameof(RadarrQualityProfiles); + public const string LidarrRootFolders = nameof(LidarrRootFolders); + public const string LidarrQualityProfiles = nameof(LidarrQualityProfiles); public const string FanartTv = nameof(FanartTv); } } diff --git a/src/Ombi.Helpers/LoggingEvents.cs b/src/Ombi.Helpers/LoggingEvents.cs index 39c309102..40ec3fd2b 100644 --- a/src/Ombi.Helpers/LoggingEvents.cs +++ b/src/Ombi.Helpers/LoggingEvents.cs @@ -20,6 +20,7 @@ namespace Ombi.Helpers public static EventId CouchPotatoCacher => new EventId(2007); public static EventId PlexContentCacher => new EventId(2008); public static EventId SickRageCacher => new EventId(2009); + public static EventId LidarrArtistCache => new EventId(2010); public static EventId MovieSender => new EventId(3000); diff --git a/src/Ombi.Helpers/OmbiRoles.cs b/src/Ombi.Helpers/OmbiRoles.cs index 5b62a2bae..1d584d57f 100644 --- a/src/Ombi.Helpers/OmbiRoles.cs +++ b/src/Ombi.Helpers/OmbiRoles.cs @@ -7,9 +7,11 @@ public const string Admin = nameof(Admin); public const string AutoApproveMovie = nameof(AutoApproveMovie); public const string AutoApproveTv = nameof(AutoApproveTv); + public const string AutoApproveMusic = nameof(AutoApproveMusic); public const string PowerUser = nameof(PowerUser); public const string RequestTv = nameof(RequestTv); public const string RequestMovie = nameof(RequestMovie); + public const string RequestMusic = nameof(RequestMusic); public const string Disabled = nameof(Disabled); public const string ReceivesNewsletter = nameof(ReceivesNewsletter); } diff --git a/src/Ombi.Helpers/StringHelper.cs b/src/Ombi.Helpers/StringHelper.cs index 2dad81015..c198301fc 100644 --- a/src/Ombi.Helpers/StringHelper.cs +++ b/src/Ombi.Helpers/StringHelper.cs @@ -76,6 +76,10 @@ namespace Ombi.Helpers return -1; } + public static string RemoveSpaces(this string str) + { + return str.Replace(" ", ""); + } public static string StripCharacters(this string str, params char[] chars) { return string.Concat(str.Where(c => !chars.Contains(c))); diff --git a/src/Ombi.Notifications/Agents/DiscordNotification.cs b/src/Ombi.Notifications/Agents/DiscordNotification.cs index 66280ef70..d788b471c 100644 --- a/src/Ombi.Notifications/Agents/DiscordNotification.cs +++ b/src/Ombi.Notifications/Agents/DiscordNotification.cs @@ -20,8 +20,8 @@ namespace Ombi.Notifications.Agents { public DiscordNotification(IDiscordApi api, ISettingsService sn, ILogger log, INotificationTemplatesRepository r, - IMovieRequestRepository m, ITvRequestRepository t, ISettingsService s, IRepository sub) - : base(sn, r, m, t,s,log, sub) + IMovieRequestRepository m, ITvRequestRepository t, ISettingsService s, IRepository sub, IMusicRequestRepository music) + : base(sn, r, m, t, s, log, sub, music) { Api = api; Logger = log; @@ -130,12 +130,18 @@ namespace Ombi.Notifications.Agents title = MovieRequest.Title; image = MovieRequest.PosterPath; } - else + else if (model.RequestType == RequestType.TvShow) { user = TvRequest.RequestedUser.UserAlias; title = TvRequest.ParentRequest.Title; image = TvRequest.ParentRequest.PosterPath; } + else if (model.RequestType == RequestType.Album) + { + user = AlbumRequest.RequestedUser.UserAlias; + title = AlbumRequest.Title; + image = AlbumRequest.Cover; + } var message = $"Hello! The user '{user}' has requested {title} but it could not be added. This has been added into the requests queue and will keep retrying"; var notification = new NotificationMessage { diff --git a/src/Ombi.Notifications/Agents/EmailNotification.cs b/src/Ombi.Notifications/Agents/EmailNotification.cs index 53046ade0..3ab045e87 100644 --- a/src/Ombi.Notifications/Agents/EmailNotification.cs +++ b/src/Ombi.Notifications/Agents/EmailNotification.cs @@ -22,7 +22,7 @@ namespace Ombi.Notifications.Agents public class EmailNotification : BaseNotification, IEmailNotification { public EmailNotification(ISettingsService settings, INotificationTemplatesRepository r, IMovieRequestRepository m, ITvRequestRepository t, IEmailProvider prov, ISettingsService c, - ILogger log, UserManager um, IRepository sub) : base(settings, r, m, t, c, log, sub) + ILogger log, UserManager um, IRepository sub, IMusicRequestRepository music) : base(settings, r, m, t, c, log, sub, music) { EmailProvider = prov; Logger = log; diff --git a/src/Ombi.Notifications/Agents/MattermostNotification.cs b/src/Ombi.Notifications/Agents/MattermostNotification.cs index 8199d8bfe..9e8a34e3b 100644 --- a/src/Ombi.Notifications/Agents/MattermostNotification.cs +++ b/src/Ombi.Notifications/Agents/MattermostNotification.cs @@ -21,7 +21,7 @@ namespace Ombi.Notifications.Agents public class MattermostNotification : BaseNotification, IMattermostNotification { public MattermostNotification(IMattermostApi api, ISettingsService sn, ILogger log, INotificationTemplatesRepository r, IMovieRequestRepository m, ITvRequestRepository t, - ISettingsService s, IRepository sub) : base(sn, r, m, t,s,log, sub) + ISettingsService s, IRepository sub, IMusicRequestRepository music) : base(sn, r, m, t, s, log, sub, music) { Api = api; Logger = log; diff --git a/src/Ombi.Notifications/Agents/MobileNotification.cs b/src/Ombi.Notifications/Agents/MobileNotification.cs index 8559c043d..c521b99a4 100644 --- a/src/Ombi.Notifications/Agents/MobileNotification.cs +++ b/src/Ombi.Notifications/Agents/MobileNotification.cs @@ -22,7 +22,7 @@ namespace Ombi.Notifications.Agents { public MobileNotification(IOneSignalApi api, ISettingsService sn, ILogger log, INotificationTemplatesRepository r, IMovieRequestRepository m, ITvRequestRepository t, ISettingsService s, IRepository notification, - UserManager um, IRepository sub) : base(sn, r, m, t, s,log, sub) + UserManager um, IRepository sub, IMusicRequestRepository music) : base(sn, r, m, t, s, log, sub, music) { _api = api; _logger = log; diff --git a/src/Ombi.Notifications/Agents/PushbulletNotification.cs b/src/Ombi.Notifications/Agents/PushbulletNotification.cs index 24aa8cd22..0e488bf79 100644 --- a/src/Ombi.Notifications/Agents/PushbulletNotification.cs +++ b/src/Ombi.Notifications/Agents/PushbulletNotification.cs @@ -17,7 +17,7 @@ namespace Ombi.Notifications.Agents public class PushbulletNotification : BaseNotification, IPushbulletNotification { public PushbulletNotification(IPushbulletApi api, ISettingsService sn, ILogger log, INotificationTemplatesRepository r, IMovieRequestRepository m, ITvRequestRepository t, - ISettingsService s, IRepository sub) : base(sn, r, m, t,s,log, sub) + ISettingsService s, IRepository sub, IMusicRequestRepository music) : base(sn, r, m, t, s, log, sub, music) { Api = api; Logger = log; diff --git a/src/Ombi.Notifications/Agents/PushoverNotification.cs b/src/Ombi.Notifications/Agents/PushoverNotification.cs index 555bff1fa..b5d743cc2 100644 --- a/src/Ombi.Notifications/Agents/PushoverNotification.cs +++ b/src/Ombi.Notifications/Agents/PushoverNotification.cs @@ -18,7 +18,7 @@ namespace Ombi.Notifications.Agents public class PushoverNotification : BaseNotification, IPushoverNotification { public PushoverNotification(IPushoverApi api, ISettingsService sn, ILogger log, INotificationTemplatesRepository r, IMovieRequestRepository m, ITvRequestRepository t, - ISettingsService s, IRepository sub) : base(sn, r, m, t, s, log, sub) + ISettingsService s, IRepository sub, IMusicRequestRepository music) : base(sn, r, m, t, s, log, sub, music) { Api = api; Logger = log; diff --git a/src/Ombi.Notifications/Agents/SlackNotification.cs b/src/Ombi.Notifications/Agents/SlackNotification.cs index 894758591..6c04f5ea6 100644 --- a/src/Ombi.Notifications/Agents/SlackNotification.cs +++ b/src/Ombi.Notifications/Agents/SlackNotification.cs @@ -18,7 +18,7 @@ namespace Ombi.Notifications.Agents public class SlackNotification : BaseNotification, ISlackNotification { public SlackNotification(ISlackApi api, ISettingsService sn, ILogger log, INotificationTemplatesRepository r, IMovieRequestRepository m, ITvRequestRepository t, - ISettingsService s, IRepository sub) : base(sn, r, m, t, s, log, sub) + ISettingsService s, IRepository sub, IMusicRequestRepository music) : base(sn, r, m, t, s, log, sub, music) { Api = api; Logger = log; diff --git a/src/Ombi.Notifications/Agents/TelegramNotification.cs b/src/Ombi.Notifications/Agents/TelegramNotification.cs index 827b0b590..7bcda7c7f 100644 --- a/src/Ombi.Notifications/Agents/TelegramNotification.cs +++ b/src/Ombi.Notifications/Agents/TelegramNotification.cs @@ -19,7 +19,7 @@ namespace Ombi.Notifications.Agents public TelegramNotification(ITelegramApi api, ISettingsService sn, ILogger log, INotificationTemplatesRepository r, IMovieRequestRepository m, ITvRequestRepository t, ISettingsService s - , IRepository sub) : base(sn, r, m, t,s,log, sub) + , IRepository sub, IMusicRequestRepository music) : base(sn, r, m, t,s,log, sub, music) { Api = api; Logger = log; diff --git a/src/Ombi.Notifications/BaseNotification.cs b/src/Ombi.Notifications/BaseNotification.cs index 507b8059b..287f86455 100644 --- a/src/Ombi.Notifications/BaseNotification.cs +++ b/src/Ombi.Notifications/BaseNotification.cs @@ -19,7 +19,7 @@ namespace Ombi.Notifications.Interfaces public abstract class BaseNotification : INotification where T : Settings.Settings.Models.Settings, new() { protected BaseNotification(ISettingsService settings, INotificationTemplatesRepository templateRepo, IMovieRequestRepository movie, ITvRequestRepository tv, - ISettingsService customization, ILogger> log, IRepository sub) + ISettingsService customization, ILogger> log, IRepository sub, IMusicRequestRepository album) { Settings = settings; TemplateRepository = templateRepo; @@ -30,12 +30,14 @@ namespace Ombi.Notifications.Interfaces CustomizationSettings.ClearCache(); RequestSubscription = sub; _log = log; + AlbumRepository = album; } protected ISettingsService Settings { get; } protected INotificationTemplatesRepository TemplateRepository { get; } protected IMovieRequestRepository MovieRepository { get; } protected ITvRequestRepository TvRepository { get; } + protected IMusicRequestRepository AlbumRepository { get; } protected CustomizationSettings Customization { get; set; } protected IRepository RequestSubscription { get; set; } private ISettingsService CustomizationSettings { get; } @@ -43,6 +45,7 @@ namespace Ombi.Notifications.Interfaces protected ChildRequests TvRequest { get; set; } + protected AlbumRequest AlbumRequest { get; set; } protected MovieRequests MovieRequest { get; set; } protected IQueryable SubsribedUsers { get; private set; } @@ -130,10 +133,14 @@ namespace Ombi.Notifications.Interfaces { MovieRequest = await MovieRepository.GetWithUser().FirstOrDefaultAsync(x => x.Id == requestId); } - else + else if (type == RequestType.TvShow) { TvRequest = await TvRepository.GetChild().FirstOrDefaultAsync(x => x.Id == requestId); } + else if (type == RequestType.Album) + { + AlbumRequest = await AlbumRepository.GetWithUser().FirstOrDefaultAsync(x => x.Id == requestId); + } } private async Task GetConfiguration() @@ -181,11 +188,16 @@ namespace Ombi.Notifications.Interfaces curlys.Setup(model, MovieRequest, Customization); } - else + else if (model.RequestType == RequestType.TvShow) { _log.LogDebug("Notification options: {@model}, Req: {@TvRequest}, Settings: {@Customization}", model, TvRequest, Customization); curlys.Setup(model, TvRequest, Customization); } + else if (model.RequestType == RequestType.Album) + { + _log.LogDebug("Notification options: {@model}, Req: {@AlbumRequest}, Settings: {@Customization}", model, AlbumRequest, Customization); + curlys.Setup(model, AlbumRequest, Customization); + } var parsed = resolver.ParseMessage(template, curlys); return parsed; diff --git a/src/Ombi.Notifications/NotificationMessageCurlys.cs b/src/Ombi.Notifications/NotificationMessageCurlys.cs index 497c49c86..710f64619 100644 --- a/src/Ombi.Notifications/NotificationMessageCurlys.cs +++ b/src/Ombi.Notifications/NotificationMessageCurlys.cs @@ -47,14 +47,48 @@ namespace Ombi.Notifications if (req?.RequestType == RequestType.Movie) { - PosterImage = string.Format((req?.PosterPath ?? string.Empty).StartsWith("/", StringComparison.InvariantCultureIgnoreCase) + PosterImage = string.Format((req?.PosterPath ?? string.Empty).StartsWith("/", StringComparison.InvariantCultureIgnoreCase) ? "https://image.tmdb.org/t/p/w300{0}" : "https://image.tmdb.org/t/p/w300/{0}", req?.PosterPath); } else { PosterImage = req?.PosterPath; } - + + AdditionalInformation = opts?.AdditionalInformation ?? string.Empty; + } + + public void Setup(NotificationOptions opts, AlbumRequest req, CustomizationSettings s) + { + LoadIssues(opts); + string title; + if (req == null) + { + opts.Substitutes.TryGetValue("Title", out title); + } + else + { + title = req?.Title; + } + ApplicationUrl = (s?.ApplicationUrl.HasValue() ?? false) ? s.ApplicationUrl : string.Empty; + ApplicationName = string.IsNullOrEmpty(s?.ApplicationName) ? "Ombi" : s?.ApplicationName; + RequestedUser = req?.RequestedUser?.UserName; + if (UserName.IsNullOrEmpty()) + { + // Can be set if it's an issue + UserName = req?.RequestedUser?.UserName; + } + + Alias = (req?.RequestedUser?.Alias.HasValue() ?? false) ? req?.RequestedUser?.Alias : req?.RequestedUser?.UserName; + Title = title; + RequestedDate = req?.RequestedDate.ToString("D"); + if (Type.IsNullOrEmpty()) + { + Type = req?.RequestType.Humanize(); + } + Year = req?.ReleaseDate.Year.ToString(); + PosterImage = (req?.Cover.HasValue() ?? false) ? req.Cover : req?.Disk ?? string.Empty; + AdditionalInformation = opts?.AdditionalInformation ?? string.Empty; } diff --git a/src/Ombi.Schedule/JobSetup.cs b/src/Ombi.Schedule/JobSetup.cs index e24e4bd87..f82c96fba 100644 --- a/src/Ombi.Schedule/JobSetup.cs +++ b/src/Ombi.Schedule/JobSetup.cs @@ -4,6 +4,7 @@ using Ombi.Core.Settings; using Ombi.Schedule.Jobs; using Ombi.Schedule.Jobs.Couchpotato; using Ombi.Schedule.Jobs.Emby; +using Ombi.Schedule.Jobs.Lidarr; using Ombi.Schedule.Jobs.Ombi; using Ombi.Schedule.Jobs.Plex; using Ombi.Schedule.Jobs.Radarr; @@ -19,7 +20,7 @@ namespace Ombi.Schedule IOmbiAutomaticUpdater updater, IEmbyContentSync embySync, IPlexUserImporter userImporter, IEmbyUserImporter embyUserImporter, ISonarrSync cache, ICouchPotatoSync cpCache, ISettingsService jobsettings, ISickRageSync srSync, IRefreshMetadata refresh, - INewsletterJob newsletter, IPlexRecentlyAddedSync recentlyAddedPlex) + INewsletterJob newsletter, IPlexRecentlyAddedSync recentlyAddedPlex, ILidarrArtistSync artist) { _plexContentSync = plexContentSync; _radarrSync = radarrSync; @@ -34,6 +35,7 @@ namespace Ombi.Schedule _refreshMetadata = refresh; _newsletter = newsletter; _plexRecentlyAddedSync = recentlyAddedPlex; + _lidarrArtistSync = artist; } private readonly IPlexContentSync _plexContentSync; @@ -49,6 +51,7 @@ namespace Ombi.Schedule private readonly ISettingsService _jobSettings; private readonly IRefreshMetadata _refreshMetadata; private readonly INewsletterJob _newsletter; + private readonly ILidarrArtistSync _lidarrArtistSync; public void Setup() { @@ -62,6 +65,7 @@ namespace Ombi.Schedule RecurringJob.AddOrUpdate(() => _cpCache.Start(), JobSettingsHelper.CouchPotato(s)); RecurringJob.AddOrUpdate(() => _srSync.Start(), JobSettingsHelper.SickRageSync(s)); RecurringJob.AddOrUpdate(() => _refreshMetadata.Start(), JobSettingsHelper.RefreshMetadata(s)); + RecurringJob.AddOrUpdate(() => _lidarrArtistSync.CacheContent(), JobSettingsHelper.LidarrArtistSync(s)); RecurringJob.AddOrUpdate(() => _updater.Update(null), JobSettingsHelper.Updater(s)); diff --git a/src/Ombi.Schedule/Jobs/Lidarr/ILidarrAlbumSync.cs b/src/Ombi.Schedule/Jobs/Lidarr/ILidarrAlbumSync.cs new file mode 100644 index 000000000..56444b105 --- /dev/null +++ b/src/Ombi.Schedule/Jobs/Lidarr/ILidarrAlbumSync.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using Ombi.Store.Entities; + +namespace Ombi.Schedule.Jobs.Lidarr +{ + public interface ILidarrAlbumSync + { + Task CacheContent(); + void Dispose(); + Task> GetCachedContent(); + } +} \ No newline at end of file diff --git a/src/Ombi.Schedule/Jobs/Lidarr/ILidarrArtistSync.cs b/src/Ombi.Schedule/Jobs/Lidarr/ILidarrArtistSync.cs new file mode 100644 index 000000000..1d3424756 --- /dev/null +++ b/src/Ombi.Schedule/Jobs/Lidarr/ILidarrArtistSync.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using Ombi.Store.Entities; + +namespace Ombi.Schedule.Jobs.Lidarr +{ + public interface ILidarrArtistSync + { + Task CacheContent(); + void Dispose(); + Task> GetCachedContent(); + } +} \ No newline at end of file diff --git a/src/Ombi.Schedule/Jobs/Lidarr/ILidarrAvailabilityChecker.cs b/src/Ombi.Schedule/Jobs/Lidarr/ILidarrAvailabilityChecker.cs new file mode 100644 index 000000000..f0c679229 --- /dev/null +++ b/src/Ombi.Schedule/Jobs/Lidarr/ILidarrAvailabilityChecker.cs @@ -0,0 +1,9 @@ +using System.Threading.Tasks; + +namespace Ombi.Schedule.Jobs.Lidarr +{ + public interface ILidarrAvailabilityChecker + { + Task Start(); + } +} \ No newline at end of file diff --git a/src/Ombi.Schedule/Jobs/Lidarr/LidarrAlbumSync.cs b/src/Ombi.Schedule/Jobs/Lidarr/LidarrAlbumSync.cs new file mode 100644 index 000000000..2e32b6478 --- /dev/null +++ b/src/Ombi.Schedule/Jobs/Lidarr/LidarrAlbumSync.cs @@ -0,0 +1,119 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Hangfire; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Internal; +using Microsoft.Extensions.Logging; +using Ombi.Api.Lidarr; +using Ombi.Api.Radarr; +using Ombi.Core.Settings; +using Ombi.Helpers; +using Ombi.Settings.Settings.Models.External; +using Ombi.Store.Context; +using Ombi.Store.Entities; +using Serilog; +using ILogger = Microsoft.Extensions.Logging.ILogger; + +namespace Ombi.Schedule.Jobs.Lidarr +{ + public class LidarrAlbumSync : ILidarrAlbumSync + { + public LidarrAlbumSync(ISettingsService lidarr, ILidarrApi lidarrApi, ILogger log, IOmbiContext ctx, + IBackgroundJobClient job, ILidarrAvailabilityChecker availability) + { + _lidarrSettings = lidarr; + _lidarrApi = lidarrApi; + _logger = log; + _ctx = ctx; + _job = job; + _availability = availability; + _lidarrSettings.ClearCache(); + } + + private readonly ISettingsService _lidarrSettings; + private readonly ILidarrApi _lidarrApi; + private readonly ILogger _logger; + private readonly IOmbiContext _ctx; + private readonly IBackgroundJobClient _job; + private readonly ILidarrAvailabilityChecker _availability; + + public async Task CacheContent() + { + try + { + var settings = await _lidarrSettings.GetSettingsAsync(); + if (settings.Enabled) + { + try + { + var albums = await _lidarrApi.GetAllAlbums(settings.ApiKey, settings.FullUri); + if (albums != null && albums.Any()) + { + // Let's remove the old cached data + await _ctx.Database.ExecuteSqlCommandAsync("DELETE FROM LidarrAlbumCache"); + + var albumCache = new List(); + foreach (var a in albums) + { + if (a.id > 0) + { + albumCache.Add(new LidarrAlbumCache + { + ArtistId = a.artistId, + ForeignAlbumId = a.foreignAlbumId, + ReleaseDate = a.releaseDate, + TrackCount = a.currentRelease.trackCount, + Monitored = a.monitored, + Title = a.title, + PercentOfTracks = a.statistics?.percentOfEpisodes ?? 0m, + AddedAt = DateTime.Now, + }); + } + } + await _ctx.LidarrAlbumCache.AddRangeAsync(albumCache); + + await _ctx.SaveChangesAsync(); + } + } + catch (System.Exception ex) + { + _logger.LogError(LoggingEvents.Cacher, ex, "Failed caching queued items from Lidarr Album"); + } + + _job.Enqueue(() => _availability.Start()); + } + } + catch (Exception) + { + _logger.LogInformation(LoggingEvents.LidarrArtistCache, "Lidarr is not setup, cannot cache Album"); + } + } + + public async Task> GetCachedContent() + { + return await _ctx.LidarrAlbumCache.ToListAsync(); + } + + private bool _disposed; + protected virtual void Dispose(bool disposing) + { + if (_disposed) + return; + + if (disposing) + { + _ctx?.Dispose(); + _lidarrSettings?.Dispose(); + } + _disposed = true; + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + } +} \ No newline at end of file diff --git a/src/Ombi.Schedule/Jobs/Lidarr/LidarrArtistSync.cs b/src/Ombi.Schedule/Jobs/Lidarr/LidarrArtistSync.cs new file mode 100644 index 000000000..ac6264e3d --- /dev/null +++ b/src/Ombi.Schedule/Jobs/Lidarr/LidarrArtistSync.cs @@ -0,0 +1,115 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Hangfire; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Internal; +using Microsoft.Extensions.Logging; +using Ombi.Api.Lidarr; +using Ombi.Api.Radarr; +using Ombi.Core.Settings; +using Ombi.Helpers; +using Ombi.Settings.Settings.Models.External; +using Ombi.Store.Context; +using Ombi.Store.Entities; +using Serilog; +using ILogger = Microsoft.Extensions.Logging.ILogger; + +namespace Ombi.Schedule.Jobs.Lidarr +{ + public class LidarrArtistSync : ILidarrArtistSync + { + public LidarrArtistSync(ISettingsService lidarr, ILidarrApi lidarrApi, ILogger log, IOmbiContext ctx, + IBackgroundJobClient background, ILidarrAlbumSync album) + { + _lidarrSettings = lidarr; + _lidarrApi = lidarrApi; + _logger = log; + _ctx = ctx; + _job = background; + _albumSync = album; + _lidarrSettings.ClearCache(); + } + + private readonly ISettingsService _lidarrSettings; + private readonly ILidarrApi _lidarrApi; + private readonly ILogger _logger; + private readonly IOmbiContext _ctx; + private readonly IBackgroundJobClient _job; + private readonly ILidarrAlbumSync _albumSync; + + public async Task CacheContent() + { + try + { + var settings = await _lidarrSettings.GetSettingsAsync(); + if (settings.Enabled) + { + try + { + var artists = await _lidarrApi.GetArtists(settings.ApiKey, settings.FullUri); + if (artists != null && artists.Any()) + { + // Let's remove the old cached data + await _ctx.Database.ExecuteSqlCommandAsync("DELETE FROM LidarrArtistCache"); + + var artistCache = new List(); + foreach (var a in artists) + { + if (a.id > 0) + { + artistCache.Add(new LidarrArtistCache + { + ArtistId = a.id, + ArtistName = a.artistName, + ForeignArtistId = a.foreignArtistId, + Monitored = a.monitored + }); + } + } + await _ctx.LidarrArtistCache.AddRangeAsync(artistCache); + + await _ctx.SaveChangesAsync(); + } + } + catch (Exception ex) + { + _logger.LogError(LoggingEvents.Cacher, ex, "Failed caching queued items from Lidarr"); + } + + _job.Enqueue(() => _albumSync.CacheContent()); + } + } + catch (Exception) + { + _logger.LogInformation(LoggingEvents.LidarrArtistCache, "Lidarr is not setup, cannot cache Artist"); + } + } + + public async Task> GetCachedContent() + { + return await _ctx.LidarrArtistCache.ToListAsync(); + } + + private bool _disposed; + protected virtual void Dispose(bool disposing) + { + if (_disposed) + return; + + if (disposing) + { + _ctx?.Dispose(); + _lidarrSettings?.Dispose(); + } + _disposed = true; + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + } +} \ No newline at end of file diff --git a/src/Ombi.Schedule/Jobs/Lidarr/LidarrAvailabilityChecker.cs b/src/Ombi.Schedule/Jobs/Lidarr/LidarrAvailabilityChecker.cs new file mode 100644 index 000000000..d5ba14a6d --- /dev/null +++ b/src/Ombi.Schedule/Jobs/Lidarr/LidarrAvailabilityChecker.cs @@ -0,0 +1,73 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Hangfire; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using Ombi.Core.Notifications; +using Ombi.Helpers; +using Ombi.Notifications.Models; +using Ombi.Store.Entities; +using Ombi.Store.Entities.Requests; +using Ombi.Store.Repository; +using Ombi.Store.Repository.Requests; + +namespace Ombi.Schedule.Jobs.Lidarr +{ + public class LidarrAvailabilityChecker : ILidarrAvailabilityChecker + { + public LidarrAvailabilityChecker(IMusicRequestRepository requests, IRepository albums, ILogger log, + IBackgroundJobClient job, INotificationService notification) + { + _cachedAlbums = albums; + _requestRepository = requests; + _logger = log; + _job = job; + _notificationService = notification; + } + + private readonly IMusicRequestRepository _requestRepository; + private readonly IRepository _cachedAlbums; + private readonly ILogger _logger; + private readonly IBackgroundJobClient _job; + private readonly INotificationService _notificationService; + + public async Task Start() + { + var allAlbumRequests = _requestRepository.GetAll().Include(x => x.RequestedUser).Where(x => !x.Available); + var albumsToUpdate = new List(); + foreach (var request in allAlbumRequests) + { + // Check if we have it cached + var cachedAlbum = await _cachedAlbums.FirstOrDefaultAsync(x => x.ForeignAlbumId.Equals(request.ForeignAlbumId)); + if (cachedAlbum != null) + { + if (cachedAlbum.Monitored && cachedAlbum.FullyAvailable) + { + request.Available = true; + request.MarkedAsAvailable = DateTime.Now; + albumsToUpdate.Add(request); + } + } + } + + foreach (var albumRequest in albumsToUpdate) + { + await _requestRepository.Update(albumRequest); + var recipient = albumRequest.RequestedUser.Email.HasValue() ? albumRequest.RequestedUser.Email : string.Empty; + + _logger.LogDebug("AlbumId: {0}, RequestUser: {1}", albumRequest.Id, recipient); + + _job.Enqueue(() => _notificationService.Publish(new NotificationOptions + { + DateTime = DateTime.Now, + NotificationType = NotificationType.RequestAvailable, + RequestId = albumRequest.Id, + RequestType = RequestType.Album, + Recipient = recipient, + })); + } + } + } +} \ No newline at end of file diff --git a/src/Ombi.Schedule/Jobs/Ombi/HtmlTemplateGenerator.cs b/src/Ombi.Schedule/Jobs/Ombi/HtmlTemplateGenerator.cs index 51e920b15..09b7d9858 100644 --- a/src/Ombi.Schedule/Jobs/Ombi/HtmlTemplateGenerator.cs +++ b/src/Ombi.Schedule/Jobs/Ombi/HtmlTemplateGenerator.cs @@ -1,4 +1,5 @@ using System.Text; +using Ombi.Helpers; namespace Ombi.Schedule.Jobs.Ombi { @@ -22,13 +23,20 @@ namespace Ombi.Schedule.Jobs.Ombi protected virtual void AddMediaServerUrl(StringBuilder sb, string mediaurl, string url) { - sb.Append(""); - sb.Append(""); - sb.AppendFormat("", mediaurl); - sb.AppendFormat("", url); - sb.Append(""); - sb.Append(""); - sb.Append(""); + if (url.HasValue()) + { + sb.Append(""); + sb.Append( + ""); + sb.AppendFormat("", mediaurl); + sb.AppendFormat( + "", + url); + sb.Append(""); + sb.Append(""); + sb.Append(""); + } + sb.Append(""); sb.Append(""); } @@ -44,9 +52,9 @@ namespace Ombi.Schedule.Jobs.Ombi { sb.Append(""); sb.Append(""); - sb.AppendFormat("", url); + if(url.HasValue()) sb.AppendFormat("", url); sb.AppendFormat("

{0}

", title); - sb.Append("
"); + if (url.HasValue()) sb.Append(""); sb.Append(""); sb.Append(""); } diff --git a/src/Ombi.Schedule/Jobs/Ombi/NewsletterJob.cs b/src/Ombi.Schedule/Jobs/Ombi/NewsletterJob.cs index 6e89d167e..f152f6b4b 100644 --- a/src/Ombi.Schedule/Jobs/Ombi/NewsletterJob.cs +++ b/src/Ombi.Schedule/Jobs/Ombi/NewsletterJob.cs @@ -9,6 +9,8 @@ using MailKit; using Microsoft.AspNetCore.Identity; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; +using Ombi.Api.Lidarr; +using Ombi.Api.Lidarr.Models; using Ombi.Api.TheMovieDb; using Ombi.Api.TheMovieDb.Models; using Ombi.Api.TvMaze; @@ -18,6 +20,7 @@ using Ombi.Notifications; using Ombi.Notifications.Models; using Ombi.Notifications.Templates; using Ombi.Settings.Settings.Models; +using Ombi.Settings.Settings.Models.External; using Ombi.Settings.Settings.Models.Notifications; using Ombi.Store.Entities; using Ombi.Store.Repository; @@ -29,7 +32,8 @@ namespace Ombi.Schedule.Jobs.Ombi public NewsletterJob(IPlexContentRepository plex, IEmbyContentRepository emby, IRepository addedLog, IMovieDbApi movieApi, ITvMazeApi tvApi, IEmailProvider email, ISettingsService custom, ISettingsService emailSettings, INotificationTemplatesRepository templateRepo, - UserManager um, ISettingsService newsletter, ILogger log) + UserManager um, ISettingsService newsletter, ILogger log, + ILidarrApi lidarrApi, IRepository albumCache, ISettingsService lidarrSettings) { _plex = plex; _emby = emby; @@ -46,6 +50,10 @@ namespace Ombi.Schedule.Jobs.Ombi _customizationSettings.ClearCache(); _newsletterSettings.ClearCache(); _log = log; + _lidarrApi = lidarrApi; + _lidarrAlbumRepository = albumCache; + _lidarrSettings = lidarrSettings; + _lidarrSettings.ClearCache(); } private readonly IPlexContentRepository _plex; @@ -60,6 +68,9 @@ namespace Ombi.Schedule.Jobs.Ombi private readonly ISettingsService _newsletterSettings; private readonly UserManager _userManager; private readonly ILogger _log; + private readonly ILidarrApi _lidarrApi; + private readonly IRepository _lidarrAlbumRepository; + private readonly ISettingsService _lidarrSettings; public async Task Start(NewsletterSettings settings, bool test) { @@ -87,21 +98,26 @@ namespace Ombi.Schedule.Jobs.Ombi // Get the Content var plexContent = _plex.GetAll().Include(x => x.Episodes).AsNoTracking(); var embyContent = _emby.GetAll().Include(x => x.Episodes).AsNoTracking(); + var lidarrContent = _lidarrAlbumRepository.GetAll().AsNoTracking(); var addedLog = _recentlyAddedLog.GetAll(); var addedPlexMovieLogIds = addedLog.Where(x => x.Type == RecentlyAddedType.Plex && x.ContentType == ContentType.Parent).Select(x => x.ContentId); var addedEmbyMoviesLogIds = addedLog.Where(x => x.Type == RecentlyAddedType.Emby && x.ContentType == ContentType.Parent).Select(x => x.ContentId); + var addedAlbumLogIds = addedLog.Where(x => x.Type == RecentlyAddedType.Lidarr && x.ContentType == ContentType.Album).Select(x => x.AlbumId); var addedPlexEpisodesLogIds = addedLog.Where(x => x.Type == RecentlyAddedType.Plex && x.ContentType == ContentType.Episode); var addedEmbyEpisodesLogIds = addedLog.Where(x => x.Type == RecentlyAddedType.Emby && x.ContentType == ContentType.Episode); + // Filter out the ones that we haven't sent yet var plexContentMoviesToSend = plexContent.Where(x => x.Type == PlexMediaTypeEntity.Movie && x.HasTheMovieDb && !addedPlexMovieLogIds.Contains(StringHelper.IntParseLinq(x.TheMovieDbId))); var embyContentMoviesToSend = embyContent.Where(x => x.Type == EmbyMediaType.Movie && x.HasTheMovieDb && !addedEmbyMoviesLogIds.Contains(StringHelper.IntParseLinq(x.TheMovieDbId))); + var lidarrContentAlbumsToSend = lidarrContent.Where(x => !addedAlbumLogIds.Contains(x.ForeignAlbumId)).ToHashSet(); _log.LogInformation("Plex Movies to send: {0}", plexContentMoviesToSend.Count()); _log.LogInformation("Emby Movies to send: {0}", embyContentMoviesToSend.Count()); + _log.LogInformation("Albums to send: {0}", lidarrContentAlbumsToSend.Count()); var plexEpisodesToSend = FilterPlexEpisodes(_plex.GetAllEpisodes().Include(x => x.Series).Where(x => x.Series.HasTvDb).AsNoTracking(), addedPlexEpisodesLogIds); @@ -117,11 +133,12 @@ namespace Ombi.Schedule.Jobs.Ombi var embym = embyContent.Where(x => x.Type == EmbyMediaType.Movie ).OrderByDescending(x => x.AddedAt).Take(10); var plext = _plex.GetAllEpisodes().Include(x => x.Series).OrderByDescending(x => x.Series.AddedAt).Take(10).ToHashSet(); var embyt = _emby.GetAllEpisodes().Include(x => x.Series).OrderByDescending(x => x.AddedAt).Take(10).ToHashSet(); - body = await BuildHtml(plexm, embym, plext, embyt, settings); + var lidarr = lidarrContent.OrderByDescending(x => x.AddedAt).Take(10).ToHashSet(); + body = await BuildHtml(plexm, embym, plext, embyt, lidarr, settings); } else { - body = await BuildHtml(plexContentMoviesToSend, embyContentMoviesToSend, plexEpisodesToSend, embyEpisodesToSend, settings); + body = await BuildHtml(plexContentMoviesToSend, embyContentMoviesToSend, plexEpisodesToSend, embyEpisodesToSend, lidarrContentAlbumsToSend, settings); if (body.IsNullOrEmpty()) { return; @@ -298,7 +315,8 @@ namespace Ombi.Schedule.Jobs.Ombi return resolver.ParseMessage(template, curlys); } - private async Task BuildHtml(IQueryable plexContentToSend, IQueryable embyContentToSend, HashSet plexEpisodes, HashSet embyEp, NewsletterSettings settings) + private async Task BuildHtml(IQueryable plexContentToSend, IQueryable embyContentToSend, + HashSet plexEpisodes, HashSet embyEp, HashSet albums, NewsletterSettings settings) { var sb = new StringBuilder(); @@ -340,6 +358,24 @@ namespace Ombi.Schedule.Jobs.Ombi sb.Append(""); } + + if (albums.Any() && !settings.DisableMusic) + { + sb.Append("

New Albums



"); + sb.Append( + ""); + sb.Append(""); + sb.Append(""); + sb.Append(""); + sb.Append("
"); + sb.Append(""); + sb.Append(""); + await ProcessAlbums(albums, sb); + sb.Append(""); + sb.Append("
"); + sb.Append("
"); + } + return sb.ToString(); } @@ -382,6 +418,40 @@ namespace Ombi.Schedule.Jobs.Ombi } } } + private async Task ProcessAlbums(HashSet albumsToSend, StringBuilder sb) + { + var settings = await _lidarrSettings.GetSettingsAsync(); + int count = 0; + var ordered = albumsToSend.OrderByDescending(x => x.AddedAt); + foreach (var content in ordered) + { + var info = await _lidarrApi.GetAlbumByForeignId(content.ForeignAlbumId, settings.ApiKey, settings.FullUri); + if (info == null) + { + continue; + } + try + { + CreateAlbumHtmlContent(sb, info); + count += 1; + } + catch (Exception e) + { + _log.LogError(e, "Error when Processing Lidarr Album {0}", info.title); + } + finally + { + EndLoopHtml(sb); + } + + if (count == 2) + { + count = 0; + sb.Append(""); + sb.Append(""); + } + } + } private async Task ProcessEmbyMovies(IQueryable embyContent, StringBuilder sb) { @@ -467,6 +537,41 @@ namespace Ombi.Schedule.Jobs.Ombi } } + private void CreateAlbumHtmlContent(StringBuilder sb, AlbumLookup info) + { + var cover = info.images + .FirstOrDefault(x => x.coverType.Equals("cover", StringComparison.InvariantCultureIgnoreCase))?.url; + if (cover.IsNullOrEmpty()) + { + cover = info.remoteCover; + } + AddBackgroundInsideTable(sb, cover); + var disk = info.images + .FirstOrDefault(x => x.coverType.Equals("disc", StringComparison.InvariantCultureIgnoreCase))?.url; + if (disk.IsNullOrEmpty()) + { + disk = info.remoteCover; + } + AddPosterInsideTable(sb, disk); + + AddMediaServerUrl(sb, string.Empty, string.Empty); + AddInfoTable(sb); + + var releaseDate = $"({info.releaseDate.Year})"; + + AddTitle(sb, string.Empty, $"{info.title} {releaseDate}"); + + var summary = info.artist?.artistName ?? string.Empty; + if (summary.Length > 280) + { + summary = summary.Remove(280); + summary = summary + "...

"; + } + AddParagraph(sb, summary); + + AddGenres(sb, $"Type: {info.albumType}"); + } + private async Task ProcessPlexTv(HashSet plexContent, StringBuilder sb) { var series = new List(); diff --git a/src/Ombi.Schedule/Ombi.Schedule.csproj b/src/Ombi.Schedule/Ombi.Schedule.csproj index 47e599e80..06cc2bb49 100644 --- a/src/Ombi.Schedule/Ombi.Schedule.csproj +++ b/src/Ombi.Schedule/Ombi.Schedule.csproj @@ -27,6 +27,7 @@ + diff --git a/src/Ombi.Settings/Settings/Models/External/LidarrSettings.cs b/src/Ombi.Settings/Settings/Models/External/LidarrSettings.cs new file mode 100644 index 000000000..e0bdbdc43 --- /dev/null +++ b/src/Ombi.Settings/Settings/Models/External/LidarrSettings.cs @@ -0,0 +1,15 @@ +using Ombi.Core.Settings.Models.External; + +namespace Ombi.Settings.Settings.Models.External +{ + public class LidarrSettings : ExternalSettings + { + public bool Enabled { get; set; } + public string ApiKey { get; set; } + public string DefaultQualityProfile { get; set; } + public string DefaultRootPath { get; set; } + public bool AlbumFolder { get; set; } + public int LanguageProfileId { get; set; } + public int MetadataProfileId { get; set; } + } +} \ No newline at end of file diff --git a/src/Ombi.Settings/Settings/Models/JobSettings.cs b/src/Ombi.Settings/Settings/Models/JobSettings.cs index bb536a685..48c721e29 100644 --- a/src/Ombi.Settings/Settings/Models/JobSettings.cs +++ b/src/Ombi.Settings/Settings/Models/JobSettings.cs @@ -13,5 +13,6 @@ public string SickRageSync { get; set; } public string RefreshMetadata { get; set; } public string Newsletter { get; set; } + public string LidarrArtistSync { get; set; } } } \ No newline at end of file diff --git a/src/Ombi.Settings/Settings/Models/JobSettingsHelper.cs b/src/Ombi.Settings/Settings/Models/JobSettingsHelper.cs index a5afbeba7..0f8fec5fd 100644 --- a/src/Ombi.Settings/Settings/Models/JobSettingsHelper.cs +++ b/src/Ombi.Settings/Settings/Models/JobSettingsHelper.cs @@ -52,6 +52,10 @@ namespace Ombi.Settings.Settings.Models { return Get(s.RefreshMetadata, Cron.DayInterval(3)); } + public static string LidarrArtistSync(JobSettings s) + { + return Get(s.LidarrArtistSync, Cron.Hourly(40)); + } private static string Get(string settings, string defaultCron) { diff --git a/src/Ombi.Settings/Settings/Models/Notifications/NewsletterSettings.cs b/src/Ombi.Settings/Settings/Models/Notifications/NewsletterSettings.cs index e79f3182c..3f6416af5 100644 --- a/src/Ombi.Settings/Settings/Models/Notifications/NewsletterSettings.cs +++ b/src/Ombi.Settings/Settings/Models/Notifications/NewsletterSettings.cs @@ -6,6 +6,7 @@ namespace Ombi.Settings.Settings.Models.Notifications { public bool DisableTv { get; set; } public bool DisableMovies { get; set; } + public bool DisableMusic { get; set; } public bool Enabled { get; set; } public List ExternalEmails { get; set; } = new List(); } diff --git a/src/Ombi.Store/Context/IOmbiContext.cs b/src/Ombi.Store/Context/IOmbiContext.cs index 0c716c7c4..2c4d809b4 100644 --- a/src/Ombi.Store/Context/IOmbiContext.cs +++ b/src/Ombi.Store/Context/IOmbiContext.cs @@ -28,6 +28,7 @@ namespace Ombi.Store.Context void Seed(); DbSet Audit { get; set; } DbSet MovieRequests { get; set; } + DbSet AlbumRequests { get; set; } DbSet TvRequests { get; set; } DbSet ChildRequests { get; set; } DbSet Issues { get; set; } @@ -39,6 +40,8 @@ namespace Ombi.Store.Context EntityEntry Update(TEntity entity) where TEntity : class; DbSet CouchPotatoCache { get; set; } DbSet SickRageCache { get; set; } + DbSet LidarrArtistCache { get; set; } + DbSet LidarrAlbumCache { get; set; } DbSet SickRageEpisodeCache { get; set; } DbSet RequestLogs { get; set; } DbSet RecentlyAddedLogs { get; set; } diff --git a/src/Ombi.Store/Context/OmbiContext.cs b/src/Ombi.Store/Context/OmbiContext.cs index 8d6036877..e93cc89ba 100644 --- a/src/Ombi.Store/Context/OmbiContext.cs +++ b/src/Ombi.Store/Context/OmbiContext.cs @@ -31,6 +31,7 @@ namespace Ombi.Store.Context public DbSet EmbyEpisode { get; set; } public DbSet MovieRequests { get; set; } + public DbSet AlbumRequests { get; set; } public DbSet TvRequests { get; set; } public DbSet ChildRequests { get; set; } @@ -44,6 +45,8 @@ namespace Ombi.Store.Context public DbSet Audit { get; set; } public DbSet Tokens { get; set; } public DbSet SonarrCache { get; set; } + public DbSet LidarrArtistCache { get; set; } + public DbSet LidarrAlbumCache { get; set; } public DbSet SonarrEpisodeCache { get; set; } public DbSet SickRageCache { get; set; } public DbSet SickRageEpisodeCache { get; set; } diff --git a/src/Ombi.Store/Entities/LidarrAlbumCache.cs b/src/Ombi.Store/Entities/LidarrAlbumCache.cs new file mode 100644 index 000000000..03099face --- /dev/null +++ b/src/Ombi.Store/Entities/LidarrAlbumCache.cs @@ -0,0 +1,23 @@ +using System; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Ombi.Store.Entities +{ + [Table("LidarrAlbumCache")] + public class LidarrAlbumCache : Entity + { + public int ArtistId { get; set; } + public string ForeignAlbumId { get; set; } + public int TrackCount { get; set; } + public DateTime ReleaseDate { get; set; } + public bool Monitored { get; set; } + public string Title { get; set; } + public decimal PercentOfTracks { get; set; } + public DateTime AddedAt { get; set; } + + [NotMapped] + public bool PartiallyAvailable => PercentOfTracks != 100 && PercentOfTracks > 0; + [NotMapped] + public bool FullyAvailable => PercentOfTracks == 100; + } +} \ No newline at end of file diff --git a/src/Ombi.Store/Entities/LidarrArtistCache.cs b/src/Ombi.Store/Entities/LidarrArtistCache.cs new file mode 100644 index 000000000..dd78b4e2c --- /dev/null +++ b/src/Ombi.Store/Entities/LidarrArtistCache.cs @@ -0,0 +1,14 @@ +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Ombi.Store.Entities +{ + [Table("LidarrArtistCache")] + public class LidarrArtistCache : Entity + { + public int ArtistId { get; set; } + public string ArtistName { get; set; } + public string ForeignArtistId { get; set; } + public bool Monitored { get; set; } + } +} \ No newline at end of file diff --git a/src/Ombi.Store/Entities/OmbiUser.cs b/src/Ombi.Store/Entities/OmbiUser.cs index f67183982..9513df818 100644 --- a/src/Ombi.Store/Entities/OmbiUser.cs +++ b/src/Ombi.Store/Entities/OmbiUser.cs @@ -23,6 +23,7 @@ namespace Ombi.Store.Entities public int? MovieRequestLimit { get; set; } public int? EpisodeRequestLimit { get; set; } + public int? MusicRequestLimit { get; set; } public string UserAccessToken { get; set; } @@ -59,5 +60,6 @@ namespace Ombi.Store.Entities get => base.ConcurrencyStamp; set => base.ConcurrencyStamp = value; } + } } \ No newline at end of file diff --git a/src/Ombi.Store/Entities/RecentlyAddedLog.cs b/src/Ombi.Store/Entities/RecentlyAddedLog.cs index 1ef091149..782d89e3f 100644 --- a/src/Ombi.Store/Entities/RecentlyAddedLog.cs +++ b/src/Ombi.Store/Entities/RecentlyAddedLog.cs @@ -11,18 +11,21 @@ namespace Ombi.Store.Entities public int ContentId { get; set; } // This is dependant on the type, it's either TMDBID or TVDBID public int? EpisodeNumber { get; set; } public int? SeasonNumber { get; set; } + public string AlbumId { get; set; } public DateTime AddedAt { get; set; } } public enum RecentlyAddedType { Plex = 0, - Emby = 1 + Emby = 1, + Lidarr = 2 } public enum ContentType { Parent = 0, - Episode = 1 + Episode = 1, + Album = 2, } } \ No newline at end of file diff --git a/src/Ombi.Store/Entities/RequestType.cs b/src/Ombi.Store/Entities/RequestType.cs index 06cd6c069..151453bdd 100644 --- a/src/Ombi.Store/Entities/RequestType.cs +++ b/src/Ombi.Store/Entities/RequestType.cs @@ -7,6 +7,7 @@ namespace Ombi.Store.Entities public enum RequestType { TvShow = 0, - Movie = 1 + Movie = 1, + Album = 2, } } diff --git a/src/Ombi.Store/Entities/Requests/AlbumRequest.cs b/src/Ombi.Store/Entities/Requests/AlbumRequest.cs new file mode 100644 index 000000000..2735603c6 --- /dev/null +++ b/src/Ombi.Store/Entities/Requests/AlbumRequest.cs @@ -0,0 +1,21 @@ +using System; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Ombi.Store.Entities.Requests +{ + [Table("AlbumRequests")] + public class AlbumRequest : BaseRequest + { + public string ForeignAlbumId { get; set; } + public string ForeignArtistId { get; set; } + public string Disk { get; set; } + public string Cover { get; set; } + public decimal Rating { get; set; } + public DateTime ReleaseDate { get; set; } + public string ArtistName { get; set; } + [NotMapped] + public bool Subscribed { get; set; } + [NotMapped] + public bool ShowSubscribe { get; set; } + } +} \ No newline at end of file diff --git a/src/Ombi.Store/Migrations/20180824152254_MusicRequests.Designer.cs b/src/Ombi.Store/Migrations/20180824152254_MusicRequests.Designer.cs new file mode 100644 index 000000000..415563212 --- /dev/null +++ b/src/Ombi.Store/Migrations/20180824152254_MusicRequests.Designer.cs @@ -0,0 +1,1045 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Ombi.Store.Context; + +namespace Ombi.Store.Migrations +{ + [DbContext(typeof(OmbiContext))] + [Migration("20180824152254_MusicRequests")] + partial class MusicRequests + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.1.1-rtm-30846"); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken(); + + b.Property("Name") + .HasMaxLength(256); + + b.Property("NormalizedName") + .HasMaxLength(256); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasName("RoleNameIndex"); + + b.ToTable("AspNetRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ClaimType"); + + b.Property("ClaimValue"); + + b.Property("RoleId") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ClaimType"); + + b.Property("ClaimValue"); + + b.Property("UserId") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider"); + + b.Property("ProviderKey"); + + b.Property("ProviderDisplayName"); + + b.Property("UserId") + .IsRequired(); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId"); + + b.Property("RoleId"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId"); + + b.Property("LoginProvider"); + + b.Property("Name"); + + b.Property("Value"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.ApplicationConfiguration", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Type"); + + b.Property("Value"); + + b.HasKey("Id"); + + b.ToTable("ApplicationConfiguration"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Audit", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AuditArea"); + + b.Property("AuditType"); + + b.Property("DateTime"); + + b.Property("Description"); + + b.Property("User"); + + b.HasKey("Id"); + + b.ToTable("Audit"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.CouchPotatoCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("TheMovieDbId"); + + b.HasKey("Id"); + + b.ToTable("CouchPotatoCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.EmbyContent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("EmbyId") + .IsRequired(); + + b.Property("ImdbId"); + + b.Property("ProviderId"); + + b.Property("TheMovieDbId"); + + b.Property("Title"); + + b.Property("TvDbId"); + + b.Property("Type"); + + b.Property("Url"); + + b.HasKey("Id"); + + b.ToTable("EmbyContent"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.EmbyEpisode", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("EmbyId"); + + b.Property("EpisodeNumber"); + + b.Property("ImdbId"); + + b.Property("ParentId"); + + b.Property("ProviderId"); + + b.Property("SeasonNumber"); + + b.Property("TheMovieDbId"); + + b.Property("Title"); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.HasIndex("ParentId"); + + b.ToTable("EmbyEpisode"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.GlobalSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Content"); + + b.Property("SettingsName"); + + b.HasKey("Id"); + + b.ToTable("GlobalSettings"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.NotificationTemplates", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Agent"); + + b.Property("Enabled"); + + b.Property("Message"); + + b.Property("NotificationType"); + + b.Property("Subject"); + + b.HasKey("Id"); + + b.ToTable("NotificationTemplates"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.NotificationUserId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("PlayerId"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("NotificationUserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.OmbiUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AccessFailedCount"); + + b.Property("Alias"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken(); + + b.Property("Email") + .HasMaxLength(256); + + b.Property("EmailConfirmed"); + + b.Property("EmbyConnectUserId"); + + b.Property("EpisodeRequestLimit"); + + b.Property("LastLoggedIn"); + + b.Property("LockoutEnabled"); + + b.Property("LockoutEnd"); + + b.Property("MovieRequestLimit"); + + b.Property("MusicRequestLimit"); + + b.Property("NormalizedEmail") + .HasMaxLength(256); + + b.Property("NormalizedUserName") + .HasMaxLength(256); + + b.Property("PasswordHash"); + + b.Property("PhoneNumber"); + + b.Property("PhoneNumberConfirmed"); + + b.Property("ProviderUserId"); + + b.Property("SecurityStamp"); + + b.Property("TwoFactorEnabled"); + + b.Property("UserAccessToken"); + + b.Property("UserName") + .HasMaxLength(256); + + b.Property("UserType"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasName("UserNameIndex"); + + b.ToTable("AspNetUsers"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexEpisode", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("EpisodeNumber"); + + b.Property("GrandparentKey"); + + b.Property("Key"); + + b.Property("ParentKey"); + + b.Property("SeasonNumber"); + + b.Property("Title"); + + b.HasKey("Id"); + + b.HasIndex("GrandparentKey"); + + b.ToTable("PlexEpisode"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexSeasonsContent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ParentKey"); + + b.Property("PlexContentId"); + + b.Property("PlexServerContentId"); + + b.Property("SeasonKey"); + + b.Property("SeasonNumber"); + + b.HasKey("Id"); + + b.HasIndex("PlexServerContentId"); + + b.ToTable("PlexSeasonsContent"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexServerContent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("ImdbId"); + + b.Property("Key"); + + b.Property("Quality"); + + b.Property("ReleaseYear"); + + b.Property("TheMovieDbId"); + + b.Property("Title"); + + b.Property("TvDbId"); + + b.Property("Type"); + + b.Property("Url"); + + b.HasKey("Id"); + + b.ToTable("PlexServerContent"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.RadarrCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("HasFile"); + + b.Property("TheMovieDbId"); + + b.HasKey("Id"); + + b.ToTable("RadarrCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.RecentlyAddedLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("ContentId"); + + b.Property("ContentType"); + + b.Property("EpisodeNumber"); + + b.Property("SeasonNumber"); + + b.Property("Type"); + + b.HasKey("Id"); + + b.ToTable("RecentlyAddedLog"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.AlbumRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Approved"); + + b.Property("ArtistName"); + + b.Property("Available"); + + b.Property("Cover"); + + b.Property("Denied"); + + b.Property("DeniedReason"); + + b.Property("Disk"); + + b.Property("ForeignAlbumId"); + + b.Property("ForeignArtistId"); + + b.Property("MarkedAsApproved"); + + b.Property("MarkedAsAvailable"); + + b.Property("MarkedAsDenied"); + + b.Property("Rating"); + + b.Property("ReleaseDate"); + + b.Property("RequestType"); + + b.Property("RequestedDate"); + + b.Property("RequestedUserId"); + + b.Property("Title"); + + b.HasKey("Id"); + + b.HasIndex("RequestedUserId"); + + b.ToTable("AlbumRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.ChildRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Approved"); + + b.Property("Available"); + + b.Property("Denied"); + + b.Property("DeniedReason"); + + b.Property("IssueId"); + + b.Property("MarkedAsApproved"); + + b.Property("MarkedAsAvailable"); + + b.Property("MarkedAsDenied"); + + b.Property("ParentRequestId"); + + b.Property("RequestType"); + + b.Property("RequestedDate"); + + b.Property("RequestedUserId"); + + b.Property("SeriesType"); + + b.Property("Title"); + + b.HasKey("Id"); + + b.HasIndex("ParentRequestId"); + + b.HasIndex("RequestedUserId"); + + b.ToTable("ChildRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Value"); + + b.HasKey("Id"); + + b.ToTable("IssueCategory"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueComments", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Comment"); + + b.Property("Date"); + + b.Property("IssuesId"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("IssuesId"); + + b.HasIndex("UserId"); + + b.ToTable("IssueComments"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.Issues", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Description"); + + b.Property("IssueCategoryId"); + + b.Property("IssueId"); + + b.Property("ProviderId"); + + b.Property("RequestId"); + + b.Property("RequestType"); + + b.Property("ResovledDate"); + + b.Property("Status"); + + b.Property("Subject"); + + b.Property("Title"); + + b.Property("UserReportedId"); + + b.HasKey("Id"); + + b.HasIndex("IssueCategoryId"); + + b.HasIndex("IssueId"); + + b.HasIndex("UserReportedId"); + + b.ToTable("Issues"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.MovieRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Approved"); + + b.Property("Available"); + + b.Property("Background"); + + b.Property("Denied"); + + b.Property("DeniedReason"); + + b.Property("DigitalReleaseDate"); + + b.Property("ImdbId"); + + b.Property("IssueId"); + + b.Property("MarkedAsApproved"); + + b.Property("MarkedAsAvailable"); + + b.Property("MarkedAsDenied"); + + b.Property("Overview"); + + b.Property("PosterPath"); + + b.Property("QualityOverride"); + + b.Property("ReleaseDate"); + + b.Property("RequestType"); + + b.Property("RequestedDate"); + + b.Property("RequestedUserId"); + + b.Property("RootPathOverride"); + + b.Property("Status"); + + b.Property("TheMovieDbId"); + + b.Property("Title"); + + b.HasKey("Id"); + + b.HasIndex("RequestedUserId"); + + b.ToTable("MovieRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.RequestLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("EpisodeCount"); + + b.Property("RequestDate"); + + b.Property("RequestId"); + + b.Property("RequestType"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("RequestLog"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.TvRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Background"); + + b.Property("ImdbId"); + + b.Property("Overview"); + + b.Property("PosterPath"); + + b.Property("QualityOverride"); + + b.Property("ReleaseDate"); + + b.Property("RootFolder"); + + b.Property("Status"); + + b.Property("Title"); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.ToTable("TvRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.RequestSubscription", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("RequestId"); + + b.Property("RequestType"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("RequestSubscription"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.SickRageCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.ToTable("SickRageCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.SickRageEpisodeCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("EpisodeNumber"); + + b.Property("SeasonNumber"); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.ToTable("SickRageEpisodeCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.SonarrCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.ToTable("SonarrCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.SonarrEpisodeCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("EpisodeNumber"); + + b.Property("HasFile"); + + b.Property("SeasonNumber"); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.ToTable("SonarrEpisodeCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Tokens", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Token"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Tokens"); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.EpisodeRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AirDate"); + + b.Property("Approved"); + + b.Property("Available"); + + b.Property("EpisodeNumber"); + + b.Property("Requested"); + + b.Property("SeasonId"); + + b.Property("Title"); + + b.Property("Url"); + + b.HasKey("Id"); + + b.HasIndex("SeasonId"); + + b.ToTable("EpisodeRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.SeasonRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChildRequestId"); + + b.Property("SeasonNumber"); + + b.HasKey("Id"); + + b.HasIndex("ChildRequestId"); + + b.ToTable("SeasonRequests"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole") + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole") + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Ombi.Store.Entities.OmbiUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Ombi.Store.Entities.EmbyEpisode", b => + { + b.HasOne("Ombi.Store.Entities.EmbyContent", "Series") + .WithMany("Episodes") + .HasForeignKey("ParentId") + .HasPrincipalKey("EmbyId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.NotificationUserId", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany("NotificationUserIds") + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexEpisode", b => + { + b.HasOne("Ombi.Store.Entities.PlexServerContent", "Series") + .WithMany("Episodes") + .HasForeignKey("GrandparentKey") + .HasPrincipalKey("Key") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexSeasonsContent", b => + { + b.HasOne("Ombi.Store.Entities.PlexServerContent") + .WithMany("Seasons") + .HasForeignKey("PlexServerContentId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.AlbumRequest", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "RequestedUser") + .WithMany() + .HasForeignKey("RequestedUserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.ChildRequests", b => + { + b.HasOne("Ombi.Store.Entities.Requests.TvRequests", "ParentRequest") + .WithMany("ChildRequests") + .HasForeignKey("ParentRequestId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Ombi.Store.Entities.OmbiUser", "RequestedUser") + .WithMany() + .HasForeignKey("RequestedUserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueComments", b => + { + b.HasOne("Ombi.Store.Entities.Requests.Issues", "Issues") + .WithMany("Comments") + .HasForeignKey("IssuesId"); + + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.Issues", b => + { + b.HasOne("Ombi.Store.Entities.Requests.IssueCategory", "IssueCategory") + .WithMany() + .HasForeignKey("IssueCategoryId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Ombi.Store.Entities.Requests.ChildRequests") + .WithMany("Issues") + .HasForeignKey("IssueId"); + + b.HasOne("Ombi.Store.Entities.Requests.MovieRequests") + .WithMany("Issues") + .HasForeignKey("IssueId"); + + b.HasOne("Ombi.Store.Entities.OmbiUser", "UserReported") + .WithMany() + .HasForeignKey("UserReportedId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.MovieRequests", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "RequestedUser") + .WithMany() + .HasForeignKey("RequestedUserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.RequestLog", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.RequestSubscription", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Tokens", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.EpisodeRequests", b => + { + b.HasOne("Ombi.Store.Repository.Requests.SeasonRequests", "Season") + .WithMany("Episodes") + .HasForeignKey("SeasonId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.SeasonRequests", b => + { + b.HasOne("Ombi.Store.Entities.Requests.ChildRequests", "ChildRequest") + .WithMany("SeasonRequests") + .HasForeignKey("ChildRequestId") + .OnDelete(DeleteBehavior.Cascade); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Ombi.Store/Migrations/20180824152254_MusicRequests.cs b/src/Ombi.Store/Migrations/20180824152254_MusicRequests.cs new file mode 100644 index 000000000..4810a39eb --- /dev/null +++ b/src/Ombi.Store/Migrations/20180824152254_MusicRequests.cs @@ -0,0 +1,67 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Ombi.Store.Migrations +{ + public partial class MusicRequests : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "MusicRequestLimit", + table: "AspNetUsers", + nullable: true); + + migrationBuilder.CreateTable( + name: "AlbumRequests", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Title = table.Column(nullable: true), + Approved = table.Column(nullable: false), + MarkedAsApproved = table.Column(nullable: false), + RequestedDate = table.Column(nullable: false), + Available = table.Column(nullable: false), + MarkedAsAvailable = table.Column(nullable: true), + RequestedUserId = table.Column(nullable: true), + Denied = table.Column(nullable: true), + MarkedAsDenied = table.Column(nullable: false), + DeniedReason = table.Column(nullable: true), + RequestType = table.Column(nullable: false), + ForeignAlbumId = table.Column(nullable: true), + ForeignArtistId = table.Column(nullable: true), + Disk = table.Column(nullable: true), + Cover = table.Column(nullable: true), + Rating = table.Column(nullable: false), + ReleaseDate = table.Column(nullable: false), + ArtistName = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AlbumRequests", x => x.Id); + table.ForeignKey( + name: "FK_AlbumRequests_AspNetUsers_RequestedUserId", + column: x => x.RequestedUserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateIndex( + name: "IX_AlbumRequests_RequestedUserId", + table: "AlbumRequests", + column: "RequestedUserId"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "AlbumRequests"); + + migrationBuilder.DropColumn( + name: "MusicRequestLimit", + table: "AspNetUsers"); + } + } +} diff --git a/src/Ombi.Store/Migrations/20180824211553_LidarrSyncJobs.Designer.cs b/src/Ombi.Store/Migrations/20180824211553_LidarrSyncJobs.Designer.cs new file mode 100644 index 000000000..c97886525 --- /dev/null +++ b/src/Ombi.Store/Migrations/20180824211553_LidarrSyncJobs.Designer.cs @@ -0,0 +1,1087 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Ombi.Store.Context; + +namespace Ombi.Store.Migrations +{ + [DbContext(typeof(OmbiContext))] + [Migration("20180824211553_LidarrSyncJobs")] + partial class LidarrSyncJobs + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.1.1-rtm-30846"); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken(); + + b.Property("Name") + .HasMaxLength(256); + + b.Property("NormalizedName") + .HasMaxLength(256); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasName("RoleNameIndex"); + + b.ToTable("AspNetRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ClaimType"); + + b.Property("ClaimValue"); + + b.Property("RoleId") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ClaimType"); + + b.Property("ClaimValue"); + + b.Property("UserId") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider"); + + b.Property("ProviderKey"); + + b.Property("ProviderDisplayName"); + + b.Property("UserId") + .IsRequired(); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId"); + + b.Property("RoleId"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId"); + + b.Property("LoginProvider"); + + b.Property("Name"); + + b.Property("Value"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.ApplicationConfiguration", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Type"); + + b.Property("Value"); + + b.HasKey("Id"); + + b.ToTable("ApplicationConfiguration"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Audit", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AuditArea"); + + b.Property("AuditType"); + + b.Property("DateTime"); + + b.Property("Description"); + + b.Property("User"); + + b.HasKey("Id"); + + b.ToTable("Audit"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.CouchPotatoCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("TheMovieDbId"); + + b.HasKey("Id"); + + b.ToTable("CouchPotatoCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.EmbyContent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("EmbyId") + .IsRequired(); + + b.Property("ImdbId"); + + b.Property("ProviderId"); + + b.Property("TheMovieDbId"); + + b.Property("Title"); + + b.Property("TvDbId"); + + b.Property("Type"); + + b.Property("Url"); + + b.HasKey("Id"); + + b.ToTable("EmbyContent"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.EmbyEpisode", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("EmbyId"); + + b.Property("EpisodeNumber"); + + b.Property("ImdbId"); + + b.Property("ParentId"); + + b.Property("ProviderId"); + + b.Property("SeasonNumber"); + + b.Property("TheMovieDbId"); + + b.Property("Title"); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.HasIndex("ParentId"); + + b.ToTable("EmbyEpisode"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.GlobalSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Content"); + + b.Property("SettingsName"); + + b.HasKey("Id"); + + b.ToTable("GlobalSettings"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.LidarrAlbumCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ArtistId"); + + b.Property("ForeignAlbumId"); + + b.Property("Monitored"); + + b.Property("PercentOfTracks"); + + b.Property("ReleaseDate"); + + b.Property("Title"); + + b.Property("TrackCount"); + + b.HasKey("Id"); + + b.ToTable("LidarrAlbumCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.LidarrArtistCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ArtistId"); + + b.Property("ArtistName"); + + b.Property("ForeignArtistId"); + + b.Property("Monitored"); + + b.HasKey("Id"); + + b.ToTable("LidarrArtistCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.NotificationTemplates", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Agent"); + + b.Property("Enabled"); + + b.Property("Message"); + + b.Property("NotificationType"); + + b.Property("Subject"); + + b.HasKey("Id"); + + b.ToTable("NotificationTemplates"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.NotificationUserId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("PlayerId"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("NotificationUserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.OmbiUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AccessFailedCount"); + + b.Property("Alias"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken(); + + b.Property("Email") + .HasMaxLength(256); + + b.Property("EmailConfirmed"); + + b.Property("EmbyConnectUserId"); + + b.Property("EpisodeRequestLimit"); + + b.Property("LastLoggedIn"); + + b.Property("LockoutEnabled"); + + b.Property("LockoutEnd"); + + b.Property("MovieRequestLimit"); + + b.Property("MusicRequestLimit"); + + b.Property("NormalizedEmail") + .HasMaxLength(256); + + b.Property("NormalizedUserName") + .HasMaxLength(256); + + b.Property("PasswordHash"); + + b.Property("PhoneNumber"); + + b.Property("PhoneNumberConfirmed"); + + b.Property("ProviderUserId"); + + b.Property("SecurityStamp"); + + b.Property("TwoFactorEnabled"); + + b.Property("UserAccessToken"); + + b.Property("UserName") + .HasMaxLength(256); + + b.Property("UserType"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasName("UserNameIndex"); + + b.ToTable("AspNetUsers"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexEpisode", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("EpisodeNumber"); + + b.Property("GrandparentKey"); + + b.Property("Key"); + + b.Property("ParentKey"); + + b.Property("SeasonNumber"); + + b.Property("Title"); + + b.HasKey("Id"); + + b.HasIndex("GrandparentKey"); + + b.ToTable("PlexEpisode"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexSeasonsContent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ParentKey"); + + b.Property("PlexContentId"); + + b.Property("PlexServerContentId"); + + b.Property("SeasonKey"); + + b.Property("SeasonNumber"); + + b.HasKey("Id"); + + b.HasIndex("PlexServerContentId"); + + b.ToTable("PlexSeasonsContent"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexServerContent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("ImdbId"); + + b.Property("Key"); + + b.Property("Quality"); + + b.Property("ReleaseYear"); + + b.Property("TheMovieDbId"); + + b.Property("Title"); + + b.Property("TvDbId"); + + b.Property("Type"); + + b.Property("Url"); + + b.HasKey("Id"); + + b.ToTable("PlexServerContent"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.RadarrCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("HasFile"); + + b.Property("TheMovieDbId"); + + b.HasKey("Id"); + + b.ToTable("RadarrCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.RecentlyAddedLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("ContentId"); + + b.Property("ContentType"); + + b.Property("EpisodeNumber"); + + b.Property("SeasonNumber"); + + b.Property("Type"); + + b.HasKey("Id"); + + b.ToTable("RecentlyAddedLog"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.AlbumRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Approved"); + + b.Property("ArtistName"); + + b.Property("Available"); + + b.Property("Cover"); + + b.Property("Denied"); + + b.Property("DeniedReason"); + + b.Property("Disk"); + + b.Property("ForeignAlbumId"); + + b.Property("ForeignArtistId"); + + b.Property("MarkedAsApproved"); + + b.Property("MarkedAsAvailable"); + + b.Property("MarkedAsDenied"); + + b.Property("Rating"); + + b.Property("ReleaseDate"); + + b.Property("RequestType"); + + b.Property("RequestedDate"); + + b.Property("RequestedUserId"); + + b.Property("Title"); + + b.HasKey("Id"); + + b.HasIndex("RequestedUserId"); + + b.ToTable("AlbumRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.ChildRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Approved"); + + b.Property("Available"); + + b.Property("Denied"); + + b.Property("DeniedReason"); + + b.Property("IssueId"); + + b.Property("MarkedAsApproved"); + + b.Property("MarkedAsAvailable"); + + b.Property("MarkedAsDenied"); + + b.Property("ParentRequestId"); + + b.Property("RequestType"); + + b.Property("RequestedDate"); + + b.Property("RequestedUserId"); + + b.Property("SeriesType"); + + b.Property("Title"); + + b.HasKey("Id"); + + b.HasIndex("ParentRequestId"); + + b.HasIndex("RequestedUserId"); + + b.ToTable("ChildRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Value"); + + b.HasKey("Id"); + + b.ToTable("IssueCategory"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueComments", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Comment"); + + b.Property("Date"); + + b.Property("IssuesId"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("IssuesId"); + + b.HasIndex("UserId"); + + b.ToTable("IssueComments"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.Issues", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Description"); + + b.Property("IssueCategoryId"); + + b.Property("IssueId"); + + b.Property("ProviderId"); + + b.Property("RequestId"); + + b.Property("RequestType"); + + b.Property("ResovledDate"); + + b.Property("Status"); + + b.Property("Subject"); + + b.Property("Title"); + + b.Property("UserReportedId"); + + b.HasKey("Id"); + + b.HasIndex("IssueCategoryId"); + + b.HasIndex("IssueId"); + + b.HasIndex("UserReportedId"); + + b.ToTable("Issues"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.MovieRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Approved"); + + b.Property("Available"); + + b.Property("Background"); + + b.Property("Denied"); + + b.Property("DeniedReason"); + + b.Property("DigitalReleaseDate"); + + b.Property("ImdbId"); + + b.Property("IssueId"); + + b.Property("MarkedAsApproved"); + + b.Property("MarkedAsAvailable"); + + b.Property("MarkedAsDenied"); + + b.Property("Overview"); + + b.Property("PosterPath"); + + b.Property("QualityOverride"); + + b.Property("ReleaseDate"); + + b.Property("RequestType"); + + b.Property("RequestedDate"); + + b.Property("RequestedUserId"); + + b.Property("RootPathOverride"); + + b.Property("Status"); + + b.Property("TheMovieDbId"); + + b.Property("Title"); + + b.HasKey("Id"); + + b.HasIndex("RequestedUserId"); + + b.ToTable("MovieRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.RequestLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("EpisodeCount"); + + b.Property("RequestDate"); + + b.Property("RequestId"); + + b.Property("RequestType"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("RequestLog"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.TvRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Background"); + + b.Property("ImdbId"); + + b.Property("Overview"); + + b.Property("PosterPath"); + + b.Property("QualityOverride"); + + b.Property("ReleaseDate"); + + b.Property("RootFolder"); + + b.Property("Status"); + + b.Property("Title"); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.ToTable("TvRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.RequestSubscription", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("RequestId"); + + b.Property("RequestType"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("RequestSubscription"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.SickRageCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.ToTable("SickRageCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.SickRageEpisodeCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("EpisodeNumber"); + + b.Property("SeasonNumber"); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.ToTable("SickRageEpisodeCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.SonarrCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.ToTable("SonarrCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.SonarrEpisodeCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("EpisodeNumber"); + + b.Property("HasFile"); + + b.Property("SeasonNumber"); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.ToTable("SonarrEpisodeCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Tokens", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Token"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Tokens"); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.EpisodeRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AirDate"); + + b.Property("Approved"); + + b.Property("Available"); + + b.Property("EpisodeNumber"); + + b.Property("Requested"); + + b.Property("SeasonId"); + + b.Property("Title"); + + b.Property("Url"); + + b.HasKey("Id"); + + b.HasIndex("SeasonId"); + + b.ToTable("EpisodeRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.SeasonRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChildRequestId"); + + b.Property("SeasonNumber"); + + b.HasKey("Id"); + + b.HasIndex("ChildRequestId"); + + b.ToTable("SeasonRequests"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole") + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole") + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Ombi.Store.Entities.OmbiUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Ombi.Store.Entities.EmbyEpisode", b => + { + b.HasOne("Ombi.Store.Entities.EmbyContent", "Series") + .WithMany("Episodes") + .HasForeignKey("ParentId") + .HasPrincipalKey("EmbyId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.NotificationUserId", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany("NotificationUserIds") + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexEpisode", b => + { + b.HasOne("Ombi.Store.Entities.PlexServerContent", "Series") + .WithMany("Episodes") + .HasForeignKey("GrandparentKey") + .HasPrincipalKey("Key") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexSeasonsContent", b => + { + b.HasOne("Ombi.Store.Entities.PlexServerContent") + .WithMany("Seasons") + .HasForeignKey("PlexServerContentId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.AlbumRequest", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "RequestedUser") + .WithMany() + .HasForeignKey("RequestedUserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.ChildRequests", b => + { + b.HasOne("Ombi.Store.Entities.Requests.TvRequests", "ParentRequest") + .WithMany("ChildRequests") + .HasForeignKey("ParentRequestId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Ombi.Store.Entities.OmbiUser", "RequestedUser") + .WithMany() + .HasForeignKey("RequestedUserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueComments", b => + { + b.HasOne("Ombi.Store.Entities.Requests.Issues", "Issues") + .WithMany("Comments") + .HasForeignKey("IssuesId"); + + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.Issues", b => + { + b.HasOne("Ombi.Store.Entities.Requests.IssueCategory", "IssueCategory") + .WithMany() + .HasForeignKey("IssueCategoryId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Ombi.Store.Entities.Requests.ChildRequests") + .WithMany("Issues") + .HasForeignKey("IssueId"); + + b.HasOne("Ombi.Store.Entities.Requests.MovieRequests") + .WithMany("Issues") + .HasForeignKey("IssueId"); + + b.HasOne("Ombi.Store.Entities.OmbiUser", "UserReported") + .WithMany() + .HasForeignKey("UserReportedId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.MovieRequests", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "RequestedUser") + .WithMany() + .HasForeignKey("RequestedUserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.RequestLog", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.RequestSubscription", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Tokens", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.EpisodeRequests", b => + { + b.HasOne("Ombi.Store.Repository.Requests.SeasonRequests", "Season") + .WithMany("Episodes") + .HasForeignKey("SeasonId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.SeasonRequests", b => + { + b.HasOne("Ombi.Store.Entities.Requests.ChildRequests", "ChildRequest") + .WithMany("SeasonRequests") + .HasForeignKey("ChildRequestId") + .OnDelete(DeleteBehavior.Cascade); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Ombi.Store/Migrations/20180824211553_LidarrSyncJobs.cs b/src/Ombi.Store/Migrations/20180824211553_LidarrSyncJobs.cs new file mode 100644 index 000000000..2b843d3e2 --- /dev/null +++ b/src/Ombi.Store/Migrations/20180824211553_LidarrSyncJobs.cs @@ -0,0 +1,55 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Ombi.Store.Migrations +{ + public partial class LidarrSyncJobs : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "LidarrAlbumCache", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + ArtistId = table.Column(nullable: false), + ForeignAlbumId = table.Column(nullable: true), + TrackCount = table.Column(nullable: false), + ReleaseDate = table.Column(nullable: false), + Monitored = table.Column(nullable: false), + Title = table.Column(nullable: true), + PercentOfTracks = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_LidarrAlbumCache", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "LidarrArtistCache", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + ArtistId = table.Column(nullable: false), + ArtistName = table.Column(nullable: true), + ForeignArtistId = table.Column(nullable: true), + Monitored = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_LidarrArtistCache", x => x.Id); + }); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "LidarrAlbumCache"); + + migrationBuilder.DropTable( + name: "LidarrArtistCache"); + } + } +} diff --git a/src/Ombi.Store/Migrations/20180828083219_MusicIssues.Designer.cs b/src/Ombi.Store/Migrations/20180828083219_MusicIssues.Designer.cs new file mode 100644 index 000000000..52f00c840 --- /dev/null +++ b/src/Ombi.Store/Migrations/20180828083219_MusicIssues.Designer.cs @@ -0,0 +1,1091 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Ombi.Store.Context; + +namespace Ombi.Store.Migrations +{ + [DbContext(typeof(OmbiContext))] + [Migration("20180828083219_MusicIssues")] + partial class MusicIssues + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.1.1-rtm-30846"); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken(); + + b.Property("Name") + .HasMaxLength(256); + + b.Property("NormalizedName") + .HasMaxLength(256); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasName("RoleNameIndex"); + + b.ToTable("AspNetRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ClaimType"); + + b.Property("ClaimValue"); + + b.Property("RoleId") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ClaimType"); + + b.Property("ClaimValue"); + + b.Property("UserId") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider"); + + b.Property("ProviderKey"); + + b.Property("ProviderDisplayName"); + + b.Property("UserId") + .IsRequired(); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId"); + + b.Property("RoleId"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId"); + + b.Property("LoginProvider"); + + b.Property("Name"); + + b.Property("Value"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.ApplicationConfiguration", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Type"); + + b.Property("Value"); + + b.HasKey("Id"); + + b.ToTable("ApplicationConfiguration"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Audit", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AuditArea"); + + b.Property("AuditType"); + + b.Property("DateTime"); + + b.Property("Description"); + + b.Property("User"); + + b.HasKey("Id"); + + b.ToTable("Audit"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.CouchPotatoCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("TheMovieDbId"); + + b.HasKey("Id"); + + b.ToTable("CouchPotatoCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.EmbyContent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("EmbyId") + .IsRequired(); + + b.Property("ImdbId"); + + b.Property("ProviderId"); + + b.Property("TheMovieDbId"); + + b.Property("Title"); + + b.Property("TvDbId"); + + b.Property("Type"); + + b.Property("Url"); + + b.HasKey("Id"); + + b.ToTable("EmbyContent"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.EmbyEpisode", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("EmbyId"); + + b.Property("EpisodeNumber"); + + b.Property("ImdbId"); + + b.Property("ParentId"); + + b.Property("ProviderId"); + + b.Property("SeasonNumber"); + + b.Property("TheMovieDbId"); + + b.Property("Title"); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.HasIndex("ParentId"); + + b.ToTable("EmbyEpisode"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.GlobalSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Content"); + + b.Property("SettingsName"); + + b.HasKey("Id"); + + b.ToTable("GlobalSettings"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.LidarrAlbumCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("ArtistId"); + + b.Property("ForeignAlbumId"); + + b.Property("Monitored"); + + b.Property("PercentOfTracks"); + + b.Property("ReleaseDate"); + + b.Property("Title"); + + b.Property("TrackCount"); + + b.HasKey("Id"); + + b.ToTable("LidarrAlbumCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.LidarrArtistCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ArtistId"); + + b.Property("ArtistName"); + + b.Property("ForeignArtistId"); + + b.Property("Monitored"); + + b.HasKey("Id"); + + b.ToTable("LidarrArtistCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.NotificationTemplates", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Agent"); + + b.Property("Enabled"); + + b.Property("Message"); + + b.Property("NotificationType"); + + b.Property("Subject"); + + b.HasKey("Id"); + + b.ToTable("NotificationTemplates"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.NotificationUserId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("PlayerId"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("NotificationUserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.OmbiUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AccessFailedCount"); + + b.Property("Alias"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken(); + + b.Property("Email") + .HasMaxLength(256); + + b.Property("EmailConfirmed"); + + b.Property("EmbyConnectUserId"); + + b.Property("EpisodeRequestLimit"); + + b.Property("LastLoggedIn"); + + b.Property("LockoutEnabled"); + + b.Property("LockoutEnd"); + + b.Property("MovieRequestLimit"); + + b.Property("MusicRequestLimit"); + + b.Property("NormalizedEmail") + .HasMaxLength(256); + + b.Property("NormalizedUserName") + .HasMaxLength(256); + + b.Property("PasswordHash"); + + b.Property("PhoneNumber"); + + b.Property("PhoneNumberConfirmed"); + + b.Property("ProviderUserId"); + + b.Property("SecurityStamp"); + + b.Property("TwoFactorEnabled"); + + b.Property("UserAccessToken"); + + b.Property("UserName") + .HasMaxLength(256); + + b.Property("UserType"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasName("UserNameIndex"); + + b.ToTable("AspNetUsers"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexEpisode", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("EpisodeNumber"); + + b.Property("GrandparentKey"); + + b.Property("Key"); + + b.Property("ParentKey"); + + b.Property("SeasonNumber"); + + b.Property("Title"); + + b.HasKey("Id"); + + b.HasIndex("GrandparentKey"); + + b.ToTable("PlexEpisode"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexSeasonsContent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ParentKey"); + + b.Property("PlexContentId"); + + b.Property("PlexServerContentId"); + + b.Property("SeasonKey"); + + b.Property("SeasonNumber"); + + b.HasKey("Id"); + + b.HasIndex("PlexServerContentId"); + + b.ToTable("PlexSeasonsContent"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexServerContent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("ImdbId"); + + b.Property("Key"); + + b.Property("Quality"); + + b.Property("ReleaseYear"); + + b.Property("TheMovieDbId"); + + b.Property("Title"); + + b.Property("TvDbId"); + + b.Property("Type"); + + b.Property("Url"); + + b.HasKey("Id"); + + b.ToTable("PlexServerContent"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.RadarrCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("HasFile"); + + b.Property("TheMovieDbId"); + + b.HasKey("Id"); + + b.ToTable("RadarrCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.RecentlyAddedLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("AlbumId"); + + b.Property("ContentId"); + + b.Property("ContentType"); + + b.Property("EpisodeNumber"); + + b.Property("SeasonNumber"); + + b.Property("Type"); + + b.HasKey("Id"); + + b.ToTable("RecentlyAddedLog"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.AlbumRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Approved"); + + b.Property("ArtistName"); + + b.Property("Available"); + + b.Property("Cover"); + + b.Property("Denied"); + + b.Property("DeniedReason"); + + b.Property("Disk"); + + b.Property("ForeignAlbumId"); + + b.Property("ForeignArtistId"); + + b.Property("MarkedAsApproved"); + + b.Property("MarkedAsAvailable"); + + b.Property("MarkedAsDenied"); + + b.Property("Rating"); + + b.Property("ReleaseDate"); + + b.Property("RequestType"); + + b.Property("RequestedDate"); + + b.Property("RequestedUserId"); + + b.Property("Title"); + + b.HasKey("Id"); + + b.HasIndex("RequestedUserId"); + + b.ToTable("AlbumRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.ChildRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Approved"); + + b.Property("Available"); + + b.Property("Denied"); + + b.Property("DeniedReason"); + + b.Property("IssueId"); + + b.Property("MarkedAsApproved"); + + b.Property("MarkedAsAvailable"); + + b.Property("MarkedAsDenied"); + + b.Property("ParentRequestId"); + + b.Property("RequestType"); + + b.Property("RequestedDate"); + + b.Property("RequestedUserId"); + + b.Property("SeriesType"); + + b.Property("Title"); + + b.HasKey("Id"); + + b.HasIndex("ParentRequestId"); + + b.HasIndex("RequestedUserId"); + + b.ToTable("ChildRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Value"); + + b.HasKey("Id"); + + b.ToTable("IssueCategory"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueComments", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Comment"); + + b.Property("Date"); + + b.Property("IssuesId"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("IssuesId"); + + b.HasIndex("UserId"); + + b.ToTable("IssueComments"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.Issues", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Description"); + + b.Property("IssueCategoryId"); + + b.Property("IssueId"); + + b.Property("ProviderId"); + + b.Property("RequestId"); + + b.Property("RequestType"); + + b.Property("ResovledDate"); + + b.Property("Status"); + + b.Property("Subject"); + + b.Property("Title"); + + b.Property("UserReportedId"); + + b.HasKey("Id"); + + b.HasIndex("IssueCategoryId"); + + b.HasIndex("IssueId"); + + b.HasIndex("UserReportedId"); + + b.ToTable("Issues"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.MovieRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Approved"); + + b.Property("Available"); + + b.Property("Background"); + + b.Property("Denied"); + + b.Property("DeniedReason"); + + b.Property("DigitalReleaseDate"); + + b.Property("ImdbId"); + + b.Property("IssueId"); + + b.Property("MarkedAsApproved"); + + b.Property("MarkedAsAvailable"); + + b.Property("MarkedAsDenied"); + + b.Property("Overview"); + + b.Property("PosterPath"); + + b.Property("QualityOverride"); + + b.Property("ReleaseDate"); + + b.Property("RequestType"); + + b.Property("RequestedDate"); + + b.Property("RequestedUserId"); + + b.Property("RootPathOverride"); + + b.Property("Status"); + + b.Property("TheMovieDbId"); + + b.Property("Title"); + + b.HasKey("Id"); + + b.HasIndex("RequestedUserId"); + + b.ToTable("MovieRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.RequestLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("EpisodeCount"); + + b.Property("RequestDate"); + + b.Property("RequestId"); + + b.Property("RequestType"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("RequestLog"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.TvRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Background"); + + b.Property("ImdbId"); + + b.Property("Overview"); + + b.Property("PosterPath"); + + b.Property("QualityOverride"); + + b.Property("ReleaseDate"); + + b.Property("RootFolder"); + + b.Property("Status"); + + b.Property("Title"); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.ToTable("TvRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.RequestSubscription", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("RequestId"); + + b.Property("RequestType"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("RequestSubscription"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.SickRageCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.ToTable("SickRageCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.SickRageEpisodeCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("EpisodeNumber"); + + b.Property("SeasonNumber"); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.ToTable("SickRageEpisodeCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.SonarrCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.ToTable("SonarrCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.SonarrEpisodeCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("EpisodeNumber"); + + b.Property("HasFile"); + + b.Property("SeasonNumber"); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.ToTable("SonarrEpisodeCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Tokens", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Token"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Tokens"); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.EpisodeRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AirDate"); + + b.Property("Approved"); + + b.Property("Available"); + + b.Property("EpisodeNumber"); + + b.Property("Requested"); + + b.Property("SeasonId"); + + b.Property("Title"); + + b.Property("Url"); + + b.HasKey("Id"); + + b.HasIndex("SeasonId"); + + b.ToTable("EpisodeRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.SeasonRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChildRequestId"); + + b.Property("SeasonNumber"); + + b.HasKey("Id"); + + b.HasIndex("ChildRequestId"); + + b.ToTable("SeasonRequests"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole") + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole") + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Ombi.Store.Entities.OmbiUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Ombi.Store.Entities.EmbyEpisode", b => + { + b.HasOne("Ombi.Store.Entities.EmbyContent", "Series") + .WithMany("Episodes") + .HasForeignKey("ParentId") + .HasPrincipalKey("EmbyId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.NotificationUserId", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany("NotificationUserIds") + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexEpisode", b => + { + b.HasOne("Ombi.Store.Entities.PlexServerContent", "Series") + .WithMany("Episodes") + .HasForeignKey("GrandparentKey") + .HasPrincipalKey("Key") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexSeasonsContent", b => + { + b.HasOne("Ombi.Store.Entities.PlexServerContent") + .WithMany("Seasons") + .HasForeignKey("PlexServerContentId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.AlbumRequest", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "RequestedUser") + .WithMany() + .HasForeignKey("RequestedUserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.ChildRequests", b => + { + b.HasOne("Ombi.Store.Entities.Requests.TvRequests", "ParentRequest") + .WithMany("ChildRequests") + .HasForeignKey("ParentRequestId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Ombi.Store.Entities.OmbiUser", "RequestedUser") + .WithMany() + .HasForeignKey("RequestedUserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueComments", b => + { + b.HasOne("Ombi.Store.Entities.Requests.Issues", "Issues") + .WithMany("Comments") + .HasForeignKey("IssuesId"); + + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.Issues", b => + { + b.HasOne("Ombi.Store.Entities.Requests.IssueCategory", "IssueCategory") + .WithMany() + .HasForeignKey("IssueCategoryId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Ombi.Store.Entities.Requests.ChildRequests") + .WithMany("Issues") + .HasForeignKey("IssueId"); + + b.HasOne("Ombi.Store.Entities.Requests.MovieRequests") + .WithMany("Issues") + .HasForeignKey("IssueId"); + + b.HasOne("Ombi.Store.Entities.OmbiUser", "UserReported") + .WithMany() + .HasForeignKey("UserReportedId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.MovieRequests", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "RequestedUser") + .WithMany() + .HasForeignKey("RequestedUserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.RequestLog", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.RequestSubscription", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Tokens", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.EpisodeRequests", b => + { + b.HasOne("Ombi.Store.Repository.Requests.SeasonRequests", "Season") + .WithMany("Episodes") + .HasForeignKey("SeasonId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.SeasonRequests", b => + { + b.HasOne("Ombi.Store.Entities.Requests.ChildRequests", "ChildRequest") + .WithMany("SeasonRequests") + .HasForeignKey("ChildRequestId") + .OnDelete(DeleteBehavior.Cascade); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Ombi.Store/Migrations/20180828083219_MusicIssues.cs b/src/Ombi.Store/Migrations/20180828083219_MusicIssues.cs new file mode 100644 index 000000000..94a06ff18 --- /dev/null +++ b/src/Ombi.Store/Migrations/20180828083219_MusicIssues.cs @@ -0,0 +1,33 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Ombi.Store.Migrations +{ + public partial class MusicIssues : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "AlbumId", + table: "RecentlyAddedLog", + nullable: true); + + migrationBuilder.AddColumn( + name: "AddedAt", + table: "LidarrAlbumCache", + nullable: false, + defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified)); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "AlbumId", + table: "RecentlyAddedLog"); + + migrationBuilder.DropColumn( + name: "AddedAt", + table: "LidarrAlbumCache"); + } + } +} diff --git a/src/Ombi.Store/Migrations/OmbiContextModelSnapshot.cs b/src/Ombi.Store/Migrations/OmbiContextModelSnapshot.cs index 5a7520005..64400e58c 100644 --- a/src/Ombi.Store/Migrations/OmbiContextModelSnapshot.cs +++ b/src/Ombi.Store/Migrations/OmbiContextModelSnapshot.cs @@ -244,6 +244,50 @@ namespace Ombi.Store.Migrations b.ToTable("GlobalSettings"); }); + modelBuilder.Entity("Ombi.Store.Entities.LidarrAlbumCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("ArtistId"); + + b.Property("ForeignAlbumId"); + + b.Property("Monitored"); + + b.Property("PercentOfTracks"); + + b.Property("ReleaseDate"); + + b.Property("Title"); + + b.Property("TrackCount"); + + b.HasKey("Id"); + + b.ToTable("LidarrAlbumCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.LidarrArtistCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ArtistId"); + + b.Property("ArtistName"); + + b.Property("ForeignArtistId"); + + b.Property("Monitored"); + + b.HasKey("Id"); + + b.ToTable("LidarrArtistCache"); + }); + modelBuilder.Entity("Ombi.Store.Entities.NotificationTemplates", b => { b.Property("Id") @@ -311,6 +355,8 @@ namespace Ombi.Store.Migrations b.Property("MovieRequestLimit"); + b.Property("MusicRequestLimit"); + b.Property("NormalizedEmail") .HasMaxLength(256); @@ -445,6 +491,8 @@ namespace Ombi.Store.Migrations b.Property("AddedAt"); + b.Property("AlbumId"); + b.Property("ContentId"); b.Property("ContentType"); @@ -460,6 +508,54 @@ namespace Ombi.Store.Migrations b.ToTable("RecentlyAddedLog"); }); + modelBuilder.Entity("Ombi.Store.Entities.Requests.AlbumRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Approved"); + + b.Property("ArtistName"); + + b.Property("Available"); + + b.Property("Cover"); + + b.Property("Denied"); + + b.Property("DeniedReason"); + + b.Property("Disk"); + + b.Property("ForeignAlbumId"); + + b.Property("ForeignArtistId"); + + b.Property("MarkedAsApproved"); + + b.Property("MarkedAsAvailable"); + + b.Property("MarkedAsDenied"); + + b.Property("Rating"); + + b.Property("ReleaseDate"); + + b.Property("RequestType"); + + b.Property("RequestedDate"); + + b.Property("RequestedUserId"); + + b.Property("Title"); + + b.HasKey("Id"); + + b.HasIndex("RequestedUserId"); + + b.ToTable("AlbumRequests"); + }); + modelBuilder.Entity("Ombi.Store.Entities.Requests.ChildRequests", b => { b.Property("Id") @@ -894,6 +990,13 @@ namespace Ombi.Store.Migrations .HasForeignKey("PlexServerContentId"); }); + modelBuilder.Entity("Ombi.Store.Entities.Requests.AlbumRequest", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "RequestedUser") + .WithMany() + .HasForeignKey("RequestedUserId"); + }); + modelBuilder.Entity("Ombi.Store.Entities.Requests.ChildRequests", b => { b.HasOne("Ombi.Store.Entities.Requests.TvRequests", "ParentRequest") diff --git a/src/Ombi.Store/Repository/Requests/IMusicRequestRepository.cs b/src/Ombi.Store/Repository/Requests/IMusicRequestRepository.cs new file mode 100644 index 000000000..28cb0b2f9 --- /dev/null +++ b/src/Ombi.Store/Repository/Requests/IMusicRequestRepository.cs @@ -0,0 +1,17 @@ +using System.Linq; +using System.Threading.Tasks; +using Ombi.Store.Entities.Requests; + +namespace Ombi.Store.Repository.Requests +{ + public interface IMusicRequestRepository : IRepository + { + IQueryable GetAll(string userId); + AlbumRequest GetRequest(string foreignAlbumId); + Task GetRequestAsync(string foreignAlbumId); + IQueryable GetWithUser(); + IQueryable GetWithUser(string userId); + Task Save(); + Task Update(AlbumRequest request); + } +} \ No newline at end of file diff --git a/src/Ombi.Store/Repository/Requests/MusicRequestRepository.cs b/src/Ombi.Store/Repository/Requests/MusicRequestRepository.cs new file mode 100644 index 000000000..59edf265a --- /dev/null +++ b/src/Ombi.Store/Repository/Requests/MusicRequestRepository.cs @@ -0,0 +1,72 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Ombi.Store.Context; +using Ombi.Store.Entities.Requests; + +namespace Ombi.Store.Repository.Requests +{ + public class MusicRequestRepository : Repository, IMusicRequestRepository + { + public MusicRequestRepository(IOmbiContext ctx) : base(ctx) + { + Db = ctx; + } + + private IOmbiContext Db { get; } + + public Task GetRequestAsync(string foreignAlbumId) + { + return Db.AlbumRequests.Where(x => x.ForeignAlbumId == foreignAlbumId) + .Include(x => x.RequestedUser) + .FirstOrDefaultAsync(); + } + + public IQueryable GetAll(string userId) + { + return GetWithUser().Where(x => x.RequestedUserId == userId); + } + + public AlbumRequest GetRequest(string foreignAlbumId) + { + return Db.AlbumRequests.Where(x => x.ForeignAlbumId == foreignAlbumId) + .Include(x => x.RequestedUser) + .FirstOrDefault(); + } + + public IQueryable GetWithUser() + { + return Db.AlbumRequests + .Include(x => x.RequestedUser) + .ThenInclude(x => x.NotificationUserIds) + .AsQueryable(); + } + + + public IQueryable GetWithUser(string userId) + { + return Db.AlbumRequests + .Where(x => x.RequestedUserId == userId) + .Include(x => x.RequestedUser) + .ThenInclude(x => x.NotificationUserIds) + .AsQueryable(); + } + + public async Task Update(AlbumRequest request) + { + if (Db.Entry(request).State == EntityState.Detached) + { + Db.AlbumRequests.Attach(request); + Db.Update(request); + } + await Db.SaveChangesAsync(); + } + + public async Task Save() + { + await Db.SaveChangesAsync(); + } + } +} \ No newline at end of file diff --git a/src/Ombi.sln b/src/Ombi.sln index f29eeb8a6..1dfaa1dc6 100644 --- a/src/Ombi.sln +++ b/src/Ombi.sln @@ -94,6 +94,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ombi.Api.SickRage", "Ombi.A EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ombi.Api.Notifications", "Ombi.Api.Notifications\Ombi.Api.Notifications.csproj", "{10D1FE9D-9124-42B7-B1E1-CEB99B832618}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ombi.Api.Lidarr", "Ombi.Api.Lidarr\Ombi.Api.Lidarr.csproj", "{4FA21A20-92F4-462C-B929-2C517A88CC56}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -244,6 +246,10 @@ Global {10D1FE9D-9124-42B7-B1E1-CEB99B832618}.Debug|Any CPU.Build.0 = Debug|Any CPU {10D1FE9D-9124-42B7-B1E1-CEB99B832618}.Release|Any CPU.ActiveCfg = Release|Any CPU {10D1FE9D-9124-42B7-B1E1-CEB99B832618}.Release|Any CPU.Build.0 = Release|Any CPU + {4FA21A20-92F4-462C-B929-2C517A88CC56}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4FA21A20-92F4-462C-B929-2C517A88CC56}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4FA21A20-92F4-462C-B929-2C517A88CC56}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4FA21A20-92F4-462C-B929-2C517A88CC56}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -279,6 +285,7 @@ Global {55866DEE-46D1-4AF7-B1A2-62F6190C8EC7} = {9293CA11-360A-4C20-A674-B9E794431BF5} {94C9A366-2595-45EA-AABB-8E4A2E90EC5B} = {9293CA11-360A-4C20-A674-B9E794431BF5} {10D1FE9D-9124-42B7-B1E1-CEB99B832618} = {9293CA11-360A-4C20-A674-B9E794431BF5} + {4FA21A20-92F4-462C-B929-2C517A88CC56} = {9293CA11-360A-4C20-A674-B9E794431BF5} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {192E9BF8-00B4-45E4-BCCC-4C215725C869} diff --git a/src/Ombi/ClientApp/app/interfaces/IIssues.ts b/src/Ombi/ClientApp/app/interfaces/IIssues.ts index cd2ad53a6..dce9882ec 100644 --- a/src/Ombi/ClientApp/app/interfaces/IIssues.ts +++ b/src/Ombi/ClientApp/app/interfaces/IIssues.ts @@ -1,4 +1,4 @@ -import { IIssueCategory, IUser, RequestType } from "./"; +import { IIssueCategory, IUser, RequestType } from "."; export interface IIssues { id?: number; diff --git a/src/Ombi/ClientApp/app/interfaces/ILidarr.ts b/src/Ombi/ClientApp/app/interfaces/ILidarr.ts new file mode 100644 index 000000000..2674a7dac --- /dev/null +++ b/src/Ombi/ClientApp/app/interfaces/ILidarr.ts @@ -0,0 +1,9 @@ +export interface ILidarrRootFolder { + id: number; + path: string; +} + +export interface ILidarrProfile { + name: string; + id: number; +} diff --git a/src/Ombi/ClientApp/app/interfaces/IRadarr.ts b/src/Ombi/ClientApp/app/interfaces/IRadarr.ts index ebc92507c..b643993f4 100644 --- a/src/Ombi/ClientApp/app/interfaces/IRadarr.ts +++ b/src/Ombi/ClientApp/app/interfaces/IRadarr.ts @@ -8,6 +8,11 @@ export interface IRadarrProfile { id: number; } +export interface IProfiles { + name: string; + id: number; +} + export interface IMinimumAvailability { value: string; name: string; diff --git a/src/Ombi/ClientApp/app/interfaces/IRequestModel.ts b/src/Ombi/ClientApp/app/interfaces/IRequestModel.ts index 92c455fc1..f7fe3df87 100644 --- a/src/Ombi/ClientApp/app/interfaces/IRequestModel.ts +++ b/src/Ombi/ClientApp/app/interfaces/IRequestModel.ts @@ -20,6 +20,23 @@ export interface IMovieRequests extends IFullBaseRequest { qualityOverrideTitle: string; } +export interface IAlbumRequest extends IBaseRequest { + foreignAlbumId: string; + foreignArtistId: string; + disk: string; + cover: string; + releaseDate: Date; + artistName: string; + + subscribed: boolean; + showSubscribe: boolean; + background: any; +} + +export interface IAlbumRequestModel { + foreignAlbumId: string; +} + export interface IRequestsViewModel { total: number; collection: T[]; @@ -29,6 +46,10 @@ export interface IMovieUpdateModel { id: number; } +export interface IAlbumUpdateModel { + id: number; +} + export interface IFullBaseRequest extends IBaseRequest { imdbId: string; overview: string; diff --git a/src/Ombi/ClientApp/app/interfaces/ISearchMusicResult.ts b/src/Ombi/ClientApp/app/interfaces/ISearchMusicResult.ts new file mode 100644 index 000000000..806beb92f --- /dev/null +++ b/src/Ombi/ClientApp/app/interfaces/ISearchMusicResult.ts @@ -0,0 +1,55 @@ +export interface ISearchArtistResult { + artistName: string; + artistType: string; + disambiguation: string; + forignArtistId: string; + + banner: string; + overview: string; + poster: string; + monitored: boolean; + approved: boolean; + requested: boolean; + requestId: number; + available: boolean; + links: ILink[]; + + subscribed: boolean; + showSubscribe: boolean; + + // for the UI + requestProcessing: boolean; + processed: boolean; + background: any; +} + +export interface ILink { + url: string; + name: string; +} + +export interface ISearchAlbumResult { + id: number; + requestId: number; + albumType: string; + artistName: string; + cover: string; + disk: string; + foreignAlbumId: string; + foreignArtistId: string; + monitored: boolean; + rating: number; + releaseDate: Date; + title: string; + fullyAvailable: boolean; + partiallyAvailable: boolean; + requested: boolean; + approved: boolean; + subscribed: boolean; + + // for the UI + showSubscribe: boolean; + requestProcessing: boolean; + processed: boolean; + background: any; +} diff --git a/src/Ombi/ClientApp/app/interfaces/ISettings.ts b/src/Ombi/ClientApp/app/interfaces/ISettings.ts index 9996658c6..2fb46a2b7 100644 --- a/src/Ombi/ClientApp/app/interfaces/ISettings.ts +++ b/src/Ombi/ClientApp/app/interfaces/ISettings.ts @@ -84,6 +84,17 @@ export interface IRadarrSettings extends IExternalSettings { minimumAvailability: string; } +export interface ILidarrSettings extends IExternalSettings { + enabled: boolean; + apiKey: string; + defaultQualityProfile: string; + defaultRootPath: string; + fullRootPath: string; + metadataProfileId: number; + languageProfileId: number; + albumFolder: boolean; +} + export interface ILandingPageSettings extends ISettings { enabled: boolean; @@ -131,6 +142,7 @@ export interface IJobSettings { refreshMetadata: string; newsletter: string; plexRecentlyAddedSync: string; + lidarrArtistSync: string; } export interface IIssueSettings extends ISettings { diff --git a/src/Ombi/ClientApp/app/interfaces/IUser.ts b/src/Ombi/ClientApp/app/interfaces/IUser.ts index 0e8141b52..cd96848fb 100644 --- a/src/Ombi/ClientApp/app/interfaces/IUser.ts +++ b/src/Ombi/ClientApp/app/interfaces/IUser.ts @@ -1,4 +1,4 @@ -import { ICheckbox } from "./index"; +import { ICheckbox } from "."; export interface IUser { id: string; diff --git a/src/Ombi/ClientApp/app/interfaces/index.ts b/src/Ombi/ClientApp/app/interfaces/index.ts index 538e1bd95..74a39099e 100644 --- a/src/Ombi/ClientApp/app/interfaces/index.ts +++ b/src/Ombi/ClientApp/app/interfaces/index.ts @@ -14,3 +14,5 @@ export * from "./ISonarr"; export * from "./IUser"; export * from "./IIssues"; export * from "./IRecentlyAdded"; +export * from "./ILidarr"; +export * from "./ISearchMusicResult"; diff --git a/src/Ombi/ClientApp/app/issues/issuestable.component.ts b/src/Ombi/ClientApp/app/issues/issuestable.component.ts index 5df8a35bd..aadfd546a 100644 --- a/src/Ombi/ClientApp/app/issues/issuestable.component.ts +++ b/src/Ombi/ClientApp/app/issues/issuestable.component.ts @@ -1,6 +1,6 @@ import { Component, EventEmitter, Input, Output } from "@angular/core"; -import { IIssues, IPagenator, IssueStatus } from "./../interfaces"; +import { IIssues, IPagenator, IssueStatus } from "../interfaces"; @Component({ selector: "issues-table", diff --git a/src/Ombi/ClientApp/app/recentlyAdded/recentlyAdded.component.ts b/src/Ombi/ClientApp/app/recentlyAdded/recentlyAdded.component.ts index 303e530e1..f426a39d1 100644 --- a/src/Ombi/ClientApp/app/recentlyAdded/recentlyAdded.component.ts +++ b/src/Ombi/ClientApp/app/recentlyAdded/recentlyAdded.component.ts @@ -1,8 +1,8 @@ import { Component, OnInit } from "@angular/core"; import { NguCarouselConfig } from "@ngu/carousel"; +import { IRecentlyAddedMovies, IRecentlyAddedTvShows } from "../interfaces"; import { ImageService, RecentlyAddedService } from "../services"; -import { IRecentlyAddedMovies, IRecentlyAddedTvShows } from "./../interfaces"; @Component({ templateUrl: "recentlyAdded.component.html", diff --git a/src/Ombi/ClientApp/app/requests/music/musicrequests.component.html b/src/Ombi/ClientApp/app/requests/music/musicrequests.component.html new file mode 100644 index 000000000..c89c2be0a --- /dev/null +++ b/src/Ombi/ClientApp/app/requests/music/musicrequests.component.html @@ -0,0 +1,265 @@ + +
+ + +
+
+
+
+
+ +
+ poster +
+ + +
+ + +
+
+ {{ 'Requests.RequestedBy' | translate }} + {{request.requestedUser.userName}} + {{request.requestedUser.alias}} + {{request.requestedUser.userName}} +
+ +
+ {{ 'Requests.RequestStatus' | translate }} + + + + + + + + +
+
+ {{ 'Requests.Denied' | translate }} + + +
+ + +
{{ 'Requests.ReleaseDate' | translate: {date: request.releaseDate | date: 'mediumDate'} }}
+
{{ 'Requests.RequestDate' | translate }} {{request.requestedDate | date: 'mediumDate'}}
+
+
+ + +
+
+ +
+
+
+ +
+ + + + + + + +
+ +
+
+
+ +
+ +
+ + +
+ + + +
+ + +
+
+
+
+ + + + +
+ + +
+ + + + + + +

{{ 'Requests.Filter' | translate }}

+
+
+

{{ 'Filter.FilterHeaderAvailability' | translate }}

+
+
+ + +
+
+
+
+ + +
+
+
+
+

{{ 'Filter.FilterHeaderRequestStatus' | translate }}

+
+
+ + +
+
+
+
+ + +
+
+
+
+ + +
+
+
+ + +
\ No newline at end of file diff --git a/src/Ombi/ClientApp/app/requests/music/musicrequests.component.ts b/src/Ombi/ClientApp/app/requests/music/musicrequests.component.ts new file mode 100644 index 000000000..8f8096acd --- /dev/null +++ b/src/Ombi/ClientApp/app/requests/music/musicrequests.component.ts @@ -0,0 +1,351 @@ +import { PlatformLocation } from "@angular/common"; +import { Component, Input, OnInit } from "@angular/core"; +import { DomSanitizer } from "@angular/platform-browser"; +import { Subject } from "rxjs"; +import { debounceTime, distinctUntilChanged } from "rxjs/operators"; + +import { AuthService } from "../../auth/auth.service"; +import { FilterType, IAlbumRequest, IFilter, IIssueCategory, IPagenator, OrderType } from "../../interfaces"; +import { NotificationService, RequestService } from "../../services"; + +@Component({ + selector: "music-requests", + templateUrl: "./musicrequests.component.html", +}) +export class MusicRequestsComponent implements OnInit { + public albumRequests: IAlbumRequest[]; + public defaultPoster: string; + + public searchChanged: Subject = new Subject(); + public searchText: string; + + public isAdmin: boolean; // Also PowerUser + + @Input() public issueCategories: IIssueCategory[]; + @Input() public issuesEnabled: boolean; + public issuesBarVisible = false; + public issueRequest: IAlbumRequest; + public issueProviderId: string; + public issueCategorySelected: IIssueCategory; + + public filterDisplay: boolean; + public filter: IFilter; + public filterType = FilterType; + + public orderType: OrderType = OrderType.RequestedDateDesc; + public OrderType = OrderType; + + public totalAlbums: number = 100; + private currentlyLoaded: number; + private amountToLoad: number; + + constructor( + private requestService: RequestService, + private auth: AuthService, + private notificationService: NotificationService, + private sanitizer: DomSanitizer, + private readonly platformLocation: PlatformLocation) { + this.searchChanged.pipe( + debounceTime(600), // Wait Xms after the last event before emitting last event + distinctUntilChanged(), // only emit if value is different from previous value + ).subscribe(x => { + this.searchText = x as string; + if (this.searchText === "") { + this.resetSearch(); + return; + } + this.requestService.searchAlbumRequests(this.searchText) + .subscribe(m => { + this.setOverrides(m); + this.albumRequests = m; + }); + }); + this.defaultPoster = "../../../images/default-music-placeholder.png"; + const base = this.platformLocation.getBaseHrefFromDOM(); + if (base) { + this.defaultPoster = "../../.." + base + "/images/default-music-placeholder.png"; + } + } + + public ngOnInit() { + this.amountToLoad = 10; + this.currentlyLoaded = 10; + this.filter = { + availabilityFilter: FilterType.None, + statusFilter: FilterType.None, + }; + this.loadInit(); + this.isAdmin = this.auth.hasRole("admin") || this.auth.hasRole("poweruser"); + } + + public paginate(event: IPagenator) { + const skipAmount = event.first; + this.loadRequests(this.amountToLoad, skipAmount); + } + + public search(text: any) { + this.searchChanged.next(text.target.value); + } + + public removeRequest(request: IAlbumRequest) { + this.requestService.removeAlbumRequest(request).subscribe(x => { + this.removeRequestFromUi(request); + this.loadRequests(this.amountToLoad, this.currentlyLoaded = 0); + }); + } + + public changeAvailability(request: IAlbumRequest, available: boolean) { + request.available = available; + + if (available) { + this.requestService.markAlbumAvailable({ id: request.id }).subscribe(x => { + if (x.result) { + this.notificationService.success( + `${request.title} Is now available`); + } else { + this.notificationService.warning("Request Available", x.message ? x.message : x.errorMessage); + request.approved = false; + } + }); + } else { + this.requestService.markAlbumUnavailable({ id: request.id }).subscribe(x => { + if (x.result) { + this.notificationService.success( + `${request.title} Is now unavailable`); + } else { + this.notificationService.warning("Request Available", x.message ? x.message : x.errorMessage); + request.approved = false; + } + }); + } + } + + public approve(request: IAlbumRequest) { + request.approved = true; + this.approveRequest(request); + } + + public deny(request: IAlbumRequest) { + request.denied = true; + this.denyRequest(request); + } + + // public selectRootFolder(searchResult: IAlbumRequest, rootFolderSelected: IRadarrRootFolder, event: any) { + // event.preventDefault(); + // // searchResult.rootPathOverride = rootFolderSelected.id; + // this.setOverride(searchResult); + // this.updateRequest(searchResult); + // } + + // public selectQualityProfile(searchResult: IMovieRequests, profileSelected: IRadarrProfile, event: any) { + // event.preventDefault(); + // searchResult.qualityOverride = profileSelected.id; + // this.setOverride(searchResult); + // this.updateRequest(searchResult); + // } + + public reportIssue(catId: IIssueCategory, req: IAlbumRequest) { + this.issueRequest = req; + this.issueCategorySelected = catId; + this.issuesBarVisible = true; + this.issueProviderId = req.foreignAlbumId; + } + + public ignore(event: any): void { + event.preventDefault(); + } + + public clearFilter(el: any) { + el = el.toElement || el.relatedTarget || el.target || el.srcElement; + + el = el.parentElement; + el = el.querySelectorAll("INPUT"); + for (el of el) { + el.checked = false; + el.parentElement.classList.remove("active"); + } + + this.filterDisplay = false; + this.filter.availabilityFilter = FilterType.None; + this.filter.statusFilter = FilterType.None; + + this.resetSearch(); + } + + public filterAvailability(filter: FilterType, el: any) { + this.filterActiveStyle(el); + this.filter.availabilityFilter = filter; + this.loadInit(); + } + + public filterStatus(filter: FilterType, el: any) { + this.filterActiveStyle(el); + this.filter.statusFilter = filter; + this.loadInit(); + } + + public setOrder(value: OrderType, el: any) { + el = el.toElement || el.relatedTarget || el.target || el.srcElement; + + const parent = el.parentElement; + const previousFilter = parent.querySelector(".active"); + + previousFilter.className = ""; + el.className = "active"; + + this.orderType = value; + + this.loadInit(); + } + + // public subscribe(request: IAlbumRequest) { + // request.subscribed = true; + // this.requestService.subscribeToMovie(request.id) + // .subscribe(x => { + // this.notificationService.success("Subscribed To Movie!"); + // }); + // } + + // public unSubscribe(request: IMovieRequests) { + // request.subscribed = false; + // this.requestService.unSubscribeToMovie(request.id) + // .subscribe(x => { + // this.notificationService.success("Unsubscribed Movie!"); + // }); + // } + + private filterActiveStyle(el: any) { + el = el.toElement || el.relatedTarget || el.target || el.srcElement; + + el = el.parentElement; //gets radio div + el = el.parentElement; //gets form group div + el = el.parentElement; //gets status filter div + el = el.querySelectorAll("INPUT"); + for (el of el) { + if (el.checked) { + if (!el.parentElement.classList.contains("active")) { + el.parentElement.className += " active"; + } + } else { + el.parentElement.classList.remove("active"); + } + } + } + + private loadRequests(amountToLoad: number, currentlyLoaded: number) { + this.requestService.getAlbumRequests(amountToLoad, currentlyLoaded, this.orderType, this.filter) + .subscribe(x => { + this.setOverrides(x.collection); + if (!this.albumRequests) { + this.albumRequests = []; + } + this.albumRequests = x.collection; + this.totalAlbums = x.total; + this.currentlyLoaded = currentlyLoaded + amountToLoad; + }); + } + + private approveRequest(request: IAlbumRequest) { + this.requestService.approveAlbum({ id: request.id }) + .subscribe(x => { + request.approved = true; + if (x.result) { + this.notificationService.success( + `Request for ${request.title} has been approved successfully`); + } else { + this.notificationService.warning("Request Approved", x.message ? x.message : x.errorMessage); + request.approved = false; + } + }); + } + + private denyRequest(request: IAlbumRequest) { + this.requestService.denyAlbum({ id: request.id }) + .subscribe(x => { + if (x.result) { + this.notificationService.success( + `Request for ${request.title} has been denied successfully`); + } else { + this.notificationService.warning("Request Denied", x.message ? x.message : x.errorMessage); + request.denied = false; + } + }); + } + + private loadInit() { + this.requestService.getAlbumRequests(this.amountToLoad, 0, this.orderType, this.filter) + .subscribe(x => { + this.albumRequests = x.collection; + this.totalAlbums = x.total; + + this.setOverrides(this.albumRequests); + + if (this.isAdmin) { + // this.radarrService.getQualityProfilesFromSettings().subscribe(c => { + // this.radarrProfiles = c; + // this.albumRequests.forEach((req) => this.setQualityOverrides(req)); + // }); + // this.radarrService.getRootFoldersFromSettings().subscribe(c => { + // this.radarrRootFolders = c; + // this.albumRequests.forEach((req) => this.setRootFolderOverrides(req)); + // }); + } + }); + } + + private resetSearch() { + this.currentlyLoaded = 5; + this.loadInit(); + } + + private removeRequestFromUi(key: IAlbumRequest) { + const index = this.albumRequests.indexOf(key, 0); + if (index > -1) { + this.albumRequests.splice(index, 1); + } + } + + private setOverrides(requests: IAlbumRequest[]): void { + requests.forEach((req) => { + this.setOverride(req); + }); + } + + // private setQualityOverrides(req: IMovieRequests): void { + // if (this.radarrProfiles) { + // const profile = this.radarrProfiles.filter((p) => { + // return p.id === req.qualityOverride; + // }); + // if (profile.length > 0) { + // req.qualityOverrideTitle = profile[0].name; + // } + // } + // } + // private setRootFolderOverrides(req: IMovieRequests): void { + // if (this.radarrRootFolders) { + // const path = this.radarrRootFolders.filter((folder) => { + // return folder.id === req.rootPathOverride; + // }); + // if (path.length > 0) { + // req.rootPathOverrideTitle = path[0].path; + // } + // } + // } + + private setOverride(req: IAlbumRequest): void { + this.setAlbumBackground(req); + // this.setQualityOverrides(req); + // this.setRootFolderOverrides(req); + } + private setAlbumBackground(req: IAlbumRequest) { + if (req.disk === null) { + if(req.cover === null) { + req.disk = this.defaultPoster; + } else { + req.disk = req.cover; + } + } + req.background = this.sanitizer.bypassSecurityTrustStyle + ("url(" + req.cover + ")"); + } +} diff --git a/src/Ombi/ClientApp/app/requests/request.component.html b/src/Ombi/ClientApp/app/requests/request.component.html index 916d3b774..45509fba3 100644 --- a/src/Ombi/ClientApp/app/requests/request.component.html +++ b/src/Ombi/ClientApp/app/requests/request.component.html @@ -9,6 +9,10 @@ {{ 'Requests.TvTab' | translate }} +
  • + {{ 'Requests.MusicTab' | translate }} + +
  • @@ -19,5 +23,8 @@
    +
    + +
    diff --git a/src/Ombi/ClientApp/app/requests/request.component.ts b/src/Ombi/ClientApp/app/requests/request.component.ts index 08df3f2be..d0fa9d0b1 100644 --- a/src/Ombi/ClientApp/app/requests/request.component.ts +++ b/src/Ombi/ClientApp/app/requests/request.component.ts @@ -1,8 +1,8 @@  import { Component, OnInit } from "@angular/core"; -import { IIssueCategory } from "./../interfaces"; -import { IssuesService, SettingsService } from "./../services"; +import { IIssueCategory } from "../interfaces"; +import { IssuesService, SettingsService } from "../services"; @Component({ templateUrl: "./request.component.html", @@ -11,6 +11,7 @@ export class RequestComponent implements OnInit { public showMovie = true; public showTv = false; + public showAlbums = false; public issueCategories: IIssueCategory[]; public issuesEnabled = false; @@ -28,10 +29,18 @@ export class RequestComponent implements OnInit { public selectMovieTab() { this.showMovie = true; this.showTv = false; + this.showAlbums = false; } public selectTvTab() { this.showMovie = false; this.showTv = true; + this.showAlbums = false; + } + + public selectMusicTab() { + this.showMovie = false; + this.showTv = false; + this.showAlbums = true; } } diff --git a/src/Ombi/ClientApp/app/requests/requests.module.ts b/src/Ombi/ClientApp/app/requests/requests.module.ts index 31a45e07a..63d7117f5 100644 --- a/src/Ombi/ClientApp/app/requests/requests.module.ts +++ b/src/Ombi/ClientApp/app/requests/requests.module.ts @@ -8,6 +8,7 @@ import { InfiniteScrollModule } from "ngx-infinite-scroll"; import { ButtonModule, DialogModule, PaginatorModule } from "primeng/primeng"; import { MovieRequestsComponent } from "./movierequests.component"; +import { MusicRequestsComponent } from "./music/musicrequests.component"; // Request import { RequestComponent } from "./request.component"; import { TvRequestChildrenComponent } from "./tvrequest-children.component"; @@ -23,7 +24,6 @@ import { SharedModule } from "../shared/shared.module"; const routes: Routes = [ { path: "", component: RequestComponent, canActivate: [AuthGuard] }, - { path: ":id", component: TvRequestChildrenComponent, canActivate: [AuthGuard] }, ]; @NgModule({ imports: [ @@ -44,6 +44,7 @@ const routes: Routes = [ MovieRequestsComponent, TvRequestsComponent, TvRequestChildrenComponent, + MusicRequestsComponent, ], exports: [ RouterModule, diff --git a/src/Ombi/ClientApp/app/requests/tvrequests.component.ts b/src/Ombi/ClientApp/app/requests/tvrequests.component.ts index 6a17066d7..144de0206 100644 --- a/src/Ombi/ClientApp/app/requests/tvrequests.component.ts +++ b/src/Ombi/ClientApp/app/requests/tvrequests.component.ts @@ -7,7 +7,7 @@ import { debounceTime, distinctUntilChanged } from "rxjs/operators"; import { AuthService } from "../auth/auth.service"; import { FilterType, IIssueCategory, IPagenator, IRequestsViewModel, ISonarrProfile, ISonarrRootFolder, ITvRequests, OrderType } from "../interfaces"; import { NotificationService, RequestService, SonarrService } from "../services"; -import { ImageService } from "./../services/image.service"; +import { ImageService } from "../services/image.service"; @Component({ selector: "tv-requests", diff --git a/src/Ombi/ClientApp/app/search/moviesearch.component.html b/src/Ombi/ClientApp/app/search/moviesearch.component.html index 44dc345bc..2cd7e2499 100644 --- a/src/Ombi/ClientApp/app/search/moviesearch.component.html +++ b/src/Ombi/ClientApp/app/search/moviesearch.component.html @@ -83,7 +83,7 @@ + {{ 'Common.Request' | translate }} diff --git a/src/Ombi/ClientApp/app/search/music/albumsearch.component.html b/src/Ombi/ClientApp/app/search/music/albumsearch.component.html new file mode 100644 index 000000000..fd5f71075 --- /dev/null +++ b/src/Ombi/ClientApp/app/search/music/albumsearch.component.html @@ -0,0 +1,116 @@ +
    + +
    +
    + + +
    + poster +
    + + +
    + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + Release Date: {{result.releaseDate | date:'yyyy-MM-dd'}} + + + {{result.rating}}/10 + + + + + +
    +
    + + +
    + +
    + +
    +
    +
    + + + + + + +
    + + + + +
    + +
    + + + + diff --git a/src/Ombi/ClientApp/app/search/music/albumsearch.component.ts b/src/Ombi/ClientApp/app/search/music/albumsearch.component.ts new file mode 100644 index 000000000..9dac4aa8b --- /dev/null +++ b/src/Ombi/ClientApp/app/search/music/albumsearch.component.ts @@ -0,0 +1,86 @@ +import { Component, EventEmitter, Input, Output } from "@angular/core"; +import { TranslateService } from "@ngx-translate/core"; + +import { AuthService } from "../../auth/auth.service"; +import { IIssueCategory, IRequestEngineResult } from "../../interfaces"; +import { ISearchAlbumResult } from "../../interfaces/ISearchMusicResult"; +import { NotificationService, RequestService } from "../../services"; + +@Component({ + selector: "album-search", + templateUrl: "./albumsearch.component.html", +}) +export class AlbumSearchComponent { + + @Input() public result: ISearchAlbumResult; + public engineResult: IRequestEngineResult; + @Input() public defaultPoster: string; + + @Input() public issueCategories: IIssueCategory[]; + @Input() public issuesEnabled: boolean; + public issuesBarVisible = false; + public issueRequestTitle: string; + public issueRequestId: number; + public issueProviderId: string; + public issueCategorySelected: IIssueCategory; + + @Output() public setSearch = new EventEmitter(); + + constructor( + private requestService: RequestService, + private notificationService: NotificationService, private authService: AuthService, + private readonly translate: TranslateService) { + } + + public selectArtist(event: Event, artistId: string) { + event.preventDefault(); + this.setSearch.emit(artistId); + } + + public reportIssue(catId: IIssueCategory, req: ISearchAlbumResult) { + this.issueRequestId = req.id; + this.issueRequestTitle = req.title + `(${req.releaseDate.getFullYear})`; + this.issueCategorySelected = catId; + this.issuesBarVisible = true; + this.issueProviderId = req.id.toString(); + } + + public request(searchResult: ISearchAlbumResult) { + searchResult.requested = true; + searchResult.requestProcessing = true; + searchResult.showSubscribe = false; + if (this.authService.hasRole("admin") || this.authService.hasRole("AutoApproveMusic")) { + searchResult.approved = true; + } + + try { + this.requestService.requestAlbum({ foreignAlbumId: searchResult.foreignAlbumId }) + .subscribe(x => { + this.engineResult = x; + + if (this.engineResult.result) { + this.translate.get("Search.RequestAdded", { title: searchResult.title }).subscribe(x => { + this.notificationService.success(x); + searchResult.processed = true; + }); + } else { + if (this.engineResult.errorMessage && this.engineResult.message) { + this.notificationService.warning("Request Added", `${this.engineResult.message} - ${this.engineResult.errorMessage}`); + } else { + this.notificationService.warning("Request Added", this.engineResult.message ? this.engineResult.message : this.engineResult.errorMessage); + } + searchResult.requested = false; + searchResult.approved = false; + searchResult.processed = false; + searchResult.requestProcessing = false; + + } + }); + } catch (e) { + + searchResult.processed = false; + searchResult.requestProcessing = false; + this.notificationService.error(e); + } + } +} diff --git a/src/Ombi/ClientApp/app/search/music/artistsearch.component.html b/src/Ombi/ClientApp/app/search/music/artistsearch.component.html new file mode 100644 index 000000000..77a68a841 --- /dev/null +++ b/src/Ombi/ClientApp/app/search/music/artistsearch.component.html @@ -0,0 +1,65 @@ +
    + +
    +
    +
    + poster + +
    +
    +
    + +

    {{result.artistName}}

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

    {{result.overview | truncate: 350 }}

    + + +
    + +
    +
    +
    + + +
    +
    + + +
    + + +
    + + + + +
    \ No newline at end of file diff --git a/src/Ombi/ClientApp/app/search/music/artistsearch.component.ts b/src/Ombi/ClientApp/app/search/music/artistsearch.component.ts new file mode 100644 index 000000000..852e294e3 --- /dev/null +++ b/src/Ombi/ClientApp/app/search/music/artistsearch.component.ts @@ -0,0 +1,27 @@ +import { Component, EventEmitter, Input, Output } from "@angular/core"; + +import { ISearchAlbumResult, ISearchArtistResult } from "../../interfaces/ISearchMusicResult"; +import { SearchService } from "../../services"; + +@Component({ + selector: "artist-search", + templateUrl: "./artistsearch.component.html", +}) +export class ArtistSearchComponent { + + @Input() public result: ISearchArtistResult; + @Input() public defaultPoster: string; + public searchingAlbums: boolean; + + @Output() public viewAlbumsResult = new EventEmitter(); + + constructor(private searchService: SearchService) { + } + + public viewAllAlbums() { + this.searchingAlbums = true; + this.searchService.getAlbumsForArtist(this.result.forignArtistId).subscribe(x => { + this.viewAlbumsResult.emit(x); + }); + } +} diff --git a/src/Ombi/ClientApp/app/search/music/musicsearch.component.html b/src/Ombi/ClientApp/app/search/music/musicsearch.component.html new file mode 100644 index 000000000..b73ee553b --- /dev/null +++ b/src/Ombi/ClientApp/app/search/music/musicsearch.component.html @@ -0,0 +1,49 @@ + +
    +
    + +
    + +
    +
    +
    +
    + + + + +
    +
    + + +
    +
    +
    +
    + +
    +
    +
    + +
    +
    + +
    + +
    +
    +
    +
    +
    + +
    +
    +
    +
    + +
    +
    + + + \ No newline at end of file diff --git a/src/Ombi/ClientApp/app/search/music/musicsearch.component.ts b/src/Ombi/ClientApp/app/search/music/musicsearch.component.ts new file mode 100644 index 000000000..e022ab6c3 --- /dev/null +++ b/src/Ombi/ClientApp/app/search/music/musicsearch.component.ts @@ -0,0 +1,191 @@ +import { PlatformLocation } from "@angular/common"; +import { Component, Input, OnInit } from "@angular/core"; +import { DomSanitizer } from "@angular/platform-browser"; +import { TranslateService } from "@ngx-translate/core"; +import { Subject } from "rxjs"; +import { debounceTime, distinctUntilChanged } from "rxjs/operators"; + +import { AuthService } from "../../auth/auth.service"; +import { IIssueCategory, IRequestEngineResult, ISearchMovieResult } from "../../interfaces"; +import { ISearchAlbumResult, ISearchArtistResult } from "../../interfaces/ISearchMusicResult"; +import { NotificationService, RequestService, SearchService } from "../../services"; + +@Component({ + selector: "music-search", + templateUrl: "./musicsearch.component.html", +}) +export class MusicSearchComponent implements OnInit { + + public searchText: string; + public searchChanged: Subject = new Subject(); + public artistResult: ISearchArtistResult[]; + public albumResult: ISearchAlbumResult[]; + public result: IRequestEngineResult; + public searchApplied = false; + public searchAlbum: boolean = true; + + @Input() public issueCategories: IIssueCategory[]; + @Input() public issuesEnabled: boolean; + public issuesBarVisible = false; + public issueRequestTitle: string; + public issueRequestId: number; + public issueProviderId: string; + public issueCategorySelected: IIssueCategory; + public defaultPoster: string; + + constructor( + private searchService: SearchService, private requestService: RequestService, + private notificationService: NotificationService, private authService: AuthService, + private readonly translate: TranslateService, private sanitizer: DomSanitizer, + private readonly platformLocation: PlatformLocation) { + + this.searchChanged.pipe( + debounceTime(600), // Wait Xms after the last event before emitting last event + distinctUntilChanged(), // only emit if value is different from previous value + ).subscribe(x => { + this.searchText = x as string; + if (this.searchText === "") { + if(this.searchAlbum) { + this.clearAlbumResults(); + } else { + this.clearArtistResults(); + } + + return; + } + if(this.searchAlbum) { + if(!this.searchText) { + this.searchText = "iowa"; // REMOVE + } + this.searchService.searchAlbum(this.searchText) + .subscribe(x => { + this.albumResult = x; + this.searchApplied = true; + this.setAlbumBackground(); + }); + } else { + this.searchService.searchArtist(this.searchText) + .subscribe(x => { + this.artistResult = x; + this.searchApplied = true; + this.setArtistBackground(); + }); + } + }); + this.defaultPoster = "../../../images/default-music-placeholder.png"; + const base = this.platformLocation.getBaseHrefFromDOM(); + if (base) { + this.defaultPoster = "../../.." + base + "/images/default-music-placeholder.png"; + } + } + + public ngOnInit() { + this.searchText = ""; + this.artistResult = []; + this.result = { + message: "", + result: false, + errorMessage: "", + }; + } + + public search(text: any) { + this.searchChanged.next(text.target.value); + } + + public searchMode(val: boolean) { + this.searchAlbum = val; + if(val) { + // Album + this.clearArtistResults(); + } else { + this.clearAlbumResults(); + } + } + + public setArtistSearch(artistId: string) { + this.searchAlbum = false; + this.clearAlbumResults(); + this.searchChanged.next(`lidarr:${artistId}`); + } + + public request(searchResult: ISearchMovieResult) { + searchResult.requested = true; + searchResult.requestProcessing = true; + searchResult.showSubscribe = false; + if (this.authService.hasRole("admin") || this.authService.hasRole("AutoApproveMovie")) { + searchResult.approved = true; + } + + try { + this.requestService.requestMovie({ theMovieDbId: searchResult.id }) + .subscribe(x => { + this.result = x; + + if (this.result.result) { + this.translate.get("Search.RequestAdded", { title: searchResult.title }).subscribe(x => { + this.notificationService.success(x); + searchResult.processed = true; + }); + } else { + if (this.result.errorMessage && this.result.message) { + this.notificationService.warning("Request Added", `${this.result.message} - ${this.result.errorMessage}`); + } else { + this.notificationService.warning("Request Added", this.result.message ? this.result.message : this.result.errorMessage); + } + searchResult.requested = false; + searchResult.approved = false; + searchResult.processed = false; + searchResult.requestProcessing = false; + + } + }); + } catch (e) { + + searchResult.processed = false; + searchResult.requestProcessing = false; + this.notificationService.error(e); + } + } + + public viewAlbumsForArtist(albums: ISearchAlbumResult[]) { + this.clearArtistResults(); + this.searchAlbum = true; + this.albumResult = albums; + this.setAlbumBackground(); + } + + private clearArtistResults() { + this.artistResult = []; + this.searchApplied = false; + } + + private clearAlbumResults() { + this.albumResult = []; + this.searchApplied = false; + } + + private setArtistBackground() { + this.artistResult.forEach((val, index) => { + if (val.poster === null) { + val.poster = this.defaultPoster; + } + val.background = this.sanitizer.bypassSecurityTrustStyle + ("url(" + val.banner + ")"); + }); + } + + private setAlbumBackground() { + this.albumResult.forEach((val, index) => { + if (val.disk === null) { + if(val.cover === null) { + val.disk = this.defaultPoster; + } else { + val.disk = val.cover; + } + } + val.background = this.sanitizer.bypassSecurityTrustStyle + ("url(" + val.cover + ")"); + }); + } +} diff --git a/src/Ombi/ClientApp/app/search/search.component.html b/src/Ombi/ClientApp/app/search/search.component.html index 398bfd311..046635812 100644 --- a/src/Ombi/ClientApp/app/search/search.component.html +++ b/src/Ombi/ClientApp/app/search/search.component.html @@ -13,6 +13,9 @@
  • {{ 'Search.TvTab' | translate }}
  • +
  • + {{ 'Search.MusicTab' | translate }} +
  • @@ -25,6 +28,9 @@
    +
    + +
    diff --git a/src/Ombi/ClientApp/app/search/search.component.ts b/src/Ombi/ClientApp/app/search/search.component.ts index f583266ee..74221e71c 100644 --- a/src/Ombi/ClientApp/app/search/search.component.ts +++ b/src/Ombi/ClientApp/app/search/search.component.ts @@ -1,7 +1,7 @@ import { Component, OnInit } from "@angular/core"; -import { IIssueCategory } from "./../interfaces"; -import { IssuesService, SettingsService } from "./../services"; +import { IIssueCategory } from "../interfaces"; +import { IssuesService, SettingsService } from "../services"; @Component({ templateUrl: "./search.component.html", @@ -9,8 +9,10 @@ import { IssuesService, SettingsService } from "./../services"; export class SearchComponent implements OnInit { public showTv: boolean; public showMovie: boolean; + public showMusic: boolean; public issueCategories: IIssueCategory[]; public issuesEnabled = false; + public musicEnabled: boolean; constructor(private issuesService: IssuesService, private settingsService: SettingsService) { @@ -18,8 +20,10 @@ export class SearchComponent implements OnInit { } public ngOnInit() { + this.settingsService.getLidarr().subscribe(x => this.musicEnabled = x.enabled); this.showMovie = true; this.showTv = false; + this.showMusic = false; this.issuesService.getCategories().subscribe(x => this.issueCategories = x); this.settingsService.getIssueSettings().subscribe(x => this.issuesEnabled = x.enabled); } @@ -27,10 +31,17 @@ export class SearchComponent implements OnInit { public selectMovieTab() { this.showMovie = true; this.showTv = false; + this.showMusic = false; } public selectTvTab() { this.showMovie = false; this.showTv = true; + this.showMusic = false; + } + public selectMusicTab() { + this.showMovie = false; + this.showTv = false; + this.showMusic = true; } } diff --git a/src/Ombi/ClientApp/app/search/search.module.ts b/src/Ombi/ClientApp/app/search/search.module.ts index 855207616..7e72dba77 100644 --- a/src/Ombi/ClientApp/app/search/search.module.ts +++ b/src/Ombi/ClientApp/app/search/search.module.ts @@ -7,6 +7,9 @@ import { NgbModule } from "@ng-bootstrap/ng-bootstrap"; import { MovieSearchComponent } from "./moviesearch.component"; import { MovieSearchGridComponent } from "./moviesearchgrid.component"; +import { AlbumSearchComponent } from "./music/albumsearch.component"; +import { ArtistSearchComponent } from "./music/artistsearch.component"; +import { MusicSearchComponent } from "./music/musicsearch.component"; import { SearchComponent } from "./search.component"; import { SeriesInformationComponent } from "./seriesinformation.component"; import { TvSearchComponent } from "./tvsearch.component"; @@ -41,6 +44,9 @@ const routes: Routes = [ TvSearchComponent, SeriesInformationComponent, MovieSearchGridComponent, + MusicSearchComponent, + ArtistSearchComponent, + AlbumSearchComponent, ], exports: [ RouterModule, diff --git a/src/Ombi/ClientApp/app/services/applications/index.ts b/src/Ombi/ClientApp/app/services/applications/index.ts index 9fe4a5403..295a53415 100644 --- a/src/Ombi/ClientApp/app/services/applications/index.ts +++ b/src/Ombi/ClientApp/app/services/applications/index.ts @@ -6,3 +6,4 @@ export * from "./sonarr.service"; export * from "./tester.service"; export * from "./plexoauth.service"; export * from "./plextv.service"; +export * from "./lidarr.service"; diff --git a/src/Ombi/ClientApp/app/services/applications/lidarr.service.ts b/src/Ombi/ClientApp/app/services/applications/lidarr.service.ts new file mode 100644 index 000000000..9ef36357a --- /dev/null +++ b/src/Ombi/ClientApp/app/services/applications/lidarr.service.ts @@ -0,0 +1,36 @@ +import { PlatformLocation } from "@angular/common"; +import { HttpClient } from "@angular/common/http"; +import { Injectable } from "@angular/core"; +import { Observable } from "rxjs"; + +import { ILidarrProfile, ILidarrRootFolder, IProfiles } from "../../interfaces"; +import { ILidarrSettings } from "../../interfaces"; +import { ServiceHelpers } from "../service.helpers"; + +@Injectable() +export class LidarrService extends ServiceHelpers { + constructor(http: HttpClient, public platformLocation: PlatformLocation) { + super(http, "/api/v1/Lidarr", platformLocation); + } + + public getRootFolders(settings: ILidarrSettings): Observable { + return this.http.post(`${this.url}/RootFolders/`, JSON.stringify(settings), {headers: this.headers}); + } + public getQualityProfiles(settings: ILidarrSettings): Observable { + return this.http.post(`${this.url}/Profiles/`, JSON.stringify(settings), {headers: this.headers}); + } + + public getRootFoldersFromSettings(): Observable { + return this.http.get(`${this.url}/RootFolders/`, {headers: this.headers}); + } + public getQualityProfilesFromSettings(): Observable { + return this.http.get(`${this.url}/Profiles/`, {headers: this.headers}); + } + + public getMetadataProfiles(settings: ILidarrSettings): Observable { + return this.http.post(`${this.url}/Metadata/`, JSON.stringify(settings), {headers: this.headers}); + } + public getLanguages(settings: ILidarrSettings): Observable { + return this.http.post(`${this.url}/Langauges/`,JSON.stringify(settings), {headers: this.headers}); + } +} diff --git a/src/Ombi/ClientApp/app/services/mobile.service.ts b/src/Ombi/ClientApp/app/services/mobile.service.ts index 29cf5f609..1f9e3fb24 100644 --- a/src/Ombi/ClientApp/app/services/mobile.service.ts +++ b/src/Ombi/ClientApp/app/services/mobile.service.ts @@ -4,7 +4,7 @@ import { Injectable } from "@angular/core"; import { HttpClient } from "@angular/common/http"; import { Observable } from "rxjs"; -import { IMobileUsersViewModel } from "./../interfaces"; +import { IMobileUsersViewModel } from "../interfaces"; import { ServiceHelpers } from "./service.helpers"; @Injectable() diff --git a/src/Ombi/ClientApp/app/services/notificationMessage.service.ts b/src/Ombi/ClientApp/app/services/notificationMessage.service.ts index 1e4603689..93727c5d2 100644 --- a/src/Ombi/ClientApp/app/services/notificationMessage.service.ts +++ b/src/Ombi/ClientApp/app/services/notificationMessage.service.ts @@ -4,7 +4,7 @@ import { Injectable } from "@angular/core"; import { HttpClient } from "@angular/common/http"; import { Observable } from "rxjs"; -import { IMassEmailModel } from "./../interfaces"; +import { IMassEmailModel } from "../interfaces"; import { ServiceHelpers } from "./service.helpers"; diff --git a/src/Ombi/ClientApp/app/services/recentlyAdded.service.ts b/src/Ombi/ClientApp/app/services/recentlyAdded.service.ts index 18e24470c..c062f973b 100644 --- a/src/Ombi/ClientApp/app/services/recentlyAdded.service.ts +++ b/src/Ombi/ClientApp/app/services/recentlyAdded.service.ts @@ -4,7 +4,7 @@ import { Injectable } from "@angular/core"; import { HttpClient } from "@angular/common/http"; import { Observable } from "rxjs"; -import { IRecentlyAddedMovies, IRecentlyAddedTvShows } from "./../interfaces"; +import { IRecentlyAddedMovies, IRecentlyAddedTvShows } from "../interfaces"; import { ServiceHelpers } from "./service.helpers"; @Injectable() diff --git a/src/Ombi/ClientApp/app/services/request.service.ts b/src/Ombi/ClientApp/app/services/request.service.ts index 48fa5622d..039939a84 100644 --- a/src/Ombi/ClientApp/app/services/request.service.ts +++ b/src/Ombi/ClientApp/app/services/request.service.ts @@ -5,7 +5,8 @@ import { HttpClient } from "@angular/common/http"; import { Observable } from "rxjs"; import { TreeNode } from "primeng/primeng"; -import { FilterType, IChildRequests, IFilter, IMovieRequestModel, IMovieRequests, IMovieUpdateModel, IRequestEngineResult, IRequestsViewModel, ITvRequests, ITvUpdateModel, OrderType } from "../interfaces"; +import { FilterType, IAlbumRequest, IAlbumRequestModel, IAlbumUpdateModel, IChildRequests, IFilter, IMovieRequestModel, IMovieRequests, + IMovieUpdateModel, IRequestEngineResult, IRequestsViewModel, ITvRequests, ITvUpdateModel, OrderType } from "../interfaces"; import { ITvRequestViewModel } from "../interfaces"; import { ServiceHelpers } from "./service.helpers"; @@ -132,4 +133,42 @@ export class RequestService extends ServiceHelpers { public setRootFolder(requestId: number, rootFolderId: number): Observable { return this.http.put(`${this.url}tv/root/${requestId}/${rootFolderId}`, {headers: this.headers}); } + + // Music + public requestAlbum(Album: IAlbumRequestModel): Observable { + return this.http.post(`${this.url}music/`, JSON.stringify(Album), {headers: this.headers}); + } + + public getTotalAlbums(): Observable { + return this.http.get(`${this.url}music/total`, {headers: this.headers}); + } + + public approveAlbum(Album: IAlbumUpdateModel): Observable { + return this.http.post(`${this.url}music/Approve`, JSON.stringify(Album), {headers: this.headers}); + } + + public denyAlbum(Album: IAlbumUpdateModel): Observable { + return this.http.put(`${this.url}music/Deny`, JSON.stringify(Album), {headers: this.headers}); + } + + public markAlbumAvailable(Album: IAlbumUpdateModel): Observable { + return this.http.post(`${this.url}music/available`, JSON.stringify(Album), {headers: this.headers}); + } + + public markAlbumUnavailable(Album: IAlbumUpdateModel): Observable { + return this.http.post(`${this.url}music/unavailable`, JSON.stringify(Album), {headers: this.headers}); + } + + public getAlbumRequests(count: number, position: number, order: OrderType, filter: IFilter): Observable> { + return this.http.get>(`${this.url}music/${count}/${position}/${order}/${filter.statusFilter}/${filter.availabilityFilter}`, {headers: this.headers}); + } + + public searchAlbumRequests(search: string): Observable { + return this.http.get(`${this.url}music/search/${search}`, {headers: this.headers}); + } + + public removeAlbumRequest(request: IAlbumRequest): any { + this.http.delete(`${this.url}music/${request.id}`, {headers: this.headers}).subscribe(); + } + } diff --git a/src/Ombi/ClientApp/app/services/search.service.ts b/src/Ombi/ClientApp/app/services/search.service.ts index 4454bda0a..9769ad229 100644 --- a/src/Ombi/ClientApp/app/services/search.service.ts +++ b/src/Ombi/ClientApp/app/services/search.service.ts @@ -7,6 +7,7 @@ import { Observable } from "rxjs"; import { TreeNode } from "primeng/primeng"; import { ISearchMovieResult } from "../interfaces"; import { ISearchTvResult } from "../interfaces"; +import { ISearchAlbumResult, ISearchArtistResult } from "../interfaces/ISearchMusicResult"; import { ServiceHelpers } from "./service.helpers"; @Injectable() @@ -68,4 +69,14 @@ export class SearchService extends ServiceHelpers { public trendingTv(): Observable { return this.http.get(`${this.url}/Tv/trending`, {headers: this.headers}); } + // Music + public searchArtist(searchTerm: string): Observable { + return this.http.get(`${this.url}/Music/Artist/` + searchTerm); + } + public searchAlbum(searchTerm: string): Observable { + return this.http.get(`${this.url}/Music/Album/` + searchTerm); + } + public getAlbumsForArtist(foreignArtistId: string): Observable { + return this.http.get(`${this.url}/Music/Artist/Album/${foreignArtistId}`); + } } diff --git a/src/Ombi/ClientApp/app/services/settings.service.ts b/src/Ombi/ClientApp/app/services/settings.service.ts index 27053d1c9..2016d10b7 100644 --- a/src/Ombi/ClientApp/app/services/settings.service.ts +++ b/src/Ombi/ClientApp/app/services/settings.service.ts @@ -18,6 +18,7 @@ import { IJobSettings, IJobSettingsViewModel, ILandingPageSettings, + ILidarrSettings, IMattermostNotifcationSettings, IMobileNotifcationSettings, INewsletterNotificationSettings, @@ -91,6 +92,14 @@ export class SettingsService extends ServiceHelpers { return this.http.post(`${this.url}/Radarr`, JSON.stringify(settings), {headers: this.headers}); } + public getLidarr(): Observable { + return this.http.get(`${this.url}/Lidarr`, {headers: this.headers}); + } + + public saveLidarr(settings: ILidarrSettings): Observable { + return this.http.post(`${this.url}/Lidarr`, JSON.stringify(settings), {headers: this.headers}); + } + public getAuthentication(): Observable { return this.http.get(`${this.url}/Authentication`, {headers: this.headers}); } diff --git a/src/Ombi/ClientApp/app/settings/jobs/jobs.component.html b/src/Ombi/ClientApp/app/settings/jobs/jobs.component.html index 947739d6f..a4dcd6fb3 100644 --- a/src/Ombi/ClientApp/app/settings/jobs/jobs.component.html +++ b/src/Ombi/ClientApp/app/settings/jobs/jobs.component.html @@ -22,12 +22,19 @@
    - - - The Radarr Sync is required - -
    + + + The Radarr Sync is required + + +
    + + + The Lidarr Sync is required + +
    +
    diff --git a/src/Ombi/ClientApp/app/settings/jobs/jobs.component.ts b/src/Ombi/ClientApp/app/settings/jobs/jobs.component.ts index d0a7a8b83..756d6ba89 100644 --- a/src/Ombi/ClientApp/app/settings/jobs/jobs.component.ts +++ b/src/Ombi/ClientApp/app/settings/jobs/jobs.component.ts @@ -3,7 +3,7 @@ import { FormBuilder, FormGroup, Validators } from "@angular/forms"; import { NotificationService, SettingsService } from "../../services"; -import { ICronTestModel } from "./../../interfaces"; +import { ICronTestModel } from "../../interfaces"; @Component({ templateUrl: "./jobs.component.html", @@ -34,6 +34,7 @@ export class JobsComponent implements OnInit { refreshMetadata: [x.refreshMetadata, Validators.required], newsletter: [x.newsletter, Validators.required], plexRecentlyAddedSync: [x.plexRecentlyAddedSync, Validators.required], + lidarrArtistSync: [x.lidarrArtistSync, Validators.required], }); }); } diff --git a/src/Ombi/ClientApp/app/settings/lidarr/lidarr.component.html b/src/Ombi/ClientApp/app/settings/lidarr/lidarr.component.html new file mode 100644 index 000000000..aa3dbaa66 --- /dev/null +++ b/src/Ombi/ClientApp/app/settings/lidarr/lidarr.component.html @@ -0,0 +1,122 @@ + + +
    +
    + Lidarr Settings +
    +
    +
    +
    + + +
    +
    + + + +
    + + + + The IP/Hostname is required +
    + +
    + + + + The Port is required +
    + + +
    + + + + The API Key is required +
    +
    +
    + + + +
    +
    +
    + +
    + +
    +
    +
    +
    + +
    + +
    + + + +
    + A Default Quality Profile is required +
    + +
    + +
    + + + +
    + A Default Root Path is required +
    + + +
    + +
    + + + +
    + A Language profile is required +
    + + +
    + +
    + + + + +
    + + A Metadata profile is required +
    + + +
    +
    + +
    +
    + + +
    +
    + +
    +
    +
    +
    +
    +
    diff --git a/src/Ombi/ClientApp/app/settings/lidarr/lidarr.component.ts b/src/Ombi/ClientApp/app/settings/lidarr/lidarr.component.ts new file mode 100644 index 000000000..8100c0194 --- /dev/null +++ b/src/Ombi/ClientApp/app/settings/lidarr/lidarr.component.ts @@ -0,0 +1,157 @@ +import { Component, OnInit } from "@angular/core"; +import { FormBuilder, FormGroup, Validators } from "@angular/forms"; + +import { ILidarrSettings, IMinimumAvailability, IProfiles, IRadarrProfile, IRadarrRootFolder } from "../../interfaces"; +import { IRadarrSettings } from "../../interfaces"; +import { LidarrService, TesterService } from "../../services"; +import { NotificationService } from "../../services"; +import { SettingsService } from "../../services"; + +@Component({ + templateUrl: "./Lidarr.component.html", +}) +export class LidarrComponent implements OnInit { + + public qualities: IRadarrProfile[]; + public languageProfiles: IProfiles[]; + public metadataProfiles: IProfiles[]; + public rootFolders: IRadarrRootFolder[]; + public minimumAvailabilityOptions: IMinimumAvailability[]; + public profilesRunning: boolean; + public rootFoldersRunning: boolean; + public metadataRunning: boolean; + public languageRunning: boolean; + public advanced = false; + public form: FormGroup; + + constructor(private settingsService: SettingsService, + private lidarrService: LidarrService, + private notificationService: NotificationService, + private fb: FormBuilder, + private testerService: TesterService) { } + + public ngOnInit() { + this.settingsService.getLidarr() + .subscribe(x => { + + this.form = this.fb.group({ + enabled: [x.enabled], + apiKey: [x.apiKey, [Validators.required]], + defaultQualityProfile: [x.defaultQualityProfile, [Validators.required]], + defaultRootPath: [x.defaultRootPath, [Validators.required]], + ssl: [x.ssl], + subDir: [x.subDir], + ip: [x.ip, [Validators.required]], + port: [x.port, [Validators.required]], + albumFolder: [x.albumFolder], + languageProfileId: [x.languageProfileId, [Validators.required]], + metadataProfileId: [x.metadataProfileId, [Validators.required]], + }); + + if (x.defaultQualityProfile) { + this.getProfiles(this.form); + } + if (x.defaultRootPath) { + this.getRootFolders(this.form); + } + if (x.languageProfileId) { + this.getLanguageProfiles(this.form); + } + if (x.metadataProfileId) { + this.getMetadataProfiles(this.form); + } + }); + + this.qualities = []; + this.qualities.push({ name: "Please Select", id: -1 }); + + this.rootFolders = []; + this.rootFolders.push({ path: "Please Select", id: -1 }); + + this.languageProfiles = []; + this.languageProfiles.push({ name: "Please Select", id: -1 }); + + this.metadataProfiles = []; + this.metadataProfiles.push({ name: "Please Select", id: -1 }); + } + + public getProfiles(form: FormGroup) { + this.profilesRunning = true; + this.lidarrService.getQualityProfiles(form.value).subscribe(x => { + this.qualities = x; + this.qualities.unshift({ name: "Please Select", id: -1 }); + + this.profilesRunning = false; + this.notificationService.success("Successfully retrieved the Quality Profiles"); + }); + } + + public getRootFolders(form: FormGroup) { + this.rootFoldersRunning = true; + this.lidarrService.getRootFolders(form.value).subscribe(x => { + this.rootFolders = x; + this.rootFolders.unshift({ path: "Please Select", id: -1 }); + + this.rootFoldersRunning = false; + this.notificationService.success("Successfully retrieved the Root Folders"); + }); + } + + public getMetadataProfiles(form: FormGroup) { + this.metadataRunning = true; + this.lidarrService.getMetadataProfiles(form.value).subscribe(x => { + this.metadataProfiles = x; + this.metadataProfiles.unshift({ name: "Please Select", id: -1 }); + + this.metadataRunning = false; + this.notificationService.success("Successfully retrieved the Metadata profiles"); + }); + } + + public getLanguageProfiles(form: FormGroup) { + this.languageRunning = true; + this.lidarrService.getLanguages(form.value).subscribe(x => { + this.languageProfiles = x; + this.languageProfiles.unshift({ name: "Please Select", id: -1 }); + + this.languageRunning = false; + this.notificationService.success("Successfully retrieved the Language profiles"); + }); + } + + public test(form: FormGroup) { + if (form.invalid) { + this.notificationService.error("Please check your entered values"); + return; + } + const settings = form.value; + this.testerService.radarrTest(settings).subscribe(x => { + if (x === true) { + this.notificationService.success("Successfully connected to Lidarr!"); + } else { + this.notificationService.error("We could not connect to Lidarr!"); + } + }); + } + + public onSubmit(form: FormGroup) { + if (form.invalid) { + this.notificationService.error("Please check your entered values"); + return; + } + if (form.controls.defaultQualityProfile.value === "-1" || form.controls.defaultRootPath.value === "Please Select") { + this.notificationService.error("Please check your entered values"); + return; + } + + const settings = form.value; + this.settingsService.saveLidarr(settings).subscribe(x => { + if (x) { + this.notificationService.success("Successfully saved Lidarr settings"); + } else { + this.notificationService.success("There was an error when saving the Lidarr settings"); + } + }); + + } +} diff --git a/src/Ombi/ClientApp/app/settings/notifications/newsletter.component.ts b/src/Ombi/ClientApp/app/settings/notifications/newsletter.component.ts index 06ed6617a..eae7176e2 100644 --- a/src/Ombi/ClientApp/app/settings/notifications/newsletter.component.ts +++ b/src/Ombi/ClientApp/app/settings/notifications/newsletter.component.ts @@ -2,7 +2,7 @@ import { INewsletterNotificationSettings, NotificationType } from "../../interfaces"; import { JobService, NotificationService, SettingsService } from "../../services"; -import { TesterService } from "./../../services/applications/tester.service"; +import { TesterService } from "../../services/applications/tester.service"; @Component({ templateUrl: "./newsletter.component.html", diff --git a/src/Ombi/ClientApp/app/settings/settings.module.ts b/src/Ombi/ClientApp/app/settings/settings.module.ts index 1cdcb45db..f102a03fe 100644 --- a/src/Ombi/ClientApp/app/settings/settings.module.ts +++ b/src/Ombi/ClientApp/app/settings/settings.module.ts @@ -8,7 +8,7 @@ import { ClipboardModule } from "ngx-clipboard"; import { AuthGuard } from "../auth/auth.guard"; import { AuthService } from "../auth/auth.service"; import { - CouchPotatoService, EmbyService, IssuesService, JobService, MobileService, NotificationMessageService, PlexService, RadarrService, + CouchPotatoService, EmbyService, IssuesService, JobService, LidarrService, MobileService, NotificationMessageService, PlexService, RadarrService, SonarrService, TesterService, ValidationService, } from "../services"; @@ -22,6 +22,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 { LidarrComponent } from "./lidarr/lidarr.component"; import { MassEmailComponent } from "./massemail/massemail.component"; import { DiscordComponent } from "./notifications/discord.component"; import { EmailNotificationComponent } from "./notifications/emailnotification.component"; @@ -73,6 +74,7 @@ const routes: Routes = [ { path: "Mobile", component: MobileComponent, canActivate: [AuthGuard] }, { path: "MassEmail", component: MassEmailComponent, canActivate: [AuthGuard] }, { path: "Newsletter", component: NewsletterComponent, canActivate: [AuthGuard] }, + { path: "Lidarr", component: LidarrComponent, canActivate: [AuthGuard] }, ]; @NgModule({ @@ -124,6 +126,7 @@ const routes: Routes = [ MobileComponent, MassEmailComponent, NewsletterComponent, + LidarrComponent, ], exports: [ RouterModule, @@ -142,6 +145,7 @@ const routes: Routes = [ EmbyService, MobileService, NotificationMessageService, + LidarrService, ], }) diff --git a/src/Ombi/ClientApp/app/settings/settingsmenu.component.html b/src/Ombi/ClientApp/app/settings/settingsmenu.component.html index 6c6bb5c3f..a8d89ab1c 100644 --- a/src/Ombi/ClientApp/app/settings/settingsmenu.component.html +++ b/src/Ombi/ClientApp/app/settings/settingsmenu.component.html @@ -48,6 +48,15 @@ + +