Merge pull request #2256 from tidusjar/feature/subscribe-to-request

Added new feature to subscribe to requests for notifications
pull/2258/head
Jamie 6 years ago committed by GitHub
commit a949d93afd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -1,11 +1,13 @@
# Changelog # Changelog
## (unreleased) ## v3.0.3304 (2018-05-09)
### **New Features** ### **New Features**
- Updated to prevent security vulnerability as noted here: https://github.com/aspnet/Announcements/issues/300. [Jamie Rees] - Updated to prevent security vulnerability as noted here: https://github.com/aspnet/Announcements/issues/300. [Jamie Rees]
- Update README.md. [Jamie]
### **Fixes** ### **Fixes**
- [LC] - Added classes to root/quality override divs. [Anojh] - [LC] - Added classes to root/quality override divs. [Anojh]

@ -9,6 +9,9 @@ ____
[![Patreon](https://img.shields.io/badge/patreon-donate-yellow.svg)](https://patreon.com/tidusjar/Ombi) [![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) [![Paypal](https://img.shields.io/badge/paypal-donate-yellow.svg)](https://paypal.me/PlexRequestsNet)
___
<a href='https://play.google.com/store/apps/details?id=com.tidusjar.Ombi&pcampaignid=MKT-Other-global-all-co-prtnr-py-PartBadge-Mar2515-1'><img width="150" alt='Get it on Google Play' src='https://play.google.com/intl/en_gb/badges/images/generic/en_badge_web_generic.png'/></a>
___ ___
We also now have merch up on Teespring! We also now have merch up on Teespring!

@ -9,13 +9,12 @@ using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Ombi.Core.Rule.Interfaces; using Ombi.Core.Rule.Interfaces;
using Ombi.Store.Entities.Requests; using Ombi.Store.Entities.Requests;
using Ombi.Store.Repository;
using Ombi.Store.Repository.Requests; using Ombi.Store.Repository.Requests;
using Ombi.Store.Entities;
using Microsoft.AspNetCore.Identity;
using Ombi.Core.Authentication; using Ombi.Core.Authentication;
using Ombi.Core.Settings; using Ombi.Core.Settings;
using Ombi.Settings.Settings.Models; using Ombi.Settings.Settings.Models;
using Ombi.Store.Entities;
using Ombi.Store.Repository;
namespace Ombi.Core.Engine namespace Ombi.Core.Engine
{ {
@ -26,11 +25,12 @@ namespace Ombi.Core.Engine
private Dictionary<int, TvRequests> _dbTv; private Dictionary<int, TvRequests> _dbTv;
protected BaseMediaEngine(IPrincipal identity, IRequestServiceMain requestService, protected BaseMediaEngine(IPrincipal identity, IRequestServiceMain requestService,
IRuleEvaluator rules, OmbiUserManager um, ICacheService cache, ISettingsService<OmbiSettings> ombiSettings) : base(identity, um, rules) IRuleEvaluator rules, OmbiUserManager um, ICacheService cache, ISettingsService<OmbiSettings> ombiSettings, IRepository<RequestSubscription> sub) : base(identity, um, rules)
{ {
RequestService = requestService; RequestService = requestService;
Cache = cache; Cache = cache;
OmbiSettings = ombiSettings; OmbiSettings = ombiSettings;
_subscriptionRepository = sub;
} }
protected IRequestServiceMain RequestService { get; } protected IRequestServiceMain RequestService { get; }
@ -38,6 +38,7 @@ namespace Ombi.Core.Engine
protected ITvRequestRepository TvRepository => RequestService.TvRequestService; protected ITvRequestRepository TvRepository => RequestService.TvRequestService;
protected readonly ICacheService Cache; protected readonly ICacheService Cache;
protected readonly ISettingsService<OmbiSettings> OmbiSettings; protected readonly ISettingsService<OmbiSettings> OmbiSettings;
protected readonly IRepository<RequestSubscription> _subscriptionRepository;
protected async Task<Dictionary<int, MovieRequests>> GetMovieRequests() protected async Task<Dictionary<int, MovieRequests>> GetMovieRequests()
{ {
@ -78,7 +79,7 @@ namespace Ombi.Core.Engine
var pendingTv = 0; var pendingTv = 0;
var approvedTv = 0; var approvedTv = 0;
var availableTv = 0; var availableTv = 0;
foreach (var tv in tvQuery) foreach (var tv in tvQuery)
{ {
foreach (var child in tv.ChildRequests) foreach (var child in tv.ChildRequests)
@ -108,21 +109,51 @@ namespace Ombi.Core.Engine
protected async Task<HideResult> HideFromOtherUsers() protected async Task<HideResult> HideFromOtherUsers()
{ {
var user = await GetUser();
if (await IsInRole(OmbiRoles.Admin) || await IsInRole(OmbiRoles.PowerUser)) if (await IsInRole(OmbiRoles.Admin) || await IsInRole(OmbiRoles.PowerUser))
{ {
return new HideResult(); return new HideResult
{
UserId = user.Id
};
} }
var settings = await Cache.GetOrAdd(CacheKeys.OmbiSettings, async () => await OmbiSettings.GetSettingsAsync()); var settings = await Cache.GetOrAdd(CacheKeys.OmbiSettings, async () => await OmbiSettings.GetSettingsAsync());
var result = new HideResult 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(); await _subscriptionRepository.Delete(existingSub);
result.UserId = user.Id;
} }
return result;
} }
public class HideResult public class HideResult

@ -18,6 +18,7 @@ namespace Ombi.Core.Engine.Interfaces
Task<RequestEngineResult> ApproveMovieById(int requestId); Task<RequestEngineResult> ApproveMovieById(int requestId);
Task<RequestEngineResult> DenyMovieById(int modelId); Task<RequestEngineResult> DenyMovieById(int modelId);
Task<FilterResult<MovieRequests>> Filter(FilterViewModel vm); Task<FilterResult<MovieRequests>> Filter(FilterViewModel vm);
} }
} }

@ -1,6 +1,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using Ombi.Core.Models.Requests; using Ombi.Core.Models.Requests;
using Ombi.Store.Entities;
namespace Ombi.Core.Engine.Interfaces namespace Ombi.Core.Engine.Interfaces
{ {
@ -18,5 +19,7 @@ namespace Ombi.Core.Engine.Interfaces
Task<RequestEngineResult> MarkUnavailable(int modelId); Task<RequestEngineResult> MarkUnavailable(int modelId);
Task<RequestEngineResult> MarkAvailable(int modelId); Task<RequestEngineResult> MarkAvailable(int modelId);
Task<int> GetTotal(); Task<int> GetTotal();
Task UnSubscribeRequest(int requestId, RequestType type);
Task SubscribeToRequest(int requestId, RequestType type);
} }
} }

@ -25,7 +25,8 @@ namespace Ombi.Core.Engine
{ {
public MovieRequestEngine(IMovieDbApi movieApi, IRequestServiceMain requestService, IPrincipal user, public MovieRequestEngine(IMovieDbApi movieApi, IRequestServiceMain requestService, IPrincipal user,
INotificationHelper helper, IRuleEvaluator r, IMovieSender sender, ILogger<MovieRequestEngine> log, INotificationHelper helper, IRuleEvaluator r, IMovieSender sender, ILogger<MovieRequestEngine> log,
OmbiUserManager manager, IRepository<RequestLog> rl, ICacheService cache, ISettingsService<OmbiSettings> ombiSettings) : base(user, requestService, r, manager, cache, ombiSettings) OmbiUserManager manager, IRepository<RequestLog> rl, ICacheService cache, ISettingsService<OmbiSettings> ombiSettings, IRepository<RequestSubscription> sub)
: base(user, requestService, r, manager, cache, ombiSettings, sub)
{ {
MovieApi = movieApi; MovieApi = movieApi;
NotificationHelper = helper; NotificationHelper = helper;
@ -137,9 +138,10 @@ namespace Ombi.Core.Engine
{ {
allRequests = await MovieRepository.GetWithUser().Skip(position).Take(count).OrderByDescending(x => x.ReleaseDate).ToListAsync(); 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); x.PosterPath = PosterPathHelper.FixPosterPath(x.PosterPath);
await CheckForSubscription(shouldHide, x);
}); });
return allRequests; return allRequests;
} }
@ -173,9 +175,30 @@ namespace Ombi.Core.Engine
{ {
allRequests = await MovieRepository.GetWithUser().ToListAsync(); allRequests = await MovieRepository.GetWithUser().ToListAsync();
} }
allRequests.ForEach(async x =>
{
x.PosterPath = PosterPathHelper.FixPosterPath(x.PosterPath);
await CheckForSubscription(shouldHide, x);
});
return allRequests; 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;
}
}
/// <summary> /// <summary>
/// Searches the movie request. /// Searches the movie request.
/// </summary> /// </summary>
@ -194,9 +217,10 @@ namespace Ombi.Core.Engine
allRequests = await MovieRepository.GetWithUser().ToListAsync(); allRequests = await MovieRepository.GetWithUser().ToListAsync();
} }
var results = allRequests.Where(x => x.Title.Contains(search, CompareOptions.IgnoreCase)).ToList(); 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); x.PosterPath = PosterPathHelper.FixPosterPath(x.PosterPath);
await CheckForSubscription(shouldHide, x);
}); });
return results; return results;
} }

@ -15,14 +15,16 @@ using Ombi.Core.Authentication;
using Ombi.Core.Settings; using Ombi.Core.Settings;
using Ombi.Helpers; using Ombi.Helpers;
using Ombi.Settings.Settings.Models; using Ombi.Settings.Settings.Models;
using Ombi.Store.Entities;
using Ombi.Store.Repository;
namespace Ombi.Core.Engine namespace Ombi.Core.Engine
{ {
public class MovieSearchEngine : BaseMediaEngine, IMovieEngine public class MovieSearchEngine : BaseMediaEngine, IMovieEngine
{ {
public MovieSearchEngine(IPrincipal identity, IRequestServiceMain service, IMovieDbApi movApi, IMapper mapper, public MovieSearchEngine(IPrincipal identity, IRequestServiceMain service, IMovieDbApi movApi, IMapper mapper,
ILogger<MovieSearchEngine> logger, IRuleEvaluator r, OmbiUserManager um, ICacheService mem, ISettingsService<OmbiSettings> s) ILogger<MovieSearchEngine> logger, IRuleEvaluator r, OmbiUserManager um, ICacheService mem, ISettingsService<OmbiSettings> s, IRepository<RequestSubscription> sub)
: base(identity, service, r, um, mem, s) : base(identity, service, r, um, mem, s, sub)
{ {
MovieApi = movApi; MovieApi = movApi;
Mapper = mapper; Mapper = mapper;

@ -29,7 +29,8 @@ namespace Ombi.Core.Engine
{ {
public TvRequestEngine(ITvMazeApi tvApi, IMovieDbApi movApi, IRequestServiceMain requestService, IPrincipal user, public TvRequestEngine(ITvMazeApi tvApi, IMovieDbApi movApi, IRequestServiceMain requestService, IPrincipal user,
INotificationHelper helper, IRuleEvaluator rule, OmbiUserManager manager, INotificationHelper helper, IRuleEvaluator rule, OmbiUserManager manager,
ITvSender sender, IAuditRepository audit, IRepository<RequestLog> rl, ISettingsService<OmbiSettings> settings, ICacheService cache) : base(user, requestService, rule, manager, cache, settings) ITvSender sender, IAuditRepository audit, IRepository<RequestLog> rl, ISettingsService<OmbiSettings> settings, ICacheService cache,
IRepository<RequestSubscription> sub) : base(user, requestService, rule, manager, cache, settings, sub)
{ {
TvApi = tvApi; TvApi = tvApi;
MovieDbApi = movApi; MovieDbApi = movApi;
@ -156,6 +157,8 @@ namespace Ombi.Core.Engine
.Skip(position).Take(count).OrderByDescending(x => x.ReleaseDate).ToListAsync(); .Skip(position).Take(count).OrderByDescending(x => x.ReleaseDate).ToListAsync();
} }
allRequests.ForEach(async r => { await CheckForSubscription(shouldHide, r); });
return allRequests; return allRequests;
} }
@ -181,25 +184,28 @@ namespace Ombi.Core.Engine
.ThenInclude(x => x.Episodes) .ThenInclude(x => x.Episodes)
.Skip(position).Take(count).ToListAsync(); .Skip(position).Take(count).ToListAsync();
} }
allRequests.ForEach(async r => { await CheckForSubscription(shouldHide, r); });
return ParseIntoTreeNode(allRequests); return ParseIntoTreeNode(allRequests);
} }
public async Task<IEnumerable<TvRequests>> GetRequests() public async Task<IEnumerable<TvRequests>> GetRequests()
{ {
var shouldHide = await HideFromOtherUsers(); var shouldHide = await HideFromOtherUsers();
IQueryable<TvRequests> allRequests; List<TvRequests> allRequests;
if (shouldHide.Hide) if (shouldHide.Hide)
{ {
allRequests = TvRepository.Get(shouldHide.UserId); allRequests = await TvRepository.Get(shouldHide.UserId).ToListAsync();
FilterChildren(allRequests, shouldHide); FilterChildren(allRequests, shouldHide);
} }
else 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<TvRequests> allRequests, HideResult shouldHide) private static void FilterChildren(IEnumerable<TvRequests> 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 = await TvRepository.GetChild().Include(x => x.SeasonRequests).Where(x => x.ParentRequestId == tvId).ToListAsync();
} }
allRequests.ForEach(async r => { await CheckForSubscription(shouldHide, r); });
return allRequests; return allRequests;
} }
@ -248,6 +256,8 @@ namespace Ombi.Core.Engine
allRequests = TvRepository.Get(); allRequests = TvRepository.Get();
} }
var results = await allRequests.Where(x => x.Title.Contains(search, CompareOptions.IgnoreCase)).ToListAsync(); var results = await allRequests.Where(x => x.Title.Contains(search, CompareOptions.IgnoreCase)).ToListAsync();
results.ForEach(async r => { await CheckForSubscription(shouldHide, r); });
return results; return results;
} }
@ -264,6 +274,7 @@ namespace Ombi.Core.Engine
allRequests = TvRepository.Get(); allRequests = TvRepository.Get();
} }
var results = await allRequests.Where(x => x.Title.Contains(search, CompareOptions.IgnoreCase)).ToListAsync(); var results = await allRequests.Where(x => x.Title.Contains(search, CompareOptions.IgnoreCase)).ToListAsync();
results.ForEach(async r => { await CheckForSubscription(shouldHide, r); });
return ParseIntoTreeNode(results); 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<RequestEngineResult> AddExistingRequest(ChildRequests newRequest, TvRequests existingRequest) private async Task<RequestEngineResult> AddExistingRequest(ChildRequests newRequest, TvRequests existingRequest)
{ {
// Add the child // Add the child

@ -20,6 +20,7 @@ using Microsoft.Extensions.Caching.Memory;
using Ombi.Core.Authentication; using Ombi.Core.Authentication;
using Ombi.Helpers; using Ombi.Helpers;
using Ombi.Settings.Settings.Models; using Ombi.Settings.Settings.Models;
using Ombi.Store.Entities;
namespace Ombi.Core.Engine namespace Ombi.Core.Engine
{ {
@ -27,8 +28,8 @@ namespace Ombi.Core.Engine
{ {
public TvSearchEngine(IPrincipal identity, IRequestServiceMain service, ITvMazeApi tvMaze, IMapper mapper, ISettingsService<PlexSettings> plexSettings, public TvSearchEngine(IPrincipal identity, IRequestServiceMain service, ITvMazeApi tvMaze, IMapper mapper, ISettingsService<PlexSettings> plexSettings,
ISettingsService<EmbySettings> embySettings, IPlexContentRepository repo, IEmbyContentRepository embyRepo, ITraktApi trakt, IRuleEvaluator r, OmbiUserManager um, ISettingsService<EmbySettings> embySettings, IPlexContentRepository repo, IEmbyContentRepository embyRepo, ITraktApi trakt, IRuleEvaluator r, OmbiUserManager um,
ICacheService memCache, ISettingsService<OmbiSettings> s) ICacheService memCache, ISettingsService<OmbiSettings> s, IRepository<RequestSubscription> sub)
: base(identity, service, r, um, memCache, s) : base(identity, service, r, um, memCache, s, sub)
{ {
TvMazeApi = tvMaze; TvMazeApi = tvMaze;
Mapper = mapper; Mapper = mapper;

@ -20,8 +20,8 @@ namespace Ombi.Notifications.Agents
{ {
public DiscordNotification(IDiscordApi api, ISettingsService<DiscordNotificationSettings> sn, public DiscordNotification(IDiscordApi api, ISettingsService<DiscordNotificationSettings> sn,
ILogger<DiscordNotification> log, INotificationTemplatesRepository r, ILogger<DiscordNotification> log, INotificationTemplatesRepository r,
IMovieRequestRepository m, ITvRequestRepository t, ISettingsService<CustomizationSettings> s) IMovieRequestRepository m, ITvRequestRepository t, ISettingsService<CustomizationSettings> s, IRepository<RequestSubscription> sub)
: base(sn, r, m, t,s,log) : base(sn, r, m, t,s,log, sub)
{ {
Api = api; Api = api;
Logger = log; Logger = log;

@ -3,6 +3,7 @@ using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using MailKit.Net.Smtp; using MailKit.Net.Smtp;
using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using MimeKit; using MimeKit;
using Ombi.Core.Settings; using Ombi.Core.Settings;
@ -21,7 +22,7 @@ namespace Ombi.Notifications.Agents
public class EmailNotification : BaseNotification<EmailNotificationSettings>, IEmailNotification public class EmailNotification : BaseNotification<EmailNotificationSettings>, IEmailNotification
{ {
public EmailNotification(ISettingsService<EmailNotificationSettings> settings, INotificationTemplatesRepository r, IMovieRequestRepository m, ITvRequestRepository t, IEmailProvider prov, ISettingsService<CustomizationSettings> c, public EmailNotification(ISettingsService<EmailNotificationSettings> settings, INotificationTemplatesRepository r, IMovieRequestRepository m, ITvRequestRepository t, IEmailProvider prov, ISettingsService<CustomizationSettings> c,
ILogger<EmailNotification> log, UserManager<OmbiUser> um) : base(settings, r, m, t, c, log) ILogger<EmailNotification> log, UserManager<OmbiUser> um, IRepository<RequestSubscription> sub) : base(settings, r, m, t, c, log, sub)
{ {
EmailProvider = prov; EmailProvider = prov;
Logger = log; Logger = log;
@ -52,7 +53,7 @@ namespace Ombi.Notifications.Agents
return true; return true;
} }
private async Task<NotificationMessage> LoadTemplate(NotificationType type, NotificationOptions model, EmailNotificationSettings settings) private async Task<NotificationMessage> LoadTemplate(NotificationType type, NotificationOptions model, EmailNotificationSettings settings)
{ {
var parsed = await LoadTemplate(NotificationAgent.Email, type, model); var parsed = await LoadTemplate(NotificationAgent.Email, type, model);
@ -62,8 +63,8 @@ namespace Ombi.Notifications.Agents
return null; return null;
} }
var email = new EmailBasicTemplate(); 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 var message = new NotificationMessage
{ {
@ -154,7 +155,7 @@ namespace Ombi.Notifications.Agents
{ {
message.To = model.Recipient; message.To = model.Recipient;
} }
await Send(message, settings); await Send(message, settings);
} }
@ -176,7 +177,7 @@ namespace Ombi.Notifications.Agents
// Issues resolved should be sent to the user // Issues resolved should be sent to the user
message.To = model.Recipient; message.To = model.Recipient;
await Send(message, settings); await Send(message, settings);
} }
@ -227,10 +228,12 @@ namespace Ombi.Notifications.Agents
var plaintext = await LoadPlainTextMessage(NotificationType.RequestDeclined, model, settings); var plaintext = await LoadPlainTextMessage(NotificationType.RequestDeclined, model, settings);
message.Other.Add("PlainTextBody", plaintext); message.Other.Add("PlainTextBody", plaintext);
await SendToSubscribers(settings, message);
message.To = model.RequestType == RequestType.Movie message.To = model.RequestType == RequestType.Movie
? MovieRequest.RequestedUser.Email ? MovieRequest.RequestedUser.Email
: TvRequest.RequestedUser.Email; : TvRequest.RequestedUser.Email;
await Send(message, settings); await Send(message, settings);
} }
protected override async Task RequestApproved(NotificationOptions model, EmailNotificationSettings 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); var plaintext = await LoadPlainTextMessage(NotificationType.RequestApproved, model, settings);
message.Other.Add("PlainTextBody", plaintext); message.Other.Add("PlainTextBody", plaintext);
await SendToSubscribers(settings, message);
message.To = model.RequestType == RequestType.Movie message.To = model.RequestType == RequestType.Movie
? MovieRequest.RequestedUser.Email ? MovieRequest.RequestedUser.Email
: TvRequest.RequestedUser.Email; : TvRequest.RequestedUser.Email;
await Send(message, settings); 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) protected override async Task AvailableRequest(NotificationOptions model, EmailNotificationSettings settings)
{ {
var message = await LoadTemplate(NotificationType.RequestAvailable, model, 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); var plaintext = await LoadPlainTextMessage(NotificationType.RequestAvailable, model, settings);
message.Other.Add("PlainTextBody", plaintext); message.Other.Add("PlainTextBody", plaintext);
await SendToSubscribers(settings, message);
message.To = model.RequestType == RequestType.Movie message.To = model.RequestType == RequestType.Movie
? MovieRequest.RequestedUser.Email ? MovieRequest.RequestedUser.Email
: TvRequest.RequestedUser.Email; : TvRequest.RequestedUser.Email;

@ -21,7 +21,7 @@ namespace Ombi.Notifications.Agents
public class MattermostNotification : BaseNotification<MattermostNotificationSettings>, IMattermostNotification public class MattermostNotification : BaseNotification<MattermostNotificationSettings>, IMattermostNotification
{ {
public MattermostNotification(IMattermostApi api, ISettingsService<MattermostNotificationSettings> sn, ILogger<MattermostNotification> log, INotificationTemplatesRepository r, IMovieRequestRepository m, ITvRequestRepository t, public MattermostNotification(IMattermostApi api, ISettingsService<MattermostNotificationSettings> sn, ILogger<MattermostNotification> log, INotificationTemplatesRepository r, IMovieRequestRepository m, ITvRequestRepository t,
ISettingsService<CustomizationSettings> s) : base(sn, r, m, t,s,log) ISettingsService<CustomizationSettings> s, IRepository<RequestSubscription> sub) : base(sn, r, m, t,s,log, sub)
{ {
Api = api; Api = api;
Logger = log; Logger = log;

@ -22,7 +22,7 @@ namespace Ombi.Notifications.Agents
{ {
public MobileNotification(IOneSignalApi api, ISettingsService<MobileNotificationSettings> sn, ILogger<MobileNotification> log, INotificationTemplatesRepository r, public MobileNotification(IOneSignalApi api, ISettingsService<MobileNotificationSettings> sn, ILogger<MobileNotification> log, INotificationTemplatesRepository r,
IMovieRequestRepository m, ITvRequestRepository t, ISettingsService<CustomizationSettings> s, IRepository<NotificationUserId> notification, IMovieRequestRepository m, ITvRequestRepository t, ISettingsService<CustomizationSettings> s, IRepository<NotificationUserId> notification,
UserManager<OmbiUser> um) : base(sn, r, m, t, s,log) UserManager<OmbiUser> um, IRepository<RequestSubscription> sub) : base(sn, r, m, t, s,log, sub)
{ {
_api = api; _api = api;
_logger = log; _logger = log;
@ -167,6 +167,7 @@ namespace Ombi.Notifications.Agents
// Send to user // Send to user
var playerIds = GetUsers(model, NotificationType.RequestDeclined); var playerIds = GetUsers(model, NotificationType.RequestDeclined);
await AddSubscribedUsers(playerIds);
await Send(playerIds, notification, settings); await Send(playerIds, notification, settings);
} }
@ -185,6 +186,8 @@ namespace Ombi.Notifications.Agents
// Send to user // Send to user
var playerIds = GetUsers(model, NotificationType.RequestApproved); var playerIds = GetUsers(model, NotificationType.RequestApproved);
await AddSubscribedUsers(playerIds);
await Send(playerIds, notification, settings); await Send(playerIds, notification, settings);
} }
@ -202,6 +205,8 @@ namespace Ombi.Notifications.Agents
}; };
// Send to user // Send to user
var playerIds = GetUsers(model, NotificationType.RequestAvailable); var playerIds = GetUsers(model, NotificationType.RequestAvailable);
await AddSubscribedUsers(playerIds);
await Send(playerIds, notification, settings); await Send(playerIds, notification, settings);
} }
protected override Task Send(NotificationMessage model, MobileNotificationSettings settings) protected override Task Send(NotificationMessage model, MobileNotificationSettings settings)
@ -269,6 +274,20 @@ namespace Ombi.Notifications.Agents
return playerIds; return playerIds;
} }
private async Task AddSubscribedUsers(List<string> 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));
}
}
}
}
} }
} }

@ -17,7 +17,7 @@ namespace Ombi.Notifications.Agents
public class PushbulletNotification : BaseNotification<PushbulletSettings>, IPushbulletNotification public class PushbulletNotification : BaseNotification<PushbulletSettings>, IPushbulletNotification
{ {
public PushbulletNotification(IPushbulletApi api, ISettingsService<PushbulletSettings> sn, ILogger<PushbulletNotification> log, INotificationTemplatesRepository r, IMovieRequestRepository m, ITvRequestRepository t, public PushbulletNotification(IPushbulletApi api, ISettingsService<PushbulletSettings> sn, ILogger<PushbulletNotification> log, INotificationTemplatesRepository r, IMovieRequestRepository m, ITvRequestRepository t,
ISettingsService<CustomizationSettings> s) : base(sn, r, m, t,s,log) ISettingsService<CustomizationSettings> s, IRepository<RequestSubscription> sub) : base(sn, r, m, t,s,log, sub)
{ {
Api = api; Api = api;
Logger = log; Logger = log;

@ -18,7 +18,7 @@ namespace Ombi.Notifications.Agents
public class PushoverNotification : BaseNotification<PushoverSettings>, IPushoverNotification public class PushoverNotification : BaseNotification<PushoverSettings>, IPushoverNotification
{ {
public PushoverNotification(IPushoverApi api, ISettingsService<PushoverSettings> sn, ILogger<PushoverNotification> log, INotificationTemplatesRepository r, IMovieRequestRepository m, ITvRequestRepository t, public PushoverNotification(IPushoverApi api, ISettingsService<PushoverSettings> sn, ILogger<PushoverNotification> log, INotificationTemplatesRepository r, IMovieRequestRepository m, ITvRequestRepository t,
ISettingsService<CustomizationSettings> s) : base(sn, r, m, t, s, log) ISettingsService<CustomizationSettings> s, IRepository<RequestSubscription> sub) : base(sn, r, m, t, s, log, sub)
{ {
Api = api; Api = api;
Logger = log; Logger = log;

@ -18,7 +18,7 @@ namespace Ombi.Notifications.Agents
public class SlackNotification : BaseNotification<SlackNotificationSettings>, ISlackNotification public class SlackNotification : BaseNotification<SlackNotificationSettings>, ISlackNotification
{ {
public SlackNotification(ISlackApi api, ISettingsService<SlackNotificationSettings> sn, ILogger<SlackNotification> log, INotificationTemplatesRepository r, IMovieRequestRepository m, ITvRequestRepository t, public SlackNotification(ISlackApi api, ISettingsService<SlackNotificationSettings> sn, ILogger<SlackNotification> log, INotificationTemplatesRepository r, IMovieRequestRepository m, ITvRequestRepository t,
ISettingsService<CustomizationSettings> s) : base(sn, r, m, t, s, log) ISettingsService<CustomizationSettings> s, IRepository<RequestSubscription> sub) : base(sn, r, m, t, s, log, sub)
{ {
Api = api; Api = api;
Logger = log; Logger = log;

@ -18,7 +18,8 @@ namespace Ombi.Notifications.Agents
{ {
public TelegramNotification(ITelegramApi api, ISettingsService<TelegramSettings> sn, ILogger<TelegramNotification> log, public TelegramNotification(ITelegramApi api, ISettingsService<TelegramSettings> sn, ILogger<TelegramNotification> log,
INotificationTemplatesRepository r, IMovieRequestRepository m, INotificationTemplatesRepository r, IMovieRequestRepository m,
ITvRequestRepository t, ISettingsService<CustomizationSettings> s) : base(sn, r, m, t,s,log) ITvRequestRepository t, ISettingsService<CustomizationSettings> s
, IRepository<RequestSubscription> sub) : base(sn, r, m, t,s,log, sub)
{ {
Api = api; Api = api;
Logger = log; Logger = log;

@ -1,4 +1,6 @@
using System; using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@ -17,7 +19,7 @@ namespace Ombi.Notifications.Interfaces
public abstract class BaseNotification<T> : INotification where T : Settings.Settings.Models.Settings, new() public abstract class BaseNotification<T> : INotification where T : Settings.Settings.Models.Settings, new()
{ {
protected BaseNotification(ISettingsService<T> settings, INotificationTemplatesRepository templateRepo, IMovieRequestRepository movie, ITvRequestRepository tv, protected BaseNotification(ISettingsService<T> settings, INotificationTemplatesRepository templateRepo, IMovieRequestRepository movie, ITvRequestRepository tv,
ISettingsService<CustomizationSettings> customization, ILogger<BaseNotification<T>> log) ISettingsService<CustomizationSettings> customization, ILogger<BaseNotification<T>> log, IRepository<RequestSubscription> sub)
{ {
Settings = settings; Settings = settings;
TemplateRepository = templateRepo; TemplateRepository = templateRepo;
@ -26,21 +28,24 @@ namespace Ombi.Notifications.Interfaces
CustomizationSettings = customization; CustomizationSettings = customization;
Settings.ClearCache(); Settings.ClearCache();
CustomizationSettings.ClearCache(); CustomizationSettings.ClearCache();
RequestSubscription = sub;
_log = log; _log = log;
} }
protected ISettingsService<T> Settings { get; } protected ISettingsService<T> Settings { get; }
protected INotificationTemplatesRepository TemplateRepository { get; } protected INotificationTemplatesRepository TemplateRepository { get; }
protected IMovieRequestRepository MovieRepository { get; } protected IMovieRequestRepository MovieRepository { get; }
protected ITvRequestRepository TvRepository { get; } protected ITvRequestRepository TvRepository { get; }
protected CustomizationSettings Customization { get; set; } protected CustomizationSettings Customization { get; set; }
protected IRepository<RequestSubscription> RequestSubscription { get; set; }
private ISettingsService<CustomizationSettings> CustomizationSettings { get; } private ISettingsService<CustomizationSettings> CustomizationSettings { get; }
private readonly ILogger<BaseNotification<T>> _log; private readonly ILogger<BaseNotification<T>> _log;
protected ChildRequests TvRequest { get; set; } protected ChildRequests TvRequest { get; set; }
protected MovieRequests MovieRequest { get; set; } protected MovieRequests MovieRequest { get; set; }
protected IQueryable<OmbiUser> SubsribedUsers { get; private set; }
public abstract string NotificationName { get; } public abstract string NotificationName { get; }
public async Task NotifyAsync(NotificationOptions model) public async Task NotifyAsync(NotificationOptions model)
@ -54,20 +59,21 @@ namespace Ombi.Notifications.Interfaces
{ {
Settings.ClearCache(); Settings.ClearCache();
if (settings == null) await NotifyAsync(model); if (settings == null) await NotifyAsync(model);
var notificationSettings = (T)settings; var notificationSettings = (T)settings;
if (!ValidateConfiguration(notificationSettings)) if (!ValidateConfiguration(notificationSettings))
{ {
return; return;
} }
// Is this a test? // Is this a test?
// The request id for tests is -1 // The request id for tests is -1
// Also issues are 0 since there might not be a request associated // Also issues are 0 since there might not be a request associated
if (model.RequestId > 0) if (model.RequestId > 0)
{ {
await LoadRequest(model.RequestId, model.RequestType); await LoadRequest(model.RequestId, model.RequestType);
SubsribedUsers = GetSubscriptions(model.RequestId, model.RequestType);
} }
Customization = await CustomizationSettings.GetSettingsAsync(); Customization = await CustomizationSettings.GetSettingsAsync();
@ -126,7 +132,7 @@ namespace Ombi.Notifications.Interfaces
} }
else 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) if (!template.Enabled)
{ {
return new NotificationMessageContent {Disabled = true}; return new NotificationMessageContent { Disabled = true };
} }
var parsed = Parse(model, template); var parsed = Parse(model, template);
return parsed; return parsed;
} }
protected IQueryable<OmbiUser> 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) private NotificationMessageContent Parse(NotificationOptions model, NotificationTemplates template)
{ {
var resolver = new NotificationMessageResolver(); var resolver = new NotificationMessageResolver();
@ -166,7 +178,7 @@ namespace Ombi.Notifications.Interfaces
if (model.RequestType == RequestType.Movie) if (model.RequestType == RequestType.Movie)
{ {
_log.LogDebug("Notification options: {@model}, Req: {@MovieRequest}, Settings: {@Customization}", model, MovieRequest, Customization); _log.LogDebug("Notification options: {@model}, Req: {@MovieRequest}, Settings: {@Customization}", model, MovieRequest, Customization);
curlys.Setup(model, MovieRequest, Customization); curlys.Setup(model, MovieRequest, Customization);
} }
else else

@ -42,5 +42,6 @@ namespace Ombi.Store.Context
DbSet<SickRageEpisodeCache> SickRageEpisodeCache { get; set; } DbSet<SickRageEpisodeCache> SickRageEpisodeCache { get; set; }
DbSet<RequestLog> RequestLogs { get; set; } DbSet<RequestLog> RequestLogs { get; set; }
DbSet<RecentlyAddedLog> RecentlyAddedLogs { get; set; } DbSet<RecentlyAddedLog> RecentlyAddedLogs { get; set; }
DbSet<RequestSubscription> RequestSubscription { get; set; }
} }
} }

@ -47,6 +47,7 @@ namespace Ombi.Store.Context
public DbSet<SonarrEpisodeCache> SonarrEpisodeCache { get; set; } public DbSet<SonarrEpisodeCache> SonarrEpisodeCache { get; set; }
public DbSet<SickRageCache> SickRageCache { get; set; } public DbSet<SickRageCache> SickRageCache { get; set; }
public DbSet<SickRageEpisodeCache> SickRageEpisodeCache { get; set; } public DbSet<SickRageEpisodeCache> SickRageEpisodeCache { get; set; }
public DbSet<RequestSubscription> RequestSubscription { get; set; }
public DbSet<ApplicationConfiguration> ApplicationConfigurations { get; set; } public DbSet<ApplicationConfiguration> ApplicationConfigurations { get; set; }

@ -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; }
}
}

@ -13,6 +13,15 @@ namespace Ombi.Store.Entities.Requests
public int? IssueId { get; set; } public int? IssueId { get; set; }
public SeriesType SeriesType { get; set; } public SeriesType SeriesType { get; set; }
/// <summary>
/// This is to see if the user is subscribed in the UI
/// </summary>
[NotMapped]
public bool Subscribed { get; set; }
[NotMapped]
public bool ShowSubscribe { get; set; }
[ForeignKey(nameof(IssueId))] [ForeignKey(nameof(IssueId))]
public List<Issues> Issues { get; set; } public List<Issues> Issues { get; set; }

@ -12,6 +12,11 @@ namespace Ombi.Store.Entities.Requests
[ForeignKey(nameof(IssueId))] [ForeignKey(nameof(IssueId))]
public List<Issues> Issues { get; set; } public List<Issues> Issues { get; set; }
[NotMapped]
public bool Subscribed { get; set; }
[NotMapped]
public bool ShowSubscribe { get; set; }
public int RootPathOverride { get; set; } public int RootPathOverride { get; set; }
public int QualityOverride { get; set; } public int QualityOverride { get; set; }
} }

@ -16,6 +16,7 @@ namespace Ombi.Store.Entities.Requests
public string Background { get; set; } public string Background { get; set; }
public DateTime ReleaseDate { get; set; } public DateTime ReleaseDate { get; set; }
public string Status { get; set; } public string Status { get; set; }
/// <summary> /// <summary>
/// This is so we can correctly send the right amount of seasons to Sonarr /// This is so we can correctly send the right amount of seasons to Sonarr
/// </summary> /// </summary>

@ -0,0 +1,981 @@
// <auto-generated />
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<string>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken();
b.Property<string>("Name")
.HasMaxLength(256);
b.Property<string>("NormalizedName")
.HasMaxLength(256);
b.HasKey("Id");
b.HasIndex("NormalizedName")
.IsUnique()
.HasName("RoleNameIndex");
b.ToTable("AspNetRoles");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("ClaimType");
b.Property<string>("ClaimValue");
b.Property<string>("RoleId")
.IsRequired();
b.HasKey("Id");
b.HasIndex("RoleId");
b.ToTable("AspNetRoleClaims");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("ClaimType");
b.Property<string>("ClaimValue");
b.Property<string>("UserId")
.IsRequired();
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("AspNetUserClaims");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.Property<string>("LoginProvider");
b.Property<string>("ProviderKey");
b.Property<string>("ProviderDisplayName");
b.Property<string>("UserId")
.IsRequired();
b.HasKey("LoginProvider", "ProviderKey");
b.HasIndex("UserId");
b.ToTable("AspNetUserLogins");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.Property<string>("UserId");
b.Property<string>("RoleId");
b.HasKey("UserId", "RoleId");
b.HasIndex("RoleId");
b.ToTable("AspNetUserRoles");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.Property<string>("UserId");
b.Property<string>("LoginProvider");
b.Property<string>("Name");
b.Property<string>("Value");
b.HasKey("UserId", "LoginProvider", "Name");
b.ToTable("AspNetUserTokens");
});
modelBuilder.Entity("Ombi.Store.Entities.ApplicationConfiguration", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("Type");
b.Property<string>("Value");
b.HasKey("Id");
b.ToTable("ApplicationConfiguration");
});
modelBuilder.Entity("Ombi.Store.Entities.Audit", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("AuditArea");
b.Property<int>("AuditType");
b.Property<DateTime>("DateTime");
b.Property<string>("Description");
b.Property<string>("User");
b.HasKey("Id");
b.ToTable("Audit");
});
modelBuilder.Entity("Ombi.Store.Entities.CouchPotatoCache", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("TheMovieDbId");
b.HasKey("Id");
b.ToTable("CouchPotatoCache");
});
modelBuilder.Entity("Ombi.Store.Entities.EmbyContent", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<DateTime>("AddedAt");
b.Property<string>("EmbyId")
.IsRequired();
b.Property<string>("ImdbId");
b.Property<string>("ProviderId");
b.Property<string>("TheMovieDbId");
b.Property<string>("Title");
b.Property<string>("TvDbId");
b.Property<int>("Type");
b.Property<string>("Url");
b.HasKey("Id");
b.ToTable("EmbyContent");
});
modelBuilder.Entity("Ombi.Store.Entities.EmbyEpisode", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<DateTime>("AddedAt");
b.Property<string>("EmbyId");
b.Property<int>("EpisodeNumber");
b.Property<string>("ImdbId");
b.Property<string>("ParentId");
b.Property<string>("ProviderId");
b.Property<int>("SeasonNumber");
b.Property<string>("TheMovieDbId");
b.Property<string>("Title");
b.Property<string>("TvDbId");
b.HasKey("Id");
b.HasIndex("ParentId");
b.ToTable("EmbyEpisode");
});
modelBuilder.Entity("Ombi.Store.Entities.GlobalSettings", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("Content");
b.Property<string>("SettingsName");
b.HasKey("Id");
b.ToTable("GlobalSettings");
});
modelBuilder.Entity("Ombi.Store.Entities.NotificationTemplates", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("Agent");
b.Property<bool>("Enabled");
b.Property<string>("Message");
b.Property<int>("NotificationType");
b.Property<string>("Subject");
b.HasKey("Id");
b.ToTable("NotificationTemplates");
});
modelBuilder.Entity("Ombi.Store.Entities.NotificationUserId", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<DateTime>("AddedAt");
b.Property<string>("PlayerId");
b.Property<string>("UserId");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("NotificationUserId");
});
modelBuilder.Entity("Ombi.Store.Entities.OmbiUser", b =>
{
b.Property<string>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("AccessFailedCount");
b.Property<string>("Alias");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken();
b.Property<string>("Email")
.HasMaxLength(256);
b.Property<bool>("EmailConfirmed");
b.Property<string>("EmbyConnectUserId");
b.Property<int?>("EpisodeRequestLimit");
b.Property<DateTime?>("LastLoggedIn");
b.Property<bool>("LockoutEnabled");
b.Property<DateTimeOffset?>("LockoutEnd");
b.Property<int?>("MovieRequestLimit");
b.Property<string>("NormalizedEmail")
.HasMaxLength(256);
b.Property<string>("NormalizedUserName")
.HasMaxLength(256);
b.Property<string>("PasswordHash");
b.Property<string>("PhoneNumber");
b.Property<bool>("PhoneNumberConfirmed");
b.Property<string>("ProviderUserId");
b.Property<string>("SecurityStamp");
b.Property<bool>("TwoFactorEnabled");
b.Property<string>("UserAccessToken");
b.Property<string>("UserName")
.HasMaxLength(256);
b.Property<int>("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<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("EpisodeNumber");
b.Property<int>("GrandparentKey");
b.Property<int>("Key");
b.Property<int>("ParentKey");
b.Property<int>("SeasonNumber");
b.Property<string>("Title");
b.HasKey("Id");
b.HasIndex("GrandparentKey");
b.ToTable("PlexEpisode");
});
modelBuilder.Entity("Ombi.Store.Entities.PlexSeasonsContent", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("ParentKey");
b.Property<int>("PlexContentId");
b.Property<int?>("PlexServerContentId");
b.Property<int>("SeasonKey");
b.Property<int>("SeasonNumber");
b.HasKey("Id");
b.HasIndex("PlexServerContentId");
b.ToTable("PlexSeasonsContent");
});
modelBuilder.Entity("Ombi.Store.Entities.PlexServerContent", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<DateTime>("AddedAt");
b.Property<string>("ImdbId");
b.Property<int>("Key");
b.Property<string>("Quality");
b.Property<string>("ReleaseYear");
b.Property<string>("TheMovieDbId");
b.Property<string>("Title");
b.Property<string>("TvDbId");
b.Property<int>("Type");
b.Property<string>("Url");
b.HasKey("Id");
b.ToTable("PlexServerContent");
});
modelBuilder.Entity("Ombi.Store.Entities.RadarrCache", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<bool>("HasFile");
b.Property<int>("TheMovieDbId");
b.HasKey("Id");
b.ToTable("RadarrCache");
});
modelBuilder.Entity("Ombi.Store.Entities.RecentlyAddedLog", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<DateTime>("AddedAt");
b.Property<int>("ContentId");
b.Property<int>("ContentType");
b.Property<int?>("EpisodeNumber");
b.Property<int?>("SeasonNumber");
b.Property<int>("Type");
b.HasKey("Id");
b.ToTable("RecentlyAddedLog");
});
modelBuilder.Entity("Ombi.Store.Entities.Requests.ChildRequests", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<bool>("Approved");
b.Property<bool>("Available");
b.Property<bool?>("Denied");
b.Property<string>("DeniedReason");
b.Property<int?>("IssueId");
b.Property<int>("ParentRequestId");
b.Property<int>("RequestType");
b.Property<DateTime>("RequestedDate");
b.Property<string>("RequestedUserId");
b.Property<int>("SeriesType");
b.Property<string>("Title");
b.HasKey("Id");
b.HasIndex("ParentRequestId");
b.HasIndex("RequestedUserId");
b.ToTable("ChildRequests");
});
modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueCategory", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("Value");
b.HasKey("Id");
b.ToTable("IssueCategory");
});
modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueComments", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("Comment");
b.Property<DateTime>("Date");
b.Property<int?>("IssuesId");
b.Property<string>("UserId");
b.HasKey("Id");
b.HasIndex("IssuesId");
b.HasIndex("UserId");
b.ToTable("IssueComments");
});
modelBuilder.Entity("Ombi.Store.Entities.Requests.Issues", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("Description");
b.Property<int>("IssueCategoryId");
b.Property<int?>("IssueId");
b.Property<string>("ProviderId");
b.Property<int?>("RequestId");
b.Property<int>("RequestType");
b.Property<DateTime?>("ResovledDate");
b.Property<int>("Status");
b.Property<string>("Subject");
b.Property<string>("Title");
b.Property<string>("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<int>("Id")
.ValueGeneratedOnAdd();
b.Property<bool>("Approved");
b.Property<bool>("Available");
b.Property<string>("Background");
b.Property<bool?>("Denied");
b.Property<string>("DeniedReason");
b.Property<DateTime?>("DigitalReleaseDate");
b.Property<string>("ImdbId");
b.Property<int?>("IssueId");
b.Property<string>("Overview");
b.Property<string>("PosterPath");
b.Property<int>("QualityOverride");
b.Property<DateTime>("ReleaseDate");
b.Property<int>("RequestType");
b.Property<DateTime>("RequestedDate");
b.Property<string>("RequestedUserId");
b.Property<int>("RootPathOverride");
b.Property<string>("Status");
b.Property<int>("TheMovieDbId");
b.Property<string>("Title");
b.HasKey("Id");
b.HasIndex("RequestedUserId");
b.ToTable("MovieRequests");
});
modelBuilder.Entity("Ombi.Store.Entities.Requests.RequestLog", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("EpisodeCount");
b.Property<DateTime>("RequestDate");
b.Property<int>("RequestId");
b.Property<int>("RequestType");
b.Property<string>("UserId");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("RequestLog");
});
modelBuilder.Entity("Ombi.Store.Entities.Requests.TvRequests", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("Background");
b.Property<string>("ImdbId");
b.Property<string>("Overview");
b.Property<string>("PosterPath");
b.Property<int?>("QualityOverride");
b.Property<DateTime>("ReleaseDate");
b.Property<int?>("RootFolder");
b.Property<string>("Status");
b.Property<string>("Title");
b.Property<int>("TvDbId");
b.HasKey("Id");
b.ToTable("TvRequests");
});
modelBuilder.Entity("Ombi.Store.Entities.RequestSubscription", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("RequestId");
b.Property<int>("RequestType");
b.Property<string>("UserId");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("RequestSubscription");
});
modelBuilder.Entity("Ombi.Store.Entities.SickRageCache", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("TvDbId");
b.HasKey("Id");
b.ToTable("SickRageCache");
});
modelBuilder.Entity("Ombi.Store.Entities.SickRageEpisodeCache", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("EpisodeNumber");
b.Property<int>("SeasonNumber");
b.Property<int>("TvDbId");
b.HasKey("Id");
b.ToTable("SickRageEpisodeCache");
});
modelBuilder.Entity("Ombi.Store.Entities.SonarrCache", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("TvDbId");
b.HasKey("Id");
b.ToTable("SonarrCache");
});
modelBuilder.Entity("Ombi.Store.Entities.SonarrEpisodeCache", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("EpisodeNumber");
b.Property<bool>("HasFile");
b.Property<int>("SeasonNumber");
b.Property<int>("TvDbId");
b.HasKey("Id");
b.ToTable("SonarrEpisodeCache");
});
modelBuilder.Entity("Ombi.Store.Entities.Tokens", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("Token");
b.Property<string>("UserId");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("Tokens");
});
modelBuilder.Entity("Ombi.Store.Repository.Requests.EpisodeRequests", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<DateTime>("AirDate");
b.Property<bool>("Approved");
b.Property<bool>("Available");
b.Property<int>("EpisodeNumber");
b.Property<bool>("Requested");
b.Property<int>("SeasonId");
b.Property<string>("Title");
b.Property<string>("Url");
b.HasKey("Id");
b.HasIndex("SeasonId");
b.ToTable("EpisodeRequests");
});
modelBuilder.Entity("Ombi.Store.Repository.Requests.SeasonRequests", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("ChildRequestId");
b.Property<int>("SeasonNumber");
b.HasKey("Id");
b.HasIndex("ChildRequestId");
b.ToTable("SeasonRequests");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole")
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.HasOne("Ombi.Store.Entities.OmbiUser")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.HasOne("Ombi.Store.Entities.OmbiUser")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", 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<string>", 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
}
}
}

@ -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<int>(nullable: false)
.Annotation("Sqlite:Autoincrement", true),
RequestId = table.Column<int>(nullable: false),
RequestType = table.Column<int>(nullable: false),
UserId = table.Column<string>(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");
}
}
}

@ -20,7 +20,7 @@ namespace Ombi.Store.Migrations
{ {
#pragma warning disable 612, 618 #pragma warning disable 612, 618
modelBuilder modelBuilder
.HasAnnotation("ProductVersion", "2.0.2-rtm-10011"); .HasAnnotation("ProductVersion", "2.0.3-rtm-10026");
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
{ {
@ -676,6 +676,24 @@ namespace Ombi.Store.Migrations
b.ToTable("TvRequests"); b.ToTable("TvRequests");
}); });
modelBuilder.Entity("Ombi.Store.Entities.RequestSubscription", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("RequestId");
b.Property<int>("RequestType");
b.Property<string>("UserId");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("RequestSubscription");
});
modelBuilder.Entity("Ombi.Store.Entities.SickRageCache", b => modelBuilder.Entity("Ombi.Store.Entities.SickRageCache", b =>
{ {
b.Property<int>("Id") b.Property<int>("Id")
@ -927,6 +945,13 @@ namespace Ombi.Store.Migrations
.HasForeignKey("UserId"); .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 => modelBuilder.Entity("Ombi.Store.Entities.Tokens", b =>
{ {
b.HasOne("Ombi.Store.Entities.OmbiUser", "User") b.HasOne("Ombi.Store.Entities.OmbiUser", "User")

@ -57,7 +57,7 @@ namespace Ombi.Store.Repository.Requests
public IQueryable<MovieRequests> GetWithUser(string userId) public IQueryable<MovieRequests> GetWithUser(string userId)
{ {
return Db.MovieRequests return Db.MovieRequests
.Where(x => x.RequestedUserId == userId) .Where(x => x.RequestedUserId == userId)
.Include(x => x.RequestedUser) .Include(x => x.RequestedUser)
.ThenInclude(x => x.NotificationUserIds) .ThenInclude(x => x.NotificationUserIds)
.AsQueryable(); .AsQueryable();

@ -12,6 +12,8 @@ export interface IMovieRequests extends IFullBaseRequest {
rootPathOverride: number; rootPathOverride: number;
qualityOverride: number; qualityOverride: number;
digitalReleaseDate: Date; digitalReleaseDate: Date;
subscribed: boolean;
showSubscribe: boolean;
// For the UI // For the UI
rootPathOverrideTitle: string; rootPathOverrideTitle: string;
@ -77,6 +79,8 @@ export interface ITvRequests {
export interface IChildRequests extends IBaseRequest { export interface IChildRequests extends IBaseRequest {
seasonRequests: INewSeasonRequests[]; seasonRequests: INewSeasonRequests[];
subscribed: boolean;
showSubscribe: boolean;
} }
export interface ITvUpdateModel { export interface ITvUpdateModel {

@ -124,7 +124,13 @@
</div> </div>
</div> </div>
<div class="col-sm-3 col-sm-push-3 small-padding"> <div class="col-sm-2 col-sm-push-3 small-padding">
<div style="float:right">
<a *ngIf="request.showSubscribe && !request.subscribed" style="color:white" (click)="subscribe(request)" pTooltip="Subscribe for notifications"> <i class="fa fa-rss"></i></a>
<a *ngIf="request.showSubscribe && request.subscribed" style="color:red" (click)="unSubscribe(request)" pTooltip="Unsubscribe notification"> <i class="fa fa-rss"></i></a>
</div>
<div *ngIf="isAdmin"> <div *ngIf="isAdmin">
<div *ngIf="!request.approved" id="approveBtn"> <div *ngIf="!request.approved" id="approveBtn">
<form> <form>
@ -164,7 +170,7 @@
</li> </li>
</ul> </ul>
</div> </div>
<div *ngIf="!request.denied" id="denyBtn"> <div *ngIf="!request.denied" id="denyBtn">
<button type="button" (click)="deny(request)" class="btn btn-sm btn-danger-outline deny"> <button type="button" (click)="deny(request)" class="btn btn-sm btn-danger-outline deny">
<i class="fa fa-times"></i> {{ 'Requests.Deny' | translate }} <i class="fa fa-times"></i> {{ 'Requests.Deny' | translate }}
@ -189,7 +195,6 @@
</div> </div>
<div class="dropdown" *ngIf="issueCategories && issuesEnabled" id="issuesBtn"> <div class="dropdown" *ngIf="issueCategories && issuesEnabled" id="issuesBtn">
<button class="btn btn-sm btn-primary-outline dropdown-toggle" type="button" data-toggle="dropdown" aria-haspopup="true" <button class="btn btn-sm btn-primary-outline dropdown-toggle" type="button" data-toggle="dropdown" aria-haspopup="true"
aria-expanded="true"> aria-expanded="true">

@ -216,6 +216,22 @@ export class MovieRequestsComponent implements OnInit {
} }
this.order = value; this.order = value;
} }
public subscribe(request: IMovieRequests) {
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) { private filterActiveStyle(el: any) {
el = el.toElement || el.relatedTarget || el.target || el.srcElement; el = el.toElement || el.relatedTarget || el.target || el.srcElement;

@ -13,7 +13,7 @@ import { RequestComponent } from "./request.component";
import { TvRequestChildrenComponent } from "./tvrequest-children.component"; import { TvRequestChildrenComponent } from "./tvrequest-children.component";
import { TvRequestsComponent } from "./tvrequests.component"; import { TvRequestsComponent } from "./tvrequests.component";
import { SidebarModule, TreeTableModule } from "primeng/primeng"; import { SidebarModule, TooltipModule, TreeTableModule } from "primeng/primeng";
import { IdentityService, RadarrService, RequestService, SonarrService } from "../services"; import { IdentityService, RadarrService, RequestService, SonarrService } from "../services";
@ -37,6 +37,7 @@ const routes: Routes = [
SidebarModule, SidebarModule,
OrderModule, OrderModule,
PaginatorModule, PaginatorModule,
TooltipModule,
], ],
declarations: [ declarations: [
RequestComponent, RequestComponent,

@ -11,8 +11,11 @@
<span *ngIf="isAdmin && !child.requestedUser.alias">{{child.requestedUser.userName}}</span> <span *ngIf="isAdmin && !child.requestedUser.alias">{{child.requestedUser.userName}}</span>
</div> </div>
<div class="col-md-1 col-md-push-9"> <div class="col-md-1 col-md-push-9">
<button id="subscribeBtn" *ngIf="child.showSubscribe && !child.subscribed" (click)="subscribe(child)" class="btn btn-sm btn-primary-outline" pTooltip="Subscribe for notifications" type="submit"><i class="fa fa-rss"></i> Subscribe</button>
<button id="subscribeBtn" *ngIf="child.showSubscribe && child.subscribed" (click)="unSubscribe(child)" class="btn btn-sm btn-danger-outline" pTooltip="UnSubscribe for notifications" type="submit"><i class="fa fa-rss"></i> UnSubscribe</button>
<div *ngIf="isAdmin"> <div *ngIf="isAdmin">
<button id="approveBtn" *ngIf="child.canApprove && !child.approved" (click)="approve(child)" class="btn btn-sm btn-success-outline" type="submit"><i class="fa fa-plus"></i> {{ 'Common.Approve' | translate }}</button> <button id="approveBtn" *ngIf="child.canApprove && !child.approved" (click)="approve(child)" class="btn btn-sm btn-success-outline" type="submit"><i class="fa fa-plus"></i> {{ 'Common.Approve' | translate }}</button>
<button id="unavailableBtn" *ngIf="child.available" (click)="changeAvailability(child, false)" style="text-align: right" value="false" class="btn btn-sm btn-info-outline change"><i class="fa fa-minus"></i> {{ 'Requests.MarkUnavailable' | translate }}</button> <button id="unavailableBtn" *ngIf="child.available" (click)="changeAvailability(child, false)" style="text-align: right" value="false" class="btn btn-sm btn-info-outline change"><i class="fa fa-minus"></i> {{ 'Requests.MarkUnavailable' | translate }}</button>

@ -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) { private removeRequestFromUi(key: IChildRequests) {
const index = this.childRequests.indexOf(key, 0); const index = this.childRequests.indexOf(key, 0);
if (index > -1) { if (index > -1) {

@ -117,4 +117,17 @@ export class RequestService extends ServiceHelpers {
public filterMovies(filter: IFilter): Observable<IFilterResult<IMovieRequests>> { public filterMovies(filter: IFilter): Observable<IFilterResult<IMovieRequests>> {
return this.http.post<IFilterResult<IMovieRequests>>(`${this.url}movie/filter`, JSON.stringify(filter), {headers: this.headers}); return this.http.post<IFilterResult<IMovieRequests>>(`${this.url}movie/filter`, JSON.stringify(filter), {headers: this.headers});
} }
public subscribeToMovie(requestId: number): Observable<boolean> {
return this.http.post<boolean>(`${this.url}movie/subscribe/${requestId}`, {headers: this.headers});
}
public unSubscribeToMovie(requestId: number): Observable<boolean> {
return this.http.post<boolean>(`${this.url}movie/unsubscribe/${requestId}`, {headers: this.headers});
}
public subscribeToTv(requestId: number): Observable<boolean> {
return this.http.post<boolean>(`${this.url}tv/subscribe/${requestId}`, {headers: this.headers});
}
public unSubscribeToTv(requestId: number): Observable<boolean> {
return this.http.post<boolean>(`${this.url}tv/unsubscribe/${requestId}`, {headers: this.headers});
}
} }

@ -9,6 +9,7 @@ using System.Threading.Tasks;
using Ombi.Store.Entities.Requests; using Ombi.Store.Entities.Requests;
using System.Diagnostics; using System.Diagnostics;
using Ombi.Models; using Ombi.Models;
using Ombi.Store.Entities;
namespace Ombi.Controllers namespace Ombi.Controllers
{ {
@ -356,5 +357,45 @@ namespace Ombi.Controllers
{ {
return await MovieRequestEngine.Filter(vm); return await MovieRequestEngine.Filter(vm);
} }
/// <summary>
/// Subscribes for notifications to a movie request
/// </summary>
[HttpPost("movie/subscribe/{requestId:int}")]
public async Task<bool> SubscribeToMovie(int requestId)
{
await MovieRequestEngine.SubscribeToRequest(requestId, RequestType.Movie);
return true;
}
/// <summary>
/// Subscribes for notifications to a TV request
/// </summary>
[HttpPost("tv/subscribe/{requestId:int}")]
public async Task<bool> SubscribeToTv(int requestId)
{
await TvRequestEngine.SubscribeToRequest(requestId, RequestType.TvShow);
return true;
}
/// <summary>
/// UnSubscribes for notifications to a movie request
/// </summary>
[HttpPost("movie/unsubscribe/{requestId:int}")]
public async Task<bool> UnSubscribeToMovie(int requestId)
{
await MovieRequestEngine.UnSubscribeRequest(requestId, RequestType.Movie);
return true;
}
/// <summary>
/// UnSubscribes for notifications to a TV request
/// </summary>
[HttpPost("tv/unsubscribe/{requestId:int}")]
public async Task<bool> UnSubscribeToTv(int requestId)
{
await TvRequestEngine.UnSubscribeRequest(requestId, RequestType.TvShow);
return true;
}
} }
} }
Loading…
Cancel
Save