diff --git a/CHANGELOG.md b/CHANGELOG.md index 91ef9d69d..5f39765e5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,8 +4,41 @@ ### **New Features** +- Finished adding subscriptions for TV Shows. [Jamie Rees] + +- Added the test button for mobile notifications. [Jamie Rees] + +- Added classes to donation html elements. [Anojh] + +- Enhanced newsletter styling to support more mail clients. [Anojh] + +- Improved the way we sync the plex content and then get the metadata. #2243. [Jamie Rees] + +### **Fixes** + +- Fixed #2257. [Jamie Rees] + +- Fixed the issue when enabling the Hide Request Users included system users e.g. API key user #2232. [Jamie Rees] + +- Fix #2167. [Anojh] + +- Fix #2228. [Anojh] + +- Fix #2246. [Anojh] + +- Fix #2234. [Anojh] + +- Fixed that sometimes there would be a hidden error on the login page. [Jamie Rees] + + +## v3.0.3304 (2018-05-09) + +### **New Features** + - Updated to prevent security vulnerability as noted here: https://github.com/aspnet/Announcements/issues/300. [Jamie Rees] +- Update README.md. [Jamie] + ### **Fixes** - [LC] - Added classes to root/quality override divs. [Anojh] diff --git a/README.md b/README.md index 1c2dc9559..2049af360 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,9 @@ ____ [![Patreon](https://img.shields.io/badge/patreon-donate-yellow.svg)](https://patreon.com/tidusjar/Ombi) [![Paypal](https://img.shields.io/badge/paypal-donate-yellow.svg)](https://paypal.me/PlexRequestsNet) +___ +Get it on Google Play + ___ We also now have merch up on Teespring! diff --git a/src/Ombi.Core/Engine/BaseMediaEngine.cs b/src/Ombi.Core/Engine/BaseMediaEngine.cs index 552b2ac38..2eab74b75 100644 --- a/src/Ombi.Core/Engine/BaseMediaEngine.cs +++ b/src/Ombi.Core/Engine/BaseMediaEngine.cs @@ -9,13 +9,12 @@ using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; using Ombi.Core.Rule.Interfaces; using Ombi.Store.Entities.Requests; -using Ombi.Store.Repository; using Ombi.Store.Repository.Requests; -using Ombi.Store.Entities; -using Microsoft.AspNetCore.Identity; using Ombi.Core.Authentication; using Ombi.Core.Settings; using Ombi.Settings.Settings.Models; +using Ombi.Store.Entities; +using Ombi.Store.Repository; namespace Ombi.Core.Engine { @@ -26,11 +25,12 @@ namespace Ombi.Core.Engine private Dictionary _dbTv; protected BaseMediaEngine(IPrincipal identity, IRequestServiceMain requestService, - IRuleEvaluator rules, OmbiUserManager um, ICacheService cache, ISettingsService ombiSettings) : base(identity, um, rules) + IRuleEvaluator rules, OmbiUserManager um, ICacheService cache, ISettingsService ombiSettings, IRepository sub) : base(identity, um, rules) { RequestService = requestService; Cache = cache; OmbiSettings = ombiSettings; + _subscriptionRepository = sub; } protected IRequestServiceMain RequestService { get; } @@ -38,6 +38,7 @@ namespace Ombi.Core.Engine protected ITvRequestRepository TvRepository => RequestService.TvRequestService; protected readonly ICacheService Cache; protected readonly ISettingsService OmbiSettings; + protected readonly IRepository _subscriptionRepository; protected async Task> GetMovieRequests() { @@ -78,7 +79,7 @@ namespace Ombi.Core.Engine var pendingTv = 0; var approvedTv = 0; - var availableTv = 0; + var availableTv = 0; foreach (var tv in tvQuery) { foreach (var child in tv.ChildRequests) @@ -108,21 +109,51 @@ namespace Ombi.Core.Engine protected async Task HideFromOtherUsers() { - if (await IsInRole(OmbiRoles.Admin) || await IsInRole(OmbiRoles.PowerUser)) + var user = await GetUser(); + if (await IsInRole(OmbiRoles.Admin) || await IsInRole(OmbiRoles.PowerUser) || user.IsSystemUser) { - return new HideResult(); + return new HideResult + { + UserId = user.Id + }; } var settings = await Cache.GetOrAdd(CacheKeys.OmbiSettings, async () => await OmbiSettings.GetSettingsAsync()); var result = new HideResult { - Hide = settings.HideRequestsUsers + Hide = settings.HideRequestsUsers, + UserId = user.Id + }; + return result; + } + + public async Task SubscribeToRequest(int requestId, RequestType type) + { + var user = await GetUser(); + var existingSub = await _subscriptionRepository.GetAll().FirstOrDefaultAsync(x => + x.UserId.Equals(user.Id) && x.RequestId == requestId && x.RequestType == type); + if (existingSub != null) + { + return; + } + var sub = new RequestSubscription + { + UserId = user.Id, + RequestId = requestId, + RequestType = type }; - if (settings.HideRequestsUsers) + + await _subscriptionRepository.Add(sub); + } + + public async Task UnSubscribeRequest(int requestId, RequestType type) + { + var user = await GetUser(); + var existingSub = await _subscriptionRepository.GetAll().FirstOrDefaultAsync(x => + x.UserId.Equals(user.Id) && x.RequestId == requestId && x.RequestType == type); + if (existingSub != null) { - var user = await GetUser(); - result.UserId = user.Id; + await _subscriptionRepository.Delete(existingSub); } - return result; } public class HideResult diff --git a/src/Ombi.Core/Engine/Interfaces/IMovieRequestEngine.cs b/src/Ombi.Core/Engine/Interfaces/IMovieRequestEngine.cs index 08ec9b594..c1ba76a7c 100644 --- a/src/Ombi.Core/Engine/Interfaces/IMovieRequestEngine.cs +++ b/src/Ombi.Core/Engine/Interfaces/IMovieRequestEngine.cs @@ -18,6 +18,7 @@ namespace Ombi.Core.Engine.Interfaces Task ApproveMovieById(int requestId); Task DenyMovieById(int modelId); Task> Filter(FilterViewModel vm); + } } \ No newline at end of file diff --git a/src/Ombi.Core/Engine/Interfaces/IRequestEngine.cs b/src/Ombi.Core/Engine/Interfaces/IRequestEngine.cs index 5dbf6e449..553ad79dd 100644 --- a/src/Ombi.Core/Engine/Interfaces/IRequestEngine.cs +++ b/src/Ombi.Core/Engine/Interfaces/IRequestEngine.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using System.Threading.Tasks; using Ombi.Core.Models.Requests; +using Ombi.Store.Entities; namespace Ombi.Core.Engine.Interfaces { @@ -18,5 +19,7 @@ namespace Ombi.Core.Engine.Interfaces Task MarkUnavailable(int modelId); Task MarkAvailable(int modelId); Task GetTotal(); + Task UnSubscribeRequest(int requestId, RequestType type); + Task SubscribeToRequest(int requestId, RequestType type); } } \ No newline at end of file diff --git a/src/Ombi.Core/Engine/MovieRequestEngine.cs b/src/Ombi.Core/Engine/MovieRequestEngine.cs index 3d88b7230..460752bb1 100644 --- a/src/Ombi.Core/Engine/MovieRequestEngine.cs +++ b/src/Ombi.Core/Engine/MovieRequestEngine.cs @@ -25,7 +25,8 @@ namespace Ombi.Core.Engine { public MovieRequestEngine(IMovieDbApi movieApi, IRequestServiceMain requestService, IPrincipal user, INotificationHelper helper, IRuleEvaluator r, IMovieSender sender, ILogger log, - OmbiUserManager manager, IRepository rl, ICacheService cache, ISettingsService ombiSettings) : base(user, requestService, r, manager, cache, ombiSettings) + OmbiUserManager manager, IRepository rl, ICacheService cache, ISettingsService ombiSettings, IRepository sub) + : base(user, requestService, r, manager, cache, ombiSettings, sub) { MovieApi = movieApi; NotificationHelper = helper; @@ -137,9 +138,10 @@ namespace Ombi.Core.Engine { allRequests = await MovieRepository.GetWithUser().Skip(position).Take(count).OrderByDescending(x => x.ReleaseDate).ToListAsync(); } - allRequests.ForEach(x => + allRequests.ForEach(async x => { x.PosterPath = PosterPathHelper.FixPosterPath(x.PosterPath); + await CheckForSubscription(shouldHide, x); }); return allRequests; } @@ -173,9 +175,30 @@ namespace Ombi.Core.Engine { allRequests = await MovieRepository.GetWithUser().ToListAsync(); } + + allRequests.ForEach(async x => + { + x.PosterPath = PosterPathHelper.FixPosterPath(x.PosterPath); + await CheckForSubscription(shouldHide, x); + }); return allRequests; } + private async Task CheckForSubscription(HideResult shouldHide, MovieRequests 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.Movie); + x.Subscribed = sub != null; + } + } + /// /// Searches the movie request. /// @@ -194,9 +217,10 @@ namespace Ombi.Core.Engine allRequests = await MovieRepository.GetWithUser().ToListAsync(); } var results = allRequests.Where(x => x.Title.Contains(search, CompareOptions.IgnoreCase)).ToList(); - results.ForEach(x => + results.ForEach(async x => { x.PosterPath = PosterPathHelper.FixPosterPath(x.PosterPath); + await CheckForSubscription(shouldHide, x); }); return results; } diff --git a/src/Ombi.Core/Engine/MovieSearchEngine.cs b/src/Ombi.Core/Engine/MovieSearchEngine.cs index 448ff9235..915eefe02 100644 --- a/src/Ombi.Core/Engine/MovieSearchEngine.cs +++ b/src/Ombi.Core/Engine/MovieSearchEngine.cs @@ -15,14 +15,16 @@ using Ombi.Core.Authentication; using Ombi.Core.Settings; using Ombi.Helpers; using Ombi.Settings.Settings.Models; +using Ombi.Store.Entities; +using Ombi.Store.Repository; namespace Ombi.Core.Engine { public class MovieSearchEngine : BaseMediaEngine, IMovieEngine { public MovieSearchEngine(IPrincipal identity, IRequestServiceMain service, IMovieDbApi movApi, IMapper mapper, - ILogger logger, IRuleEvaluator r, OmbiUserManager um, ICacheService mem, ISettingsService s) - : base(identity, service, r, um, mem, s) + ILogger logger, IRuleEvaluator r, OmbiUserManager um, ICacheService mem, ISettingsService s, IRepository sub) + : base(identity, service, r, um, mem, s, sub) { MovieApi = movApi; Mapper = mapper; diff --git a/src/Ombi.Core/Engine/TvRequestEngine.cs b/src/Ombi.Core/Engine/TvRequestEngine.cs index 2ce4da292..d74b45e5e 100644 --- a/src/Ombi.Core/Engine/TvRequestEngine.cs +++ b/src/Ombi.Core/Engine/TvRequestEngine.cs @@ -29,7 +29,8 @@ namespace Ombi.Core.Engine { public TvRequestEngine(ITvMazeApi tvApi, IMovieDbApi movApi, IRequestServiceMain requestService, IPrincipal user, INotificationHelper helper, IRuleEvaluator rule, OmbiUserManager manager, - ITvSender sender, IAuditRepository audit, IRepository rl, ISettingsService settings, ICacheService cache) : base(user, requestService, rule, manager, cache, settings) + ITvSender sender, IAuditRepository audit, IRepository rl, ISettingsService settings, ICacheService cache, + IRepository sub) : base(user, requestService, rule, manager, cache, settings, sub) { TvApi = tvApi; MovieDbApi = movApi; @@ -156,6 +157,8 @@ namespace Ombi.Core.Engine .Skip(position).Take(count).OrderByDescending(x => x.ReleaseDate).ToListAsync(); } + allRequests.ForEach(async r => { await CheckForSubscription(shouldHide, r); }); + return allRequests; } @@ -181,25 +184,28 @@ namespace Ombi.Core.Engine .ThenInclude(x => x.Episodes) .Skip(position).Take(count).ToListAsync(); } + + allRequests.ForEach(async r => { await CheckForSubscription(shouldHide, r); }); return ParseIntoTreeNode(allRequests); } public async Task> GetRequests() { var shouldHide = await HideFromOtherUsers(); - IQueryable allRequests; + List allRequests; if (shouldHide.Hide) { - allRequests = TvRepository.Get(shouldHide.UserId); + allRequests = await TvRepository.Get(shouldHide.UserId).ToListAsync(); FilterChildren(allRequests, shouldHide); } else { - allRequests = TvRepository.Get(); + allRequests = await TvRepository.Get().ToListAsync(); } - return await allRequests.ToListAsync(); + allRequests.ForEach(async r => { await CheckForSubscription(shouldHide, r); }); + return allRequests; } private static void FilterChildren(IEnumerable allRequests, HideResult shouldHide) @@ -232,6 +238,8 @@ namespace Ombi.Core.Engine allRequests = await TvRepository.GetChild().Include(x => x.SeasonRequests).Where(x => x.ParentRequestId == tvId).ToListAsync(); } + allRequests.ForEach(async r => { await CheckForSubscription(shouldHide, r); }); + return allRequests; } @@ -248,6 +256,8 @@ namespace Ombi.Core.Engine allRequests = TvRepository.Get(); } var results = await allRequests.Where(x => x.Title.Contains(search, CompareOptions.IgnoreCase)).ToListAsync(); + + results.ForEach(async r => { await CheckForSubscription(shouldHide, r); }); return results; } @@ -264,6 +274,7 @@ namespace Ombi.Core.Engine allRequests = TvRepository.Get(); } var results = await allRequests.Where(x => x.Title.Contains(search, CompareOptions.IgnoreCase)).ToListAsync(); + results.ForEach(async r => { await CheckForSubscription(shouldHide, r); }); return ParseIntoTreeNode(results); } @@ -445,6 +456,29 @@ namespace Ombi.Core.Engine } } + private async Task CheckForSubscription(HideResult shouldHide, TvRequests x) + { + foreach (var tv in x.ChildRequests) + { + await CheckForSubscription(shouldHide, tv); + } + } + + private async Task CheckForSubscription(HideResult shouldHide, ChildRequests 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.TvShow); + x.Subscribed = sub != null; + } + } + private async Task AddExistingRequest(ChildRequests newRequest, TvRequests existingRequest) { // Add the child diff --git a/src/Ombi.Core/Engine/TvSearchEngine.cs b/src/Ombi.Core/Engine/TvSearchEngine.cs index 1664ecfa7..bc5a2e984 100644 --- a/src/Ombi.Core/Engine/TvSearchEngine.cs +++ b/src/Ombi.Core/Engine/TvSearchEngine.cs @@ -20,6 +20,7 @@ using Microsoft.Extensions.Caching.Memory; using Ombi.Core.Authentication; using Ombi.Helpers; using Ombi.Settings.Settings.Models; +using Ombi.Store.Entities; namespace Ombi.Core.Engine { @@ -27,8 +28,8 @@ namespace Ombi.Core.Engine { public TvSearchEngine(IPrincipal identity, IRequestServiceMain service, ITvMazeApi tvMaze, IMapper mapper, ISettingsService plexSettings, ISettingsService embySettings, IPlexContentRepository repo, IEmbyContentRepository embyRepo, ITraktApi trakt, IRuleEvaluator r, OmbiUserManager um, - ICacheService memCache, ISettingsService s) - : base(identity, service, r, um, memCache, s) + ICacheService memCache, ISettingsService s, IRepository sub) + : base(identity, service, r, um, memCache, s, sub) { TvMazeApi = tvMaze; Mapper = mapper; diff --git a/src/Ombi.DependencyInjection/IocExtensions.cs b/src/Ombi.DependencyInjection/IocExtensions.cs index 68f4b7218..dcae39cb0 100644 --- a/src/Ombi.DependencyInjection/IocExtensions.cs +++ b/src/Ombi.DependencyInjection/IocExtensions.cs @@ -152,6 +152,7 @@ namespace Ombi.DependencyInjection services.AddTransient(); services.AddTransient(); services.AddTransient(); + services.AddTransient(); services.AddTransient(); } diff --git a/src/Ombi.Notifications/Agents/DiscordNotification.cs b/src/Ombi.Notifications/Agents/DiscordNotification.cs index 0a5d5dabc..66280ef70 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) - : base(sn, r, m, t,s,log) + IMovieRequestRepository m, ITvRequestRepository t, ISettingsService s, IRepository sub) + : base(sn, r, m, t,s,log, sub) { Api = api; Logger = log; diff --git a/src/Ombi.Notifications/Agents/EmailNotification.cs b/src/Ombi.Notifications/Agents/EmailNotification.cs index d8f005e2b..53046ade0 100644 --- a/src/Ombi.Notifications/Agents/EmailNotification.cs +++ b/src/Ombi.Notifications/Agents/EmailNotification.cs @@ -3,6 +3,7 @@ using System.Linq; using System.Threading.Tasks; using MailKit.Net.Smtp; using Microsoft.AspNetCore.Identity; +using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; using MimeKit; using Ombi.Core.Settings; @@ -21,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) : base(settings, r, m, t, c, log) + ILogger log, UserManager um, IRepository sub) : base(settings, r, m, t, c, log, sub) { EmailProvider = prov; Logger = log; @@ -52,7 +53,7 @@ namespace Ombi.Notifications.Agents return true; } - + private async Task LoadTemplate(NotificationType type, NotificationOptions model, EmailNotificationSettings settings) { var parsed = await LoadTemplate(NotificationAgent.Email, type, model); @@ -62,8 +63,8 @@ namespace Ombi.Notifications.Agents return null; } var email = new EmailBasicTemplate(); - var html = email.LoadTemplate(parsed.Subject, parsed.Message,parsed.Image, Customization.Logo); - + var html = email.LoadTemplate(parsed.Subject, parsed.Message, parsed.Image, Customization.Logo); + var message = new NotificationMessage { @@ -154,7 +155,7 @@ namespace Ombi.Notifications.Agents { message.To = model.Recipient; } - + await Send(message, settings); } @@ -176,7 +177,7 @@ namespace Ombi.Notifications.Agents // Issues resolved should be sent to the user message.To = model.Recipient; - + await Send(message, settings); } @@ -227,10 +228,12 @@ namespace Ombi.Notifications.Agents var plaintext = await LoadPlainTextMessage(NotificationType.RequestDeclined, model, settings); message.Other.Add("PlainTextBody", plaintext); + await SendToSubscribers(settings, message); message.To = model.RequestType == RequestType.Movie ? MovieRequest.RequestedUser.Email : TvRequest.RequestedUser.Email; await Send(message, settings); + } protected override async Task RequestApproved(NotificationOptions model, EmailNotificationSettings settings) @@ -244,12 +247,32 @@ namespace Ombi.Notifications.Agents var plaintext = await LoadPlainTextMessage(NotificationType.RequestApproved, model, settings); message.Other.Add("PlainTextBody", plaintext); + await SendToSubscribers(settings, message); + message.To = model.RequestType == RequestType.Movie ? MovieRequest.RequestedUser.Email : TvRequest.RequestedUser.Email; await Send(message, settings); } + private async Task SendToSubscribers(EmailNotificationSettings settings, NotificationMessage message) + { + if (await SubsribedUsers.AnyAsync()) + { + foreach (var user in SubsribedUsers) + { + if (user.Email.IsNullOrEmpty()) + { + continue; + } + + message.To = user.Email; + + await Send(message, settings); + } + } + } + protected override async Task AvailableRequest(NotificationOptions model, EmailNotificationSettings settings) { var message = await LoadTemplate(NotificationType.RequestAvailable, model, settings); @@ -260,7 +283,7 @@ namespace Ombi.Notifications.Agents var plaintext = await LoadPlainTextMessage(NotificationType.RequestAvailable, model, settings); message.Other.Add("PlainTextBody", plaintext); - + await SendToSubscribers(settings, message); message.To = model.RequestType == RequestType.Movie ? MovieRequest.RequestedUser.Email : TvRequest.RequestedUser.Email; diff --git a/src/Ombi.Notifications/Agents/Interfaces/IMobileNotification.cs b/src/Ombi.Notifications/Agents/Interfaces/IMobileNotification.cs new file mode 100644 index 000000000..1daf7e46a --- /dev/null +++ b/src/Ombi.Notifications/Agents/Interfaces/IMobileNotification.cs @@ -0,0 +1,6 @@ +namespace Ombi.Notifications.Agents +{ + public interface IMobileNotification : INotification + { + } +} \ No newline at end of file diff --git a/src/Ombi.Notifications/Agents/MattermostNotification.cs b/src/Ombi.Notifications/Agents/MattermostNotification.cs index f07d62b72..8590bfaae 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) : base(sn, r, m, t,s,log) + ISettingsService s, IRepository sub) : base(sn, r, m, t,s,log, sub) { Api = api; Logger = log; diff --git a/src/Ombi.Notifications/Agents/MobileNotification.cs b/src/Ombi.Notifications/Agents/MobileNotification.cs index 575801bd2..35619ad1f 100644 --- a/src/Ombi.Notifications/Agents/MobileNotification.cs +++ b/src/Ombi.Notifications/Agents/MobileNotification.cs @@ -18,11 +18,11 @@ using Ombi.Store.Repository.Requests; namespace Ombi.Notifications.Agents { - public class MobileNotification : BaseNotification + public class MobileNotification : BaseNotification, IMobileNotification { public MobileNotification(IOneSignalApi api, ISettingsService sn, ILogger log, INotificationTemplatesRepository r, IMovieRequestRepository m, ITvRequestRepository t, ISettingsService s, IRepository notification, - UserManager um) : base(sn, r, m, t, s,log) + UserManager um, IRepository sub) : base(sn, r, m, t, s,log, sub) { _api = api; _logger = log; @@ -167,6 +167,7 @@ namespace Ombi.Notifications.Agents // Send to user var playerIds = GetUsers(model, NotificationType.RequestDeclined); + await AddSubscribedUsers(playerIds); await Send(playerIds, notification, settings); } @@ -185,6 +186,8 @@ namespace Ombi.Notifications.Agents // Send to user var playerIds = GetUsers(model, NotificationType.RequestApproved); + + await AddSubscribedUsers(playerIds); await Send(playerIds, notification, settings); } @@ -202,6 +205,8 @@ namespace Ombi.Notifications.Agents }; // Send to user var playerIds = GetUsers(model, NotificationType.RequestAvailable); + + await AddSubscribedUsers(playerIds); await Send(playerIds, notification, settings); } protected override Task Send(NotificationMessage model, MobileNotificationSettings settings) @@ -227,7 +232,13 @@ namespace Ombi.Notifications.Agents Message = message, }; // Send to user - var playerIds = await GetAdmins(NotificationType.RequestAvailable); + var user = await _userManager.Users.Include(x => x.NotificationUserIds).FirstOrDefaultAsync(x => x.Id.Equals(model.UserId)); + if (user == null) + { + return; + } + + var playerIds = user.NotificationUserIds.Select(x => x.PlayerId).ToList(); await Send(playerIds, notification, settings); } @@ -269,6 +280,20 @@ namespace Ombi.Notifications.Agents return playerIds; } + private async Task AddSubscribedUsers(List playerIds) + { + if (await SubsribedUsers.AnyAsync()) + { + foreach (var user in SubsribedUsers.Include(x => x.NotificationUserIds)) + { + var notificationId = user.NotificationUserIds; + if (notificationId.Any()) + { + playerIds.AddRange(notificationId.Select(x => x.PlayerId)); + } + } + } + } } } \ No newline at end of file diff --git a/src/Ombi.Notifications/Agents/PushbulletNotification.cs b/src/Ombi.Notifications/Agents/PushbulletNotification.cs index 21a384db6..24aa8cd22 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) : base(sn, r, m, t,s,log) + ISettingsService s, IRepository sub) : base(sn, r, m, t,s,log, sub) { Api = api; Logger = log; diff --git a/src/Ombi.Notifications/Agents/PushoverNotification.cs b/src/Ombi.Notifications/Agents/PushoverNotification.cs index 505adc44e..5b82eb8a3 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) : base(sn, r, m, t, s, log) + ISettingsService s, IRepository sub) : base(sn, r, m, t, s, log, sub) { Api = api; Logger = log; diff --git a/src/Ombi.Notifications/Agents/SlackNotification.cs b/src/Ombi.Notifications/Agents/SlackNotification.cs index d723c065a..894758591 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) : base(sn, r, m, t, s, log) + ISettingsService s, IRepository sub) : base(sn, r, m, t, s, log, sub) { Api = api; Logger = log; diff --git a/src/Ombi.Notifications/Agents/TelegramNotification.cs b/src/Ombi.Notifications/Agents/TelegramNotification.cs index 921fd3ad2..827b0b590 100644 --- a/src/Ombi.Notifications/Agents/TelegramNotification.cs +++ b/src/Ombi.Notifications/Agents/TelegramNotification.cs @@ -18,7 +18,8 @@ namespace Ombi.Notifications.Agents { public TelegramNotification(ITelegramApi api, ISettingsService sn, ILogger log, INotificationTemplatesRepository r, IMovieRequestRepository m, - ITvRequestRepository t, ISettingsService s) : base(sn, r, m, t,s,log) + ITvRequestRepository t, ISettingsService s + , IRepository sub) : base(sn, r, m, t,s,log, sub) { Api = api; Logger = log; diff --git a/src/Ombi.Notifications/Interfaces/BaseNotification.cs b/src/Ombi.Notifications/Interfaces/BaseNotification.cs index f21d3fb12..507b8059b 100644 --- a/src/Ombi.Notifications/Interfaces/BaseNotification.cs +++ b/src/Ombi.Notifications/Interfaces/BaseNotification.cs @@ -1,4 +1,6 @@ using System; +using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; @@ -17,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) + ISettingsService customization, ILogger> log, IRepository sub) { Settings = settings; TemplateRepository = templateRepo; @@ -26,21 +28,24 @@ namespace Ombi.Notifications.Interfaces CustomizationSettings = customization; Settings.ClearCache(); CustomizationSettings.ClearCache(); + RequestSubscription = sub; _log = log; } - + protected ISettingsService Settings { get; } protected INotificationTemplatesRepository TemplateRepository { get; } protected IMovieRequestRepository MovieRepository { get; } protected ITvRequestRepository TvRepository { get; } protected CustomizationSettings Customization { get; set; } + protected IRepository RequestSubscription { get; set; } private ISettingsService CustomizationSettings { get; } private readonly ILogger> _log; protected ChildRequests TvRequest { get; set; } protected MovieRequests MovieRequest { get; set; } - + protected IQueryable SubsribedUsers { get; private set; } + public abstract string NotificationName { get; } public async Task NotifyAsync(NotificationOptions model) @@ -54,20 +59,21 @@ namespace Ombi.Notifications.Interfaces { Settings.ClearCache(); if (settings == null) await NotifyAsync(model); - + var notificationSettings = (T)settings; - + if (!ValidateConfiguration(notificationSettings)) { return; } - + // Is this a test? // The request id for tests is -1 // Also issues are 0 since there might not be a request associated if (model.RequestId > 0) { await LoadRequest(model.RequestId, model.RequestType); + SubsribedUsers = GetSubscriptions(model.RequestId, model.RequestType); } Customization = await CustomizationSettings.GetSettingsAsync(); @@ -126,7 +132,7 @@ namespace Ombi.Notifications.Interfaces } else { - TvRequest = await TvRepository.GetChild().FirstOrDefaultAsync(x => x.Id == requestId); + TvRequest = await TvRepository.GetChild().FirstOrDefaultAsync(x => x.Id == requestId); } } @@ -152,13 +158,19 @@ namespace Ombi.Notifications.Interfaces } if (!template.Enabled) { - return new NotificationMessageContent {Disabled = true}; + return new NotificationMessageContent { Disabled = true }; } var parsed = Parse(model, template); return parsed; } + protected IQueryable GetSubscriptions(int requestId, RequestType type) + { + var subs = RequestSubscription.GetAll().Include(x => x.User).Where(x => x.RequestId == requestId && type == x.RequestType); + return subs.Select(x => x.User); + } + private NotificationMessageContent Parse(NotificationOptions model, NotificationTemplates template) { var resolver = new NotificationMessageResolver(); @@ -166,7 +178,7 @@ namespace Ombi.Notifications.Interfaces if (model.RequestType == RequestType.Movie) { _log.LogDebug("Notification options: {@model}, Req: {@MovieRequest}, Settings: {@Customization}", model, MovieRequest, Customization); - + curlys.Setup(model, MovieRequest, Customization); } else diff --git a/src/Ombi.Schedule/Jobs/Ombi/IRefreshMetadata.cs b/src/Ombi.Schedule/Jobs/Ombi/IRefreshMetadata.cs index a08db74d0..ed13280b0 100644 --- a/src/Ombi.Schedule/Jobs/Ombi/IRefreshMetadata.cs +++ b/src/Ombi.Schedule/Jobs/Ombi/IRefreshMetadata.cs @@ -1,9 +1,11 @@ -using System.Threading.Tasks; +using System.Collections.Generic; +using System.Threading.Tasks; namespace Ombi.Schedule.Jobs.Ombi { public interface IRefreshMetadata : IBaseJob { Task Start(); + Task ProcessPlexServerContent(IEnumerable contentIds); } } \ No newline at end of file diff --git a/src/Ombi.Schedule/Jobs/Ombi/RefreshMetadata.cs b/src/Ombi.Schedule/Jobs/Ombi/RefreshMetadata.cs index 9b7726a15..4eae94666 100644 --- a/src/Ombi.Schedule/Jobs/Ombi/RefreshMetadata.cs +++ b/src/Ombi.Schedule/Jobs/Ombi/RefreshMetadata.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.Extensions.Logging; @@ -17,7 +18,7 @@ namespace Ombi.Schedule.Jobs.Ombi { public RefreshMetadata(IPlexContentRepository plexRepo, IEmbyContentRepository embyRepo, ILogger log, ITvMazeApi tvApi, ISettingsService plexSettings, - IMovieDbApi movieApi) + IMovieDbApi movieApi, ISettingsService embySettings) { _plexRepo = plexRepo; _embyRepo = embyRepo; @@ -25,6 +26,7 @@ namespace Ombi.Schedule.Jobs.Ombi _movieApi = movieApi; _tvApi = tvApi; _plexSettings = plexSettings; + _embySettings = embySettings; } private readonly IPlexContentRepository _plexRepo; @@ -33,6 +35,7 @@ namespace Ombi.Schedule.Jobs.Ombi private readonly IMovieDbApi _movieApi; private readonly ITvMazeApi _tvApi; private readonly ISettingsService _plexSettings; + private readonly ISettingsService _embySettings; public async Task Start() { @@ -43,6 +46,11 @@ namespace Ombi.Schedule.Jobs.Ombi if (settings.Enable) { await StartPlex(); + } + + var embySettings = await _embySettings.GetSettingsAsync(); + if (embySettings.Enable) + { await StartEmby(); } } @@ -53,12 +61,45 @@ namespace Ombi.Schedule.Jobs.Ombi } } + public async Task ProcessPlexServerContent(IEnumerable contentIds) + { + _log.LogInformation("Starting the Metadata refresh from RecentlyAddedSync"); + try + { + var settings = await _plexSettings.GetSettingsAsync(); + if (settings.Enable) + { + await StartPlexWithKnownContent(contentIds); + } + } + catch (Exception e) + { + _log.LogError(e, "Exception when refreshing the Plex Metadata"); + throw; + } + } + + private async Task StartPlexWithKnownContent(IEnumerable contentids) + { + var everything = _plexRepo.GetAll().Where(x => contentids.Contains(x.Id)); + var allMovies = everything.Where(x => x.Type == PlexMediaTypeEntity.Movie); + await StartPlexMovies(allMovies); + + // Now Tv + var allTv = everything.Where(x => x.Type == PlexMediaTypeEntity.Show); + await StartPlexTv(allTv); + } + private async Task StartPlex() { - await StartPlexMovies(); + var allMovies = _plexRepo.GetAll().Where(x => + x.Type == PlexMediaTypeEntity.Movie && (!x.TheMovieDbId.HasValue() || !x.ImdbId.HasValue())); + await StartPlexMovies(allMovies); // Now Tv - await StartPlexTv(); + var allTv = _plexRepo.GetAll().Where(x => + x.Type == PlexMediaTypeEntity.Show && (!x.TheMovieDbId.HasValue() || !x.ImdbId.HasValue() || !x.TvDbId.HasValue())); + await StartPlexTv(allTv); } private async Task StartEmby() @@ -67,10 +108,8 @@ namespace Ombi.Schedule.Jobs.Ombi await StartEmbyTv(); } - private async Task StartPlexTv() + private async Task StartPlexTv(IQueryable allTv) { - var allTv = _plexRepo.GetAll().Where(x => - x.Type == PlexMediaTypeEntity.Show && (!x.TheMovieDbId.HasValue() || !x.ImdbId.HasValue() || !x.TvDbId.HasValue())); var tvCount = 0; foreach (var show in allTv) { @@ -147,10 +186,8 @@ namespace Ombi.Schedule.Jobs.Ombi await _embyRepo.SaveChangesAsync(); } - private async Task StartPlexMovies() + private async Task StartPlexMovies(IQueryable allMovies) { - var allMovies = _plexRepo.GetAll().Where(x => - x.Type == PlexMediaTypeEntity.Movie && (!x.TheMovieDbId.HasValue() || !x.ImdbId.HasValue())); int movieCount = 0; foreach (var movie in allMovies) { diff --git a/src/Ombi.Schedule/Jobs/Plex/Interfaces/IPlexEpisodeSync.cs b/src/Ombi.Schedule/Jobs/Plex/Interfaces/IPlexEpisodeSync.cs index 7d97381be..ede393790 100644 --- a/src/Ombi.Schedule/Jobs/Plex/Interfaces/IPlexEpisodeSync.cs +++ b/src/Ombi.Schedule/Jobs/Plex/Interfaces/IPlexEpisodeSync.cs @@ -1,10 +1,14 @@ using System; +using System.Linq; using System.Threading.Tasks; +using Ombi.Api.Plex.Models; +using Ombi.Store.Entities; namespace Ombi.Schedule.Jobs.Plex.Interfaces { public interface IPlexEpisodeSync : IBaseJob { Task Start(); + Task ProcessEpsiodes(Metadata[] episodes, IQueryable currentEpisodes); } } \ No newline at end of file diff --git a/src/Ombi.Schedule/Jobs/Plex/PlexContentSync.cs b/src/Ombi.Schedule/Jobs/Plex/PlexContentSync.cs index 334b51e4b..95cd3ba38 100644 --- a/src/Ombi.Schedule/Jobs/Plex/PlexContentSync.cs +++ b/src/Ombi.Schedule/Jobs/Plex/PlexContentSync.cs @@ -77,46 +77,86 @@ namespace Ombi.Schedule.Jobs.Plex Logger.LogError("Plex Settings are not valid"); return; } - + var processedContent = new HashSet(); Logger.LogInformation("Starting Plex Content Cacher"); try { - await StartTheCache(plexSettings, recentlyAddedSearch); + if (recentlyAddedSearch) + { + var result = await StartTheCache(plexSettings, true); + foreach (var r in result) + { + processedContent.Add(r); + } + } + else + { + await StartTheCache(plexSettings, false); + } } catch (Exception e) { Logger.LogWarning(LoggingEvents.PlexContentCacher, e, "Exception thrown when attempting to cache the Plex Content"); } - Logger.LogInformation("Starting EP Cacher"); - BackgroundJob.Enqueue(() => EpisodeSync.Start()); - BackgroundJob.Enqueue(() => Metadata.Start()); + if (!recentlyAddedSearch) + { + Logger.LogInformation("Starting EP Cacher"); + BackgroundJob.Enqueue(() => EpisodeSync.Start()); + } + + if (processedContent.Any() && recentlyAddedSearch) + { + // Just check what we send it + BackgroundJob.Enqueue(() => Metadata.ProcessPlexServerContent(processedContent)); + } } - private async Task StartTheCache(PlexSettings plexSettings, bool recentlyAddedSearch) + private async Task> StartTheCache(PlexSettings plexSettings, bool recentlyAddedSearch) { + var processedContent = new HashSet(); foreach (var servers in plexSettings.Servers ?? new List()) { try { Logger.LogInformation("Starting to cache the content on server {0}", servers.Name); - await ProcessServer(servers, recentlyAddedSearch); + + if (recentlyAddedSearch) + { + // If it's recently added search then we want the results to pass to the metadata job + // This way the metadata job is smaller in size to process, it only need to look at newly added shit + var result = await ProcessServer(servers, true); + foreach (var plexServerContent in result) + { + processedContent.Add(plexServerContent); + } + } + else + { + await ProcessServer(servers, false); + } } catch (Exception e) { Logger.LogWarning(LoggingEvents.PlexContentCacher, e, "Exception thrown when attempting to cache the Plex Content in server {0}", servers.Name); } } + + return processedContent; } - private async Task ProcessServer(PlexServers servers, bool recentlyAddedSearch) + private async Task> ProcessServer(PlexServers servers, bool recentlyAddedSearch) { + var processedContent = new HashSet(); Logger.LogInformation("Getting all content from server {0}", servers.Name); var allContent = await GetAllContent(servers, recentlyAddedSearch); Logger.LogInformation("We found {0} items", allContent.Count); // Let's now process this. var contentToAdd = new HashSet(); + + var allEps = Repo.GetAllEpisodes(); + foreach (var content in allContent) { if (content.viewGroup.Equals(PlexMediaType.Episode.ToString(), StringComparison.CurrentCultureIgnoreCase)) @@ -133,8 +173,10 @@ namespace Ombi.Schedule.Jobs.Plex continue; } - await ProcessTvShow(servers, show, contentToAdd, recentlyAddedSearch); + await ProcessTvShow(servers, show, contentToAdd, recentlyAddedSearch, processedContent); } + + await EpisodeSync.ProcessEpsiodes(content.Metadata, allEps); } if (content.viewGroup.Equals(PlexMediaType.Show.ToString(), StringComparison.CurrentCultureIgnoreCase)) { @@ -142,7 +184,7 @@ namespace Ombi.Schedule.Jobs.Plex Logger.LogInformation("Processing TV Shows"); foreach (var show in content.Metadata ?? new Metadata[] { }) { - await ProcessTvShow(servers, show, contentToAdd, recentlyAddedSearch); + await ProcessTvShow(servers, show, contentToAdd, recentlyAddedSearch, processedContent); } } if (content.viewGroup.Equals(PlexMediaType.Movie.ToString(), StringComparison.CurrentCultureIgnoreCase)) @@ -212,6 +254,10 @@ namespace Ombi.Schedule.Jobs.Plex if (contentToAdd.Count > 500) { await Repo.AddRange(contentToAdd); + foreach (var c in contentToAdd) + { + processedContent.Add(c.Id); + } contentToAdd.Clear(); } } @@ -219,6 +265,10 @@ namespace Ombi.Schedule.Jobs.Plex if (contentToAdd.Count > 500) { await Repo.AddRange(contentToAdd); + foreach (var c in contentToAdd) + { + processedContent.Add(c.Id); + } contentToAdd.Clear(); } } @@ -226,10 +276,16 @@ namespace Ombi.Schedule.Jobs.Plex if (contentToAdd.Any()) { await Repo.AddRange(contentToAdd); + foreach (var c in contentToAdd) + { + processedContent.Add(c.Id); + } } + + return processedContent; } - private async Task ProcessTvShow(PlexServers servers, Metadata show, HashSet contentToAdd, bool recentlyAdded) + private async Task ProcessTvShow(PlexServers servers, Metadata show, HashSet contentToAdd, bool recentlyAdded, HashSet contentProcessed) { var seasonList = await PlexApi.GetSeasons(servers.PlexAuthToken, servers.FullUri, show.ratingKey); @@ -412,8 +468,17 @@ namespace Ombi.Schedule.Jobs.Plex if (contentToAdd.Count > 500 || recentlyAdded) { await Repo.AddRange(contentToAdd); + foreach (var plexServerContent in contentToAdd) + { + contentProcessed.Add(plexServerContent.Id); + } contentToAdd.Clear(); } + + if (contentToAdd.Any()) + { + await Repo.AddRange(contentToAdd); + } } /// diff --git a/src/Ombi.Schedule/Jobs/Plex/PlexEpisodeSync.cs b/src/Ombi.Schedule/Jobs/Plex/PlexEpisodeSync.cs index 8e8cce411..d98eace4a 100644 --- a/src/Ombi.Schedule/Jobs/Plex/PlexEpisodeSync.cs +++ b/src/Ombi.Schedule/Jobs/Plex/PlexEpisodeSync.cs @@ -111,7 +111,7 @@ namespace Ombi.Schedule.Jobs.Plex // 12.03.2017 - I think we should be able to match them now //await _repo.ExecuteSql("DELETE FROM PlexEpisode"); - await ProcessEpsiodes(episodes, currentEpisodes); + await ProcessEpsiodes(episodes?.MediaContainer?.Metadata ?? new Metadata[] { }, currentEpisodes); currentPosition += resultCount; while (currentPosition < episodes.MediaContainer.totalSize) @@ -119,7 +119,7 @@ namespace Ombi.Schedule.Jobs.Plex var ep = await _api.GetAllEpisodes(settings.PlexAuthToken, settings.FullUri, section.key, currentPosition, resultCount); - await ProcessEpsiodes(ep, currentEpisodes); + await ProcessEpsiodes(ep?.MediaContainer?.Metadata ?? new Metadata[] { }, currentEpisodes); _log.LogInformation(LoggingEvents.PlexEpisodeCacher, $"Processed {resultCount} more episodes. Total Remaining {episodes.MediaContainer.totalSize - currentPosition}"); currentPosition += resultCount; } @@ -129,12 +129,12 @@ namespace Ombi.Schedule.Jobs.Plex await _repo.SaveChangesAsync(); } - private async Task ProcessEpsiodes(PlexContainer episodes, IQueryable currentEpisodes) + public async Task ProcessEpsiodes(Metadata[] episodes, IQueryable currentEpisodes) { var ep = new HashSet(); try { - foreach (var episode in episodes?.MediaContainer?.Metadata ?? new Metadata[] { }) + foreach (var episode in episodes) { // I don't think we need to get the metadata, we only need to get the metadata if we need the provider id (TheTvDbid). Why do we need it for episodes? // We have the parent and grandparent rating keys to link up to the season and series diff --git a/src/Ombi.Schedule/Processor/ChangeLogProcessor.cs b/src/Ombi.Schedule/Processor/ChangeLogProcessor.cs index 37e10a620..e645097ef 100644 --- a/src/Ombi.Schedule/Processor/ChangeLogProcessor.cs +++ b/src/Ombi.Schedule/Processor/ChangeLogProcessor.cs @@ -90,15 +90,15 @@ namespace Ombi.Schedule.Processor } - return TransformUpdate(release); + return TransformUpdate(release,!masterBranch); } - private UpdateModel TransformUpdate(Release release) + private UpdateModel TransformUpdate(Release release, bool develop) { var newUpdate = new UpdateModel { - UpdateVersionString = release.Version.Substring(1,8), + UpdateVersionString = develop ? release.Version : release.Version.Substring(1,8), UpdateVersion = release.Version == "(unreleased)" ? 0 : int.Parse(release.Version.Substring(1, 5).Replace(".", "")), UpdateDate = DateTime.Now, ChangeLogs = new List(), diff --git a/src/Ombi.Settings/Settings/Models/JobSettingsHelper.cs b/src/Ombi.Settings/Settings/Models/JobSettingsHelper.cs index 2e56f17db..a5afbeba7 100644 --- a/src/Ombi.Settings/Settings/Models/JobSettingsHelper.cs +++ b/src/Ombi.Settings/Settings/Models/JobSettingsHelper.cs @@ -25,7 +25,7 @@ namespace Ombi.Settings.Settings.Models } public static string PlexRecentlyAdded(JobSettings s) { - return Get(s.PlexRecentlyAddedSync, Cron.Hourly(0)); + return Get(s.PlexRecentlyAddedSync, Cron.MinuteInterval(30)); } public static string CouchPotato(JobSettings s) { @@ -50,7 +50,7 @@ namespace Ombi.Settings.Settings.Models } public static string RefreshMetadata(JobSettings s) { - return Get(s.RefreshMetadata, Cron.DayInterval(2)); + return Get(s.RefreshMetadata, Cron.DayInterval(3)); } private static string Get(string settings, string defaultCron) diff --git a/src/Ombi.Store/Context/IOmbiContext.cs b/src/Ombi.Store/Context/IOmbiContext.cs index 55d7db563..0c716c7c4 100644 --- a/src/Ombi.Store/Context/IOmbiContext.cs +++ b/src/Ombi.Store/Context/IOmbiContext.cs @@ -42,5 +42,6 @@ namespace Ombi.Store.Context DbSet SickRageEpisodeCache { get; set; } DbSet RequestLogs { get; set; } DbSet RecentlyAddedLogs { get; set; } + DbSet RequestSubscription { get; set; } } } \ No newline at end of file diff --git a/src/Ombi.Store/Context/OmbiContext.cs b/src/Ombi.Store/Context/OmbiContext.cs index d1963e765..8fb05f6ae 100644 --- a/src/Ombi.Store/Context/OmbiContext.cs +++ b/src/Ombi.Store/Context/OmbiContext.cs @@ -47,6 +47,7 @@ namespace Ombi.Store.Context public DbSet SonarrEpisodeCache { get; set; } public DbSet SickRageCache { get; set; } public DbSet SickRageEpisodeCache { get; set; } + public DbSet RequestSubscription { get; set; } public DbSet ApplicationConfigurations { get; set; } diff --git a/src/Ombi.Store/Entities/OmbiUser.cs b/src/Ombi.Store/Entities/OmbiUser.cs index b3e82390a..f67183982 100644 --- a/src/Ombi.Store/Entities/OmbiUser.cs +++ b/src/Ombi.Store/Entities/OmbiUser.cs @@ -36,6 +36,8 @@ namespace Ombi.Store.Entities [NotMapped] public bool EmailLogin { get; set; } + + [NotMapped] public bool IsSystemUser => UserType == UserType.SystemUser; [JsonIgnore] public override string PasswordHash diff --git a/src/Ombi.Store/Entities/RequestSubscription.cs b/src/Ombi.Store/Entities/RequestSubscription.cs new file mode 100644 index 000000000..2f6d530c9 --- /dev/null +++ b/src/Ombi.Store/Entities/RequestSubscription.cs @@ -0,0 +1,15 @@ +using System.ComponentModel.DataAnnotations.Schema; + +namespace Ombi.Store.Entities +{ + [Table("RequestSubscription")] + public class RequestSubscription : Entity + { + public string UserId { get; set; } + public int RequestId { get; set; } + public RequestType RequestType { get; set; } + + [ForeignKey(nameof(UserId))] + public OmbiUser User { get; set; } + } +} \ No newline at end of file diff --git a/src/Ombi.Store/Entities/Requests/ChildRequests.cs b/src/Ombi.Store/Entities/Requests/ChildRequests.cs index 64c0bcc5c..3b5156ce5 100644 --- a/src/Ombi.Store/Entities/Requests/ChildRequests.cs +++ b/src/Ombi.Store/Entities/Requests/ChildRequests.cs @@ -13,6 +13,15 @@ namespace Ombi.Store.Entities.Requests public int? IssueId { get; set; } public SeriesType SeriesType { get; set; } + /// + /// This is to see if the user is subscribed in the UI + /// + [NotMapped] + public bool Subscribed { get; set; } + + [NotMapped] + public bool ShowSubscribe { get; set; } + [ForeignKey(nameof(IssueId))] public List Issues { get; set; } diff --git a/src/Ombi.Store/Entities/Requests/MovieRequests.cs b/src/Ombi.Store/Entities/Requests/MovieRequests.cs index 998c27707..675035140 100644 --- a/src/Ombi.Store/Entities/Requests/MovieRequests.cs +++ b/src/Ombi.Store/Entities/Requests/MovieRequests.cs @@ -12,6 +12,11 @@ namespace Ombi.Store.Entities.Requests [ForeignKey(nameof(IssueId))] public List Issues { get; set; } + [NotMapped] + public bool Subscribed { get; set; } + [NotMapped] + public bool ShowSubscribe { get; set; } + public int RootPathOverride { get; set; } public int QualityOverride { get; set; } } diff --git a/src/Ombi.Store/Entities/Requests/TvRequests.cs b/src/Ombi.Store/Entities/Requests/TvRequests.cs index 7a6abc792..432bc88ab 100644 --- a/src/Ombi.Store/Entities/Requests/TvRequests.cs +++ b/src/Ombi.Store/Entities/Requests/TvRequests.cs @@ -16,6 +16,7 @@ namespace Ombi.Store.Entities.Requests public string Background { get; set; } public DateTime ReleaseDate { get; set; } public string Status { get; set; } + /// /// This is so we can correctly send the right amount of seasons to Sonarr /// diff --git a/src/Ombi.Store/Migrations/20180516090124_RequestSubscription.Designer.cs b/src/Ombi.Store/Migrations/20180516090124_RequestSubscription.Designer.cs new file mode 100644 index 000000000..e83458fef --- /dev/null +++ b/src/Ombi.Store/Migrations/20180516090124_RequestSubscription.Designer.cs @@ -0,0 +1,981 @@ +// +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage; +using Microsoft.EntityFrameworkCore.Storage.Internal; +using Ombi.Helpers; +using Ombi.Store.Context; +using Ombi.Store.Entities; +using Ombi.Store.Entities.Requests; +using System; + +namespace Ombi.Store.Migrations +{ + [DbContext(typeof(OmbiContext))] + [Migration("20180516090124_RequestSubscription")] + partial class RequestSubscription + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.0.3-rtm-10026"); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken(); + + b.Property("Name") + .HasMaxLength(256); + + b.Property("NormalizedName") + .HasMaxLength(256); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasName("RoleNameIndex"); + + b.ToTable("AspNetRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ClaimType"); + + b.Property("ClaimValue"); + + b.Property("RoleId") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ClaimType"); + + b.Property("ClaimValue"); + + b.Property("UserId") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider"); + + b.Property("ProviderKey"); + + b.Property("ProviderDisplayName"); + + b.Property("UserId") + .IsRequired(); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId"); + + b.Property("RoleId"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId"); + + b.Property("LoginProvider"); + + b.Property("Name"); + + b.Property("Value"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.ApplicationConfiguration", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Type"); + + b.Property("Value"); + + b.HasKey("Id"); + + b.ToTable("ApplicationConfiguration"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Audit", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AuditArea"); + + b.Property("AuditType"); + + b.Property("DateTime"); + + b.Property("Description"); + + b.Property("User"); + + b.HasKey("Id"); + + b.ToTable("Audit"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.CouchPotatoCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("TheMovieDbId"); + + b.HasKey("Id"); + + b.ToTable("CouchPotatoCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.EmbyContent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("EmbyId") + .IsRequired(); + + b.Property("ImdbId"); + + b.Property("ProviderId"); + + b.Property("TheMovieDbId"); + + b.Property("Title"); + + b.Property("TvDbId"); + + b.Property("Type"); + + b.Property("Url"); + + b.HasKey("Id"); + + b.ToTable("EmbyContent"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.EmbyEpisode", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("EmbyId"); + + b.Property("EpisodeNumber"); + + b.Property("ImdbId"); + + b.Property("ParentId"); + + b.Property("ProviderId"); + + b.Property("SeasonNumber"); + + b.Property("TheMovieDbId"); + + b.Property("Title"); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.HasIndex("ParentId"); + + b.ToTable("EmbyEpisode"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.GlobalSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Content"); + + b.Property("SettingsName"); + + b.HasKey("Id"); + + b.ToTable("GlobalSettings"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.NotificationTemplates", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Agent"); + + b.Property("Enabled"); + + b.Property("Message"); + + b.Property("NotificationType"); + + b.Property("Subject"); + + b.HasKey("Id"); + + b.ToTable("NotificationTemplates"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.NotificationUserId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("PlayerId"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("NotificationUserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.OmbiUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AccessFailedCount"); + + b.Property("Alias"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken(); + + b.Property("Email") + .HasMaxLength(256); + + b.Property("EmailConfirmed"); + + b.Property("EmbyConnectUserId"); + + b.Property("EpisodeRequestLimit"); + + b.Property("LastLoggedIn"); + + b.Property("LockoutEnabled"); + + b.Property("LockoutEnd"); + + b.Property("MovieRequestLimit"); + + b.Property("NormalizedEmail") + .HasMaxLength(256); + + b.Property("NormalizedUserName") + .HasMaxLength(256); + + b.Property("PasswordHash"); + + b.Property("PhoneNumber"); + + b.Property("PhoneNumberConfirmed"); + + b.Property("ProviderUserId"); + + b.Property("SecurityStamp"); + + b.Property("TwoFactorEnabled"); + + b.Property("UserAccessToken"); + + b.Property("UserName") + .HasMaxLength(256); + + b.Property("UserType"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasName("UserNameIndex"); + + b.ToTable("AspNetUsers"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexEpisode", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("EpisodeNumber"); + + b.Property("GrandparentKey"); + + b.Property("Key"); + + b.Property("ParentKey"); + + b.Property("SeasonNumber"); + + b.Property("Title"); + + b.HasKey("Id"); + + b.HasIndex("GrandparentKey"); + + b.ToTable("PlexEpisode"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexSeasonsContent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ParentKey"); + + b.Property("PlexContentId"); + + b.Property("PlexServerContentId"); + + b.Property("SeasonKey"); + + b.Property("SeasonNumber"); + + b.HasKey("Id"); + + b.HasIndex("PlexServerContentId"); + + b.ToTable("PlexSeasonsContent"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexServerContent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("ImdbId"); + + b.Property("Key"); + + b.Property("Quality"); + + b.Property("ReleaseYear"); + + b.Property("TheMovieDbId"); + + b.Property("Title"); + + b.Property("TvDbId"); + + b.Property("Type"); + + b.Property("Url"); + + b.HasKey("Id"); + + b.ToTable("PlexServerContent"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.RadarrCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("HasFile"); + + b.Property("TheMovieDbId"); + + b.HasKey("Id"); + + b.ToTable("RadarrCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.RecentlyAddedLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("ContentId"); + + b.Property("ContentType"); + + b.Property("EpisodeNumber"); + + b.Property("SeasonNumber"); + + b.Property("Type"); + + b.HasKey("Id"); + + b.ToTable("RecentlyAddedLog"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.ChildRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Approved"); + + b.Property("Available"); + + b.Property("Denied"); + + b.Property("DeniedReason"); + + b.Property("IssueId"); + + b.Property("ParentRequestId"); + + b.Property("RequestType"); + + b.Property("RequestedDate"); + + b.Property("RequestedUserId"); + + b.Property("SeriesType"); + + b.Property("Title"); + + b.HasKey("Id"); + + b.HasIndex("ParentRequestId"); + + b.HasIndex("RequestedUserId"); + + b.ToTable("ChildRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Value"); + + b.HasKey("Id"); + + b.ToTable("IssueCategory"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueComments", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Comment"); + + b.Property("Date"); + + b.Property("IssuesId"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("IssuesId"); + + b.HasIndex("UserId"); + + b.ToTable("IssueComments"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.Issues", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Description"); + + b.Property("IssueCategoryId"); + + b.Property("IssueId"); + + b.Property("ProviderId"); + + b.Property("RequestId"); + + b.Property("RequestType"); + + b.Property("ResovledDate"); + + b.Property("Status"); + + b.Property("Subject"); + + b.Property("Title"); + + b.Property("UserReportedId"); + + b.HasKey("Id"); + + b.HasIndex("IssueCategoryId"); + + b.HasIndex("IssueId"); + + b.HasIndex("UserReportedId"); + + b.ToTable("Issues"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.MovieRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Approved"); + + b.Property("Available"); + + b.Property("Background"); + + b.Property("Denied"); + + b.Property("DeniedReason"); + + b.Property("DigitalReleaseDate"); + + b.Property("ImdbId"); + + b.Property("IssueId"); + + b.Property("Overview"); + + b.Property("PosterPath"); + + b.Property("QualityOverride"); + + b.Property("ReleaseDate"); + + b.Property("RequestType"); + + b.Property("RequestedDate"); + + b.Property("RequestedUserId"); + + b.Property("RootPathOverride"); + + b.Property("Status"); + + b.Property("TheMovieDbId"); + + b.Property("Title"); + + b.HasKey("Id"); + + b.HasIndex("RequestedUserId"); + + b.ToTable("MovieRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.RequestLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("EpisodeCount"); + + b.Property("RequestDate"); + + b.Property("RequestId"); + + b.Property("RequestType"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("RequestLog"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.TvRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Background"); + + b.Property("ImdbId"); + + b.Property("Overview"); + + b.Property("PosterPath"); + + b.Property("QualityOverride"); + + b.Property("ReleaseDate"); + + b.Property("RootFolder"); + + b.Property("Status"); + + b.Property("Title"); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.ToTable("TvRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.RequestSubscription", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("RequestId"); + + b.Property("RequestType"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("RequestSubscription"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.SickRageCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.ToTable("SickRageCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.SickRageEpisodeCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("EpisodeNumber"); + + b.Property("SeasonNumber"); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.ToTable("SickRageEpisodeCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.SonarrCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.ToTable("SonarrCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.SonarrEpisodeCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("EpisodeNumber"); + + b.Property("HasFile"); + + b.Property("SeasonNumber"); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.ToTable("SonarrEpisodeCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Tokens", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Token"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Tokens"); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.EpisodeRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AirDate"); + + b.Property("Approved"); + + b.Property("Available"); + + b.Property("EpisodeNumber"); + + b.Property("Requested"); + + b.Property("SeasonId"); + + b.Property("Title"); + + b.Property("Url"); + + b.HasKey("Id"); + + b.HasIndex("SeasonId"); + + b.ToTable("EpisodeRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.SeasonRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChildRequestId"); + + b.Property("SeasonNumber"); + + b.HasKey("Id"); + + b.HasIndex("ChildRequestId"); + + b.ToTable("SeasonRequests"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole") + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole") + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Ombi.Store.Entities.OmbiUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Ombi.Store.Entities.EmbyEpisode", b => + { + b.HasOne("Ombi.Store.Entities.EmbyContent", "Series") + .WithMany("Episodes") + .HasForeignKey("ParentId") + .HasPrincipalKey("EmbyId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.NotificationUserId", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany("NotificationUserIds") + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexEpisode", b => + { + b.HasOne("Ombi.Store.Entities.PlexServerContent", "Series") + .WithMany("Episodes") + .HasForeignKey("GrandparentKey") + .HasPrincipalKey("Key") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexSeasonsContent", b => + { + b.HasOne("Ombi.Store.Entities.PlexServerContent") + .WithMany("Seasons") + .HasForeignKey("PlexServerContentId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.ChildRequests", b => + { + b.HasOne("Ombi.Store.Entities.Requests.TvRequests", "ParentRequest") + .WithMany("ChildRequests") + .HasForeignKey("ParentRequestId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Ombi.Store.Entities.OmbiUser", "RequestedUser") + .WithMany() + .HasForeignKey("RequestedUserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueComments", b => + { + b.HasOne("Ombi.Store.Entities.Requests.Issues", "Issues") + .WithMany("Comments") + .HasForeignKey("IssuesId"); + + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.Issues", b => + { + b.HasOne("Ombi.Store.Entities.Requests.IssueCategory", "IssueCategory") + .WithMany() + .HasForeignKey("IssueCategoryId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Ombi.Store.Entities.Requests.ChildRequests") + .WithMany("Issues") + .HasForeignKey("IssueId"); + + b.HasOne("Ombi.Store.Entities.Requests.MovieRequests") + .WithMany("Issues") + .HasForeignKey("IssueId"); + + b.HasOne("Ombi.Store.Entities.OmbiUser", "UserReported") + .WithMany() + .HasForeignKey("UserReportedId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.MovieRequests", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "RequestedUser") + .WithMany() + .HasForeignKey("RequestedUserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.RequestLog", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.RequestSubscription", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "user") + .WithMany() + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Tokens", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.EpisodeRequests", b => + { + b.HasOne("Ombi.Store.Repository.Requests.SeasonRequests", "Season") + .WithMany("Episodes") + .HasForeignKey("SeasonId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.SeasonRequests", b => + { + b.HasOne("Ombi.Store.Entities.Requests.ChildRequests", "ChildRequest") + .WithMany("SeasonRequests") + .HasForeignKey("ChildRequestId") + .OnDelete(DeleteBehavior.Cascade); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Ombi.Store/Migrations/20180516090124_RequestSubscription.cs b/src/Ombi.Store/Migrations/20180516090124_RequestSubscription.cs new file mode 100644 index 000000000..9ba204b60 --- /dev/null +++ b/src/Ombi.Store/Migrations/20180516090124_RequestSubscription.cs @@ -0,0 +1,44 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using System; +using System.Collections.Generic; + +namespace Ombi.Store.Migrations +{ + public partial class RequestSubscription : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "RequestSubscription", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + RequestId = table.Column(nullable: false), + RequestType = table.Column(nullable: false), + UserId = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_RequestSubscription", x => x.Id); + table.ForeignKey( + name: "FK_RequestSubscription_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateIndex( + name: "IX_RequestSubscription_UserId", + table: "RequestSubscription", + column: "UserId"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "RequestSubscription"); + } + } +} diff --git a/src/Ombi.Store/Migrations/OmbiContextModelSnapshot.cs b/src/Ombi.Store/Migrations/OmbiContextModelSnapshot.cs index 794bde8a6..2da0729a8 100644 --- a/src/Ombi.Store/Migrations/OmbiContextModelSnapshot.cs +++ b/src/Ombi.Store/Migrations/OmbiContextModelSnapshot.cs @@ -20,7 +20,7 @@ namespace Ombi.Store.Migrations { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "2.0.2-rtm-10011"); + .HasAnnotation("ProductVersion", "2.0.3-rtm-10026"); modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => { @@ -676,6 +676,24 @@ namespace Ombi.Store.Migrations b.ToTable("TvRequests"); }); + modelBuilder.Entity("Ombi.Store.Entities.RequestSubscription", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("RequestId"); + + b.Property("RequestType"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("RequestSubscription"); + }); + modelBuilder.Entity("Ombi.Store.Entities.SickRageCache", b => { b.Property("Id") @@ -927,6 +945,13 @@ namespace Ombi.Store.Migrations .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") diff --git a/src/Ombi.Store/Repository/Requests/MovieRequestRepository.cs b/src/Ombi.Store/Repository/Requests/MovieRequestRepository.cs index 78c3da7dd..d4a550528 100644 --- a/src/Ombi.Store/Repository/Requests/MovieRequestRepository.cs +++ b/src/Ombi.Store/Repository/Requests/MovieRequestRepository.cs @@ -57,7 +57,7 @@ namespace Ombi.Store.Repository.Requests public IQueryable GetWithUser(string userId) { return Db.MovieRequests - .Where(x => x.RequestedUserId == userId) + .Where(x => x.RequestedUserId == userId) .Include(x => x.RequestedUser) .ThenInclude(x => x.NotificationUserIds) .AsQueryable(); diff --git a/src/Ombi/ClientApp/app/interfaces/INotificationSettings.ts b/src/Ombi/ClientApp/app/interfaces/INotificationSettings.ts index da1e147da..8c5fcd5bc 100644 --- a/src/Ombi/ClientApp/app/interfaces/INotificationSettings.ts +++ b/src/Ombi/ClientApp/app/interfaces/INotificationSettings.ts @@ -101,3 +101,8 @@ export interface IMattermostNotifcationSettings extends INotificationSettings { export interface IMobileNotifcationSettings extends INotificationSettings { notificationTemplates: INotificationTemplates[]; } + +export interface IMobileNotificationTestSettings { + settings: IMobileNotifcationSettings; + userId: string; +} diff --git a/src/Ombi/ClientApp/app/interfaces/IRequestModel.ts b/src/Ombi/ClientApp/app/interfaces/IRequestModel.ts index e744aea31..1895914c3 100644 --- a/src/Ombi/ClientApp/app/interfaces/IRequestModel.ts +++ b/src/Ombi/ClientApp/app/interfaces/IRequestModel.ts @@ -12,6 +12,8 @@ export interface IMovieRequests extends IFullBaseRequest { rootPathOverride: number; qualityOverride: number; digitalReleaseDate: Date; + subscribed: boolean; + showSubscribe: boolean; // For the UI rootPathOverrideTitle: string; @@ -77,6 +79,8 @@ export interface ITvRequests { export interface IChildRequests extends IBaseRequest { seasonRequests: INewSeasonRequests[]; + subscribed: boolean; + showSubscribe: boolean; } export interface ITvUpdateModel { diff --git a/src/Ombi/ClientApp/app/requests/movierequests.component.html b/src/Ombi/ClientApp/app/requests/movierequests.component.html index e67aeb5e6..629fe3147 100644 --- a/src/Ombi/ClientApp/app/requests/movierequests.component.html +++ b/src/Ombi/ClientApp/app/requests/movierequests.component.html @@ -124,7 +124,13 @@ -
+
+ +
+ + + +
@@ -164,7 +170,7 @@
- +
- -
+ + + +
diff --git a/src/Ombi/ClientApp/app/requests/tvrequest-children.component.ts b/src/Ombi/ClientApp/app/requests/tvrequest-children.component.ts index e9da2342f..4a10dc937 100644 --- a/src/Ombi/ClientApp/app/requests/tvrequest-children.component.ts +++ b/src/Ombi/ClientApp/app/requests/tvrequest-children.component.ts @@ -94,6 +94,22 @@ export class TvRequestChildrenComponent { }); } + public subscribe(request: IChildRequests) { + request.subscribed = true; + this.requestService.subscribeToTv(request.id) + .subscribe(x => { + this.notificationService.success("Subscribed To TV Show!"); + }); + } + + public unSubscribe(request: IChildRequests) { + request.subscribed = false; + this.requestService.unSubscribeToTv(request.id) + .subscribe(x => { + this.notificationService.success("Unsubscribed TV Show!"); + }); + } + private removeRequestFromUi(key: IChildRequests) { const index = this.childRequests.indexOf(key, 0); if (index > -1) { diff --git a/src/Ombi/ClientApp/app/services/applications/tester.service.ts b/src/Ombi/ClientApp/app/services/applications/tester.service.ts index bab94d32f..640d6ec04 100644 --- a/src/Ombi/ClientApp/app/services/applications/tester.service.ts +++ b/src/Ombi/ClientApp/app/services/applications/tester.service.ts @@ -12,6 +12,7 @@ import { IEmailNotificationSettings, IEmbyServer, IMattermostNotifcationSettings, + IMobileNotificationTestSettings, INewsletterNotificationSettings, IPlexServer, IPushbulletNotificationSettings, @@ -81,5 +82,8 @@ export class TesterService extends ServiceHelpers { } public newsletterTest(settings: INewsletterNotificationSettings): Observable { return this.http.post(`${this.url}newsletter`, JSON.stringify(settings), {headers: this.headers}); + } + public mobileNotificationTest(settings: IMobileNotificationTestSettings): Observable { + return this.http.post(`${this.url}mobile`, JSON.stringify(settings), {headers: this.headers}); } } diff --git a/src/Ombi/ClientApp/app/services/request.service.ts b/src/Ombi/ClientApp/app/services/request.service.ts index acbe319ab..2da0c1a8a 100644 --- a/src/Ombi/ClientApp/app/services/request.service.ts +++ b/src/Ombi/ClientApp/app/services/request.service.ts @@ -117,4 +117,17 @@ export class RequestService extends ServiceHelpers { public filterMovies(filter: IFilter): Observable> { return this.http.post>(`${this.url}movie/filter`, JSON.stringify(filter), {headers: this.headers}); } + + public subscribeToMovie(requestId: number): Observable { + return this.http.post(`${this.url}movie/subscribe/${requestId}`, {headers: this.headers}); + } + public unSubscribeToMovie(requestId: number): Observable { + return this.http.post(`${this.url}movie/unsubscribe/${requestId}`, {headers: this.headers}); + } + public subscribeToTv(requestId: number): Observable { + return this.http.post(`${this.url}tv/subscribe/${requestId}`, {headers: this.headers}); + } + public unSubscribeToTv(requestId: number): Observable { + return this.http.post(`${this.url}tv/unsubscribe/${requestId}`, {headers: this.headers}); + } } diff --git a/src/Ombi/ClientApp/app/settings/notifications/mobile.component.html b/src/Ombi/ClientApp/app/settings/notifications/mobile.component.html index f5522120c..3aad31ff8 100644 --- a/src/Ombi/ClientApp/app/settings/notifications/mobile.component.html +++ b/src/Ombi/ClientApp/app/settings/notifications/mobile.component.html @@ -35,14 +35,20 @@
+
- +
+
+
+ +
+
diff --git a/src/Ombi/ClientApp/app/settings/notifications/mobile.component.ts b/src/Ombi/ClientApp/app/settings/notifications/mobile.component.ts index 47ad3a25b..b40b4aa94 100644 --- a/src/Ombi/ClientApp/app/settings/notifications/mobile.component.ts +++ b/src/Ombi/ClientApp/app/settings/notifications/mobile.component.ts @@ -15,6 +15,7 @@ export class MobileComponent implements OnInit { public templates: INotificationTemplates[]; public form: FormGroup; public userList: IMobileUsersViewModel[]; + public testUserId: string; constructor(private settingsService: SettingsService, private notificationService: NotificationService, @@ -64,8 +65,12 @@ export class MobileComponent implements OnInit { this.notificationService.error("Please check your entered values"); return; } + if(!this.testUserId) { + this.notificationService.warning("Warning","Please select a user to send the test notification"); + return; + } - this.testerService.discordTest(form.value).subscribe(x => { + this.testerService.mobileNotificationTest({settings: form.value, userId: this.testUserId}).subscribe(x => { if (x) { this.notificationService.success("Successfully sent a Mobile message, please check the admin mobile device"); } else { diff --git a/src/Ombi/Controllers/External/TesterController.cs b/src/Ombi/Controllers/External/TesterController.cs index bad933e86..fca42bb63 100644 --- a/src/Ombi/Controllers/External/TesterController.cs +++ b/src/Ombi/Controllers/External/TesterController.cs @@ -14,6 +14,7 @@ using Ombi.Core.Models.UI; using Ombi.Core.Notifications; using Ombi.Core.Settings.Models.External; using Ombi.Helpers; +using Ombi.Models; using Ombi.Notifications; using Ombi.Notifications.Agents; using Ombi.Notifications.Models; @@ -37,7 +38,7 @@ namespace Ombi.Controllers.External public TesterController(INotificationService service, IDiscordNotification notification, IEmailNotification emailN, IPushbulletNotification pushbullet, ISlackNotification slack, IPushoverNotification po, IMattermostNotification mm, IPlexApi plex, IEmbyApi emby, IRadarrApi radarr, ISonarrApi sonarr, ILogger log, IEmailProvider provider, - ICouchPotatoApi cpApi, ITelegramNotification telegram, ISickRageApi srApi, INewsletterJob newsletter) + ICouchPotatoApi cpApi, ITelegramNotification telegram, ISickRageApi srApi, INewsletterJob newsletter, IMobileNotification mobileNotification) { Service = service; DiscordNotification = notification; @@ -56,6 +57,7 @@ namespace Ombi.Controllers.External TelegramNotification = telegram; SickRageApi = srApi; Newsletter = newsletter; + MobileNotification = mobileNotification; } private INotificationService Service { get; } @@ -75,6 +77,7 @@ namespace Ombi.Controllers.External private ITelegramNotification TelegramNotification { get; } private ISickRageApi SickRageApi { get; } private INewsletterJob Newsletter { get; } + private IMobileNotification MobileNotification { get; } /// @@ -388,5 +391,21 @@ namespace Ombi.Controllers.External return false; } } + + [HttpPost("mobile")] + public async Task MobileNotificationTest([FromBody] MobileNotificationTestViewModel settings) + { + try + { + await MobileNotification.NotifyAsync(new NotificationOptions { NotificationType = NotificationType.Test, RequestId = -1, UserId = settings.UserId}, settings.Settings); + + return true; + } + catch (Exception e) + { + Log.LogError(LoggingEvents.Api, e, "Could not test Mobile Notifications"); + return false; + } + } } } \ No newline at end of file diff --git a/src/Ombi/Controllers/RequestController.cs b/src/Ombi/Controllers/RequestController.cs index 5f38f3435..0a56c32b6 100644 --- a/src/Ombi/Controllers/RequestController.cs +++ b/src/Ombi/Controllers/RequestController.cs @@ -9,6 +9,7 @@ using System.Threading.Tasks; using Ombi.Store.Entities.Requests; using System.Diagnostics; using Ombi.Models; +using Ombi.Store.Entities; namespace Ombi.Controllers { @@ -356,5 +357,45 @@ namespace Ombi.Controllers { return await MovieRequestEngine.Filter(vm); } + + /// + /// Subscribes for notifications to a movie request + /// + [HttpPost("movie/subscribe/{requestId:int}")] + public async Task SubscribeToMovie(int requestId) + { + await MovieRequestEngine.SubscribeToRequest(requestId, RequestType.Movie); + return true; + } + + /// + /// Subscribes for notifications to a TV request + /// + [HttpPost("tv/subscribe/{requestId:int}")] + public async Task SubscribeToTv(int requestId) + { + await TvRequestEngine.SubscribeToRequest(requestId, RequestType.TvShow); + return true; + } + + /// + /// UnSubscribes for notifications to a movie request + /// + [HttpPost("movie/unsubscribe/{requestId:int}")] + public async Task UnSubscribeToMovie(int requestId) + { + await MovieRequestEngine.UnSubscribeRequest(requestId, RequestType.Movie); + return true; + } + + /// + /// UnSubscribes for notifications to a TV request + /// + [HttpPost("tv/unsubscribe/{requestId:int}")] + public async Task UnSubscribeToTv(int requestId) + { + await TvRequestEngine.UnSubscribeRequest(requestId, RequestType.TvShow); + return true; + } } } \ No newline at end of file diff --git a/src/Ombi/Models/MobileNotificationTestViewModel.cs b/src/Ombi/Models/MobileNotificationTestViewModel.cs new file mode 100644 index 000000000..bba7a3a41 --- /dev/null +++ b/src/Ombi/Models/MobileNotificationTestViewModel.cs @@ -0,0 +1,10 @@ +using Ombi.Settings.Settings.Models.Notifications; + +namespace Ombi.Models +{ + public class MobileNotificationTestViewModel + { + public string UserId { get; set; } + public MobileNotificationSettings Settings { get; set; } + } +} \ No newline at end of file diff --git a/src/Ombi/Startup.cs b/src/Ombi/Startup.cs index fc80be838..4b373d457 100644 --- a/src/Ombi/Startup.cs +++ b/src/Ombi/Startup.cs @@ -191,11 +191,15 @@ namespace Ombi } app.UseHangfireServer(new BackgroundJobServerOptions { WorkerCount = 1, ServerTimeout = TimeSpan.FromDays(1), ShutdownTimeout = TimeSpan.FromDays(1)}); - app.UseHangfireDashboard(settings.BaseUrl.HasValue() ? $"{settings.BaseUrl}/hangfire" : "/hangfire", - new DashboardOptions - { - Authorization = new[] { new HangfireAuthorizationFilter() } - }); + if (env.IsDevelopment()) + { + app.UseHangfireDashboard(settings.BaseUrl.HasValue() ? $"{settings.BaseUrl}/hangfire" : "/hangfire", + new DashboardOptions + { + Authorization = new[] {new HangfireAuthorizationFilter()} + }); + } + GlobalJobFilters.Filters.Add(new AutomaticRetryAttribute { Attempts = 3 }); // Setup the scheduler diff --git a/src/Ombi/Views/Shared/_Layout.cshtml b/src/Ombi/Views/Shared/_Layout.cshtml index 5dc7a1dec..4a7169dae 100644 --- a/src/Ombi/Views/Shared/_Layout.cshtml +++ b/src/Ombi/Views/Shared/_Layout.cshtml @@ -58,6 +58,21 @@ --> + + @* + + + + + + + + + + + e.com/store/apps/details?id=com.tidusjar.Ombi"> + +*@ @@ -78,6 +93,7 @@ + diff --git a/src/Ombi/webpack.config.vendor.ts b/src/Ombi/webpack.config.vendor.ts index b50e784f4..ed2021aea 100644 --- a/src/Ombi/webpack.config.vendor.ts +++ b/src/Ombi/webpack.config.vendor.ts @@ -67,6 +67,8 @@ module.exports = (env: any) => { "@ngx-translate/core", "@ngx-translate/http-loader", "ngx-order-pipe", + //"smartbanner.js/dist/smartbanner.js", + //"smartbanner.js/dist/smartbanner.css", ], }, output: {