Merge branch 'develop' of https://github.com/tidusjar/Ombi into develop

pull/2260/head
Anojh 6 years ago
commit 25944659b8

@ -4,8 +4,41 @@
### **New Features** ### **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] - 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()
{ {
@ -108,21 +109,51 @@ namespace Ombi.Core.Engine
protected async Task<HideResult> HideFromOtherUsers() protected async Task<HideResult> 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 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
}; };
if (settings.HideRequestsUsers) return result;
}
public async Task SubscribeToRequest(int requestId, RequestType type)
{ {
var user = await GetUser(); var user = await GetUser();
result.UserId = user.Id; 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
};
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)
{
await _subscriptionRepository.Delete(existingSub);
} }
return result;
} }
public class HideResult public class HideResult

@ -19,5 +19,6 @@ namespace Ombi.Core.Engine.Interfaces
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;

@ -152,6 +152,7 @@ namespace Ombi.DependencyInjection
services.AddTransient<IMattermostNotification, MattermostNotification>(); services.AddTransient<IMattermostNotification, MattermostNotification>();
services.AddTransient<IPushoverNotification, PushoverNotification>(); services.AddTransient<IPushoverNotification, PushoverNotification>();
services.AddTransient<ITelegramNotification, TelegramNotification>(); services.AddTransient<ITelegramNotification, TelegramNotification>();
services.AddTransient<IMobileNotification, MobileNotification>();
services.AddTransient<IChangeLogProcessor, ChangeLogProcessor>(); services.AddTransient<IChangeLogProcessor, ChangeLogProcessor>();
} }

@ -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;
@ -62,7 +63,7 @@ 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
@ -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;

@ -0,0 +1,6 @@
namespace Ombi.Notifications.Agents
{
public interface IMobileNotification : INotification
{
}
}

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

@ -18,11 +18,11 @@ using Ombi.Store.Repository.Requests;
namespace Ombi.Notifications.Agents namespace Ombi.Notifications.Agents
{ {
public class MobileNotification : BaseNotification<MobileNotificationSettings> public class MobileNotification : BaseNotification<MobileNotificationSettings>, IMobileNotification
{ {
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)
@ -227,7 +232,13 @@ namespace Ombi.Notifications.Agents
Message = message, Message = message,
}; };
// Send to user // 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); await Send(playerIds, notification, settings);
} }
@ -269,6 +280,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,6 +28,7 @@ namespace Ombi.Notifications.Interfaces
CustomizationSettings = customization; CustomizationSettings = customization;
Settings.ClearCache(); Settings.ClearCache();
CustomizationSettings.ClearCache(); CustomizationSettings.ClearCache();
RequestSubscription = sub;
_log = log; _log = log;
} }
@ -34,12 +37,14 @@ namespace Ombi.Notifications.Interfaces
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; }
@ -68,6 +73,7 @@ namespace Ombi.Notifications.Interfaces
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();
@ -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();

@ -1,9 +1,11 @@
using System.Threading.Tasks; using System.Collections.Generic;
using System.Threading.Tasks;
namespace Ombi.Schedule.Jobs.Ombi namespace Ombi.Schedule.Jobs.Ombi
{ {
public interface IRefreshMetadata : IBaseJob public interface IRefreshMetadata : IBaseJob
{ {
Task Start(); Task Start();
Task ProcessPlexServerContent(IEnumerable<int> contentIds);
} }
} }

@ -1,4 +1,5 @@
using System; using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@ -17,7 +18,7 @@ namespace Ombi.Schedule.Jobs.Ombi
{ {
public RefreshMetadata(IPlexContentRepository plexRepo, IEmbyContentRepository embyRepo, public RefreshMetadata(IPlexContentRepository plexRepo, IEmbyContentRepository embyRepo,
ILogger<RefreshMetadata> log, ITvMazeApi tvApi, ISettingsService<PlexSettings> plexSettings, ILogger<RefreshMetadata> log, ITvMazeApi tvApi, ISettingsService<PlexSettings> plexSettings,
IMovieDbApi movieApi) IMovieDbApi movieApi, ISettingsService<EmbySettings> embySettings)
{ {
_plexRepo = plexRepo; _plexRepo = plexRepo;
_embyRepo = embyRepo; _embyRepo = embyRepo;
@ -25,6 +26,7 @@ namespace Ombi.Schedule.Jobs.Ombi
_movieApi = movieApi; _movieApi = movieApi;
_tvApi = tvApi; _tvApi = tvApi;
_plexSettings = plexSettings; _plexSettings = plexSettings;
_embySettings = embySettings;
} }
private readonly IPlexContentRepository _plexRepo; private readonly IPlexContentRepository _plexRepo;
@ -33,6 +35,7 @@ namespace Ombi.Schedule.Jobs.Ombi
private readonly IMovieDbApi _movieApi; private readonly IMovieDbApi _movieApi;
private readonly ITvMazeApi _tvApi; private readonly ITvMazeApi _tvApi;
private readonly ISettingsService<PlexSettings> _plexSettings; private readonly ISettingsService<PlexSettings> _plexSettings;
private readonly ISettingsService<EmbySettings> _embySettings;
public async Task Start() public async Task Start()
{ {
@ -43,6 +46,11 @@ namespace Ombi.Schedule.Jobs.Ombi
if (settings.Enable) if (settings.Enable)
{ {
await StartPlex(); await StartPlex();
}
var embySettings = await _embySettings.GetSettingsAsync();
if (embySettings.Enable)
{
await StartEmby(); await StartEmby();
} }
} }
@ -53,12 +61,45 @@ namespace Ombi.Schedule.Jobs.Ombi
} }
} }
public async Task ProcessPlexServerContent(IEnumerable<int> 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<int> 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() 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 // 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() private async Task StartEmby()
@ -67,10 +108,8 @@ namespace Ombi.Schedule.Jobs.Ombi
await StartEmbyTv(); await StartEmbyTv();
} }
private async Task StartPlexTv() private async Task StartPlexTv(IQueryable<PlexServerContent> allTv)
{ {
var allTv = _plexRepo.GetAll().Where(x =>
x.Type == PlexMediaTypeEntity.Show && (!x.TheMovieDbId.HasValue() || !x.ImdbId.HasValue() || !x.TvDbId.HasValue()));
var tvCount = 0; var tvCount = 0;
foreach (var show in allTv) foreach (var show in allTv)
{ {
@ -147,10 +186,8 @@ namespace Ombi.Schedule.Jobs.Ombi
await _embyRepo.SaveChangesAsync(); await _embyRepo.SaveChangesAsync();
} }
private async Task StartPlexMovies() private async Task StartPlexMovies(IQueryable<PlexServerContent> allMovies)
{ {
var allMovies = _plexRepo.GetAll().Where(x =>
x.Type == PlexMediaTypeEntity.Movie && (!x.TheMovieDbId.HasValue() || !x.ImdbId.HasValue()));
int movieCount = 0; int movieCount = 0;
foreach (var movie in allMovies) foreach (var movie in allMovies)
{ {

@ -1,10 +1,14 @@
using System; using System;
using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Ombi.Api.Plex.Models;
using Ombi.Store.Entities;
namespace Ombi.Schedule.Jobs.Plex.Interfaces namespace Ombi.Schedule.Jobs.Plex.Interfaces
{ {
public interface IPlexEpisodeSync : IBaseJob public interface IPlexEpisodeSync : IBaseJob
{ {
Task Start(); Task Start();
Task ProcessEpsiodes(Metadata[] episodes, IQueryable<PlexEpisode> currentEpisodes);
} }
} }

@ -77,46 +77,86 @@ namespace Ombi.Schedule.Jobs.Plex
Logger.LogError("Plex Settings are not valid"); Logger.LogError("Plex Settings are not valid");
return; return;
} }
var processedContent = new HashSet<int>();
Logger.LogInformation("Starting Plex Content Cacher"); Logger.LogInformation("Starting Plex Content Cacher");
try 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) catch (Exception e)
{ {
Logger.LogWarning(LoggingEvents.PlexContentCacher, e, "Exception thrown when attempting to cache the Plex Content"); Logger.LogWarning(LoggingEvents.PlexContentCacher, e, "Exception thrown when attempting to cache the Plex Content");
} }
if (!recentlyAddedSearch)
{
Logger.LogInformation("Starting EP Cacher"); Logger.LogInformation("Starting EP Cacher");
BackgroundJob.Enqueue(() => EpisodeSync.Start()); BackgroundJob.Enqueue(() => EpisodeSync.Start());
BackgroundJob.Enqueue(() => Metadata.Start());
} }
private async Task StartTheCache(PlexSettings plexSettings, bool recentlyAddedSearch) if (processedContent.Any() && recentlyAddedSearch)
{ {
// Just check what we send it
BackgroundJob.Enqueue(() => Metadata.ProcessPlexServerContent(processedContent));
}
}
private async Task<IEnumerable<int>> StartTheCache(PlexSettings plexSettings, bool recentlyAddedSearch)
{
var processedContent = new HashSet<int>();
foreach (var servers in plexSettings.Servers ?? new List<PlexServers>()) foreach (var servers in plexSettings.Servers ?? new List<PlexServers>())
{ {
try try
{ {
Logger.LogInformation("Starting to cache the content on server {0}", servers.Name); 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) catch (Exception e)
{ {
Logger.LogWarning(LoggingEvents.PlexContentCacher, e, "Exception thrown when attempting to cache the Plex Content in server {0}", servers.Name); 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<IEnumerable<int>> ProcessServer(PlexServers servers, bool recentlyAddedSearch)
{ {
var processedContent = new HashSet<int>();
Logger.LogInformation("Getting all content from server {0}", servers.Name); Logger.LogInformation("Getting all content from server {0}", servers.Name);
var allContent = await GetAllContent(servers, recentlyAddedSearch); var allContent = await GetAllContent(servers, recentlyAddedSearch);
Logger.LogInformation("We found {0} items", allContent.Count); Logger.LogInformation("We found {0} items", allContent.Count);
// Let's now process this. // Let's now process this.
var contentToAdd = new HashSet<PlexServerContent>(); var contentToAdd = new HashSet<PlexServerContent>();
var allEps = Repo.GetAllEpisodes();
foreach (var content in allContent) foreach (var content in allContent)
{ {
if (content.viewGroup.Equals(PlexMediaType.Episode.ToString(), StringComparison.CurrentCultureIgnoreCase)) if (content.viewGroup.Equals(PlexMediaType.Episode.ToString(), StringComparison.CurrentCultureIgnoreCase))
@ -133,8 +173,10 @@ namespace Ombi.Schedule.Jobs.Plex
continue; 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)) if (content.viewGroup.Equals(PlexMediaType.Show.ToString(), StringComparison.CurrentCultureIgnoreCase))
{ {
@ -142,7 +184,7 @@ namespace Ombi.Schedule.Jobs.Plex
Logger.LogInformation("Processing TV Shows"); Logger.LogInformation("Processing TV Shows");
foreach (var show in content.Metadata ?? new Metadata[] { }) 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)) if (content.viewGroup.Equals(PlexMediaType.Movie.ToString(), StringComparison.CurrentCultureIgnoreCase))
@ -212,6 +254,10 @@ namespace Ombi.Schedule.Jobs.Plex
if (contentToAdd.Count > 500) if (contentToAdd.Count > 500)
{ {
await Repo.AddRange(contentToAdd); await Repo.AddRange(contentToAdd);
foreach (var c in contentToAdd)
{
processedContent.Add(c.Id);
}
contentToAdd.Clear(); contentToAdd.Clear();
} }
} }
@ -219,6 +265,10 @@ namespace Ombi.Schedule.Jobs.Plex
if (contentToAdd.Count > 500) if (contentToAdd.Count > 500)
{ {
await Repo.AddRange(contentToAdd); await Repo.AddRange(contentToAdd);
foreach (var c in contentToAdd)
{
processedContent.Add(c.Id);
}
contentToAdd.Clear(); contentToAdd.Clear();
} }
} }
@ -226,10 +276,16 @@ namespace Ombi.Schedule.Jobs.Plex
if (contentToAdd.Any()) if (contentToAdd.Any())
{ {
await Repo.AddRange(contentToAdd); await Repo.AddRange(contentToAdd);
foreach (var c in contentToAdd)
{
processedContent.Add(c.Id);
}
} }
return processedContent;
} }
private async Task ProcessTvShow(PlexServers servers, Metadata show, HashSet<PlexServerContent> contentToAdd, bool recentlyAdded) private async Task ProcessTvShow(PlexServers servers, Metadata show, HashSet<PlexServerContent> contentToAdd, bool recentlyAdded, HashSet<int> contentProcessed)
{ {
var seasonList = await PlexApi.GetSeasons(servers.PlexAuthToken, servers.FullUri, var seasonList = await PlexApi.GetSeasons(servers.PlexAuthToken, servers.FullUri,
show.ratingKey); show.ratingKey);
@ -412,8 +468,17 @@ namespace Ombi.Schedule.Jobs.Plex
if (contentToAdd.Count > 500 || recentlyAdded) if (contentToAdd.Count > 500 || recentlyAdded)
{ {
await Repo.AddRange(contentToAdd); await Repo.AddRange(contentToAdd);
foreach (var plexServerContent in contentToAdd)
{
contentProcessed.Add(plexServerContent.Id);
}
contentToAdd.Clear(); contentToAdd.Clear();
} }
if (contentToAdd.Any())
{
await Repo.AddRange(contentToAdd);
}
} }
/// <summary> /// <summary>

@ -111,7 +111,7 @@ namespace Ombi.Schedule.Jobs.Plex
// 12.03.2017 - I think we should be able to match them now // 12.03.2017 - I think we should be able to match them now
//await _repo.ExecuteSql("DELETE FROM PlexEpisode"); //await _repo.ExecuteSql("DELETE FROM PlexEpisode");
await ProcessEpsiodes(episodes, currentEpisodes); await ProcessEpsiodes(episodes?.MediaContainer?.Metadata ?? new Metadata[] { }, currentEpisodes);
currentPosition += resultCount; currentPosition += resultCount;
while (currentPosition < episodes.MediaContainer.totalSize) 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, var ep = await _api.GetAllEpisodes(settings.PlexAuthToken, settings.FullUri, section.key, currentPosition,
resultCount); 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}"); _log.LogInformation(LoggingEvents.PlexEpisodeCacher, $"Processed {resultCount} more episodes. Total Remaining {episodes.MediaContainer.totalSize - currentPosition}");
currentPosition += resultCount; currentPosition += resultCount;
} }
@ -129,12 +129,12 @@ namespace Ombi.Schedule.Jobs.Plex
await _repo.SaveChangesAsync(); await _repo.SaveChangesAsync();
} }
private async Task ProcessEpsiodes(PlexContainer episodes, IQueryable<PlexEpisode> currentEpisodes) public async Task ProcessEpsiodes(Metadata[] episodes, IQueryable<PlexEpisode> currentEpisodes)
{ {
var ep = new HashSet<PlexEpisode>(); var ep = new HashSet<PlexEpisode>();
try 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? // 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 // We have the parent and grandparent rating keys to link up to the season and series

@ -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 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(".", "")), UpdateVersion = release.Version == "(unreleased)" ? 0 : int.Parse(release.Version.Substring(1, 5).Replace(".", "")),
UpdateDate = DateTime.Now, UpdateDate = DateTime.Now,
ChangeLogs = new List<ChangeLog>(), ChangeLogs = new List<ChangeLog>(),

@ -25,7 +25,7 @@ namespace Ombi.Settings.Settings.Models
} }
public static string PlexRecentlyAdded(JobSettings s) 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) public static string CouchPotato(JobSettings s)
{ {
@ -50,7 +50,7 @@ namespace Ombi.Settings.Settings.Models
} }
public static string RefreshMetadata(JobSettings s) 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) private static string Get(string settings, string defaultCron)

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

@ -37,6 +37,8 @@ namespace Ombi.Store.Entities
[NotMapped] [NotMapped]
public bool EmailLogin { get; set; } public bool EmailLogin { get; set; }
[NotMapped] public bool IsSystemUser => UserType == UserType.SystemUser;
[JsonIgnore] [JsonIgnore]
public override string PasswordHash public override string PasswordHash
{ {

@ -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")

@ -101,3 +101,8 @@ export interface IMattermostNotifcationSettings extends INotificationSettings {
export interface IMobileNotifcationSettings extends INotificationSettings { export interface IMobileNotifcationSettings extends INotificationSettings {
notificationTemplates: INotificationTemplates[]; notificationTemplates: INotificationTemplates[];
} }
export interface IMobileNotificationTestSettings {
settings: IMobileNotifcationSettings;
userId: string;
}

@ -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>
@ -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">

@ -217,6 +217,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) {

@ -12,6 +12,7 @@ import {
IEmailNotificationSettings, IEmailNotificationSettings,
IEmbyServer, IEmbyServer,
IMattermostNotifcationSettings, IMattermostNotifcationSettings,
IMobileNotificationTestSettings,
INewsletterNotificationSettings, INewsletterNotificationSettings,
IPlexServer, IPlexServer,
IPushbulletNotificationSettings, IPushbulletNotificationSettings,
@ -82,4 +83,7 @@ export class TesterService extends ServiceHelpers {
public newsletterTest(settings: INewsletterNotificationSettings): Observable<boolean> { public newsletterTest(settings: INewsletterNotificationSettings): Observable<boolean> {
return this.http.post<boolean>(`${this.url}newsletter`, JSON.stringify(settings), {headers: this.headers}); return this.http.post<boolean>(`${this.url}newsletter`, JSON.stringify(settings), {headers: this.headers});
} }
public mobileNotificationTest(settings: IMobileNotificationTestSettings): Observable<boolean> {
return this.http.post<boolean>(`${this.url}mobile`, JSON.stringify(settings), {headers: this.headers});
}
} }

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

@ -35,14 +35,20 @@
<div class="row"> <div class="row">
<div class="form-group"> <div class="form-group">
<label for="select" class="control-label">User to send test notification to</label>
<div> <div>
<button [disabled]="form.invalid" type="button" (click)="test(form)" class="btn btn-primary-outline"> <select class="form-control form-control-custom" id="select" [(ngModel)]="testUserId" [ngModelOptions]="{standalone: true}">
Test <option value="">Please select</option>
<div id="spinner"></div> <option *ngFor="let x of userList" [value]="x.id">{{x.username}}</option>
</button> </select>
</div> </div>
</div> </div>
<div class="form-group">
<div>
<button [disabled]="form.invalid" type="button" (click)="test(form)" class="btn btn-danger-outline">Test</button>
</div>
</div>
<div class="form-group"> <div class="form-group">

@ -15,6 +15,7 @@ export class MobileComponent implements OnInit {
public templates: INotificationTemplates[]; public templates: INotificationTemplates[];
public form: FormGroup; public form: FormGroup;
public userList: IMobileUsersViewModel[]; public userList: IMobileUsersViewModel[];
public testUserId: string;
constructor(private settingsService: SettingsService, constructor(private settingsService: SettingsService,
private notificationService: NotificationService, private notificationService: NotificationService,
@ -64,8 +65,12 @@ export class MobileComponent implements OnInit {
this.notificationService.error("Please check your entered values"); this.notificationService.error("Please check your entered values");
return; 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) { if (x) {
this.notificationService.success("Successfully sent a Mobile message, please check the admin mobile device"); this.notificationService.success("Successfully sent a Mobile message, please check the admin mobile device");
} else { } else {

@ -14,6 +14,7 @@ using Ombi.Core.Models.UI;
using Ombi.Core.Notifications; using Ombi.Core.Notifications;
using Ombi.Core.Settings.Models.External; using Ombi.Core.Settings.Models.External;
using Ombi.Helpers; using Ombi.Helpers;
using Ombi.Models;
using Ombi.Notifications; using Ombi.Notifications;
using Ombi.Notifications.Agents; using Ombi.Notifications.Agents;
using Ombi.Notifications.Models; using Ombi.Notifications.Models;
@ -37,7 +38,7 @@ namespace Ombi.Controllers.External
public TesterController(INotificationService service, IDiscordNotification notification, IEmailNotification emailN, public TesterController(INotificationService service, IDiscordNotification notification, IEmailNotification emailN,
IPushbulletNotification pushbullet, ISlackNotification slack, IPushoverNotification po, IMattermostNotification mm, IPushbulletNotification pushbullet, ISlackNotification slack, IPushoverNotification po, IMattermostNotification mm,
IPlexApi plex, IEmbyApi emby, IRadarrApi radarr, ISonarrApi sonarr, ILogger<TesterController> log, IEmailProvider provider, IPlexApi plex, IEmbyApi emby, IRadarrApi radarr, ISonarrApi sonarr, ILogger<TesterController> log, IEmailProvider provider,
ICouchPotatoApi cpApi, ITelegramNotification telegram, ISickRageApi srApi, INewsletterJob newsletter) ICouchPotatoApi cpApi, ITelegramNotification telegram, ISickRageApi srApi, INewsletterJob newsletter, IMobileNotification mobileNotification)
{ {
Service = service; Service = service;
DiscordNotification = notification; DiscordNotification = notification;
@ -56,6 +57,7 @@ namespace Ombi.Controllers.External
TelegramNotification = telegram; TelegramNotification = telegram;
SickRageApi = srApi; SickRageApi = srApi;
Newsletter = newsletter; Newsletter = newsletter;
MobileNotification = mobileNotification;
} }
private INotificationService Service { get; } private INotificationService Service { get; }
@ -75,6 +77,7 @@ namespace Ombi.Controllers.External
private ITelegramNotification TelegramNotification { get; } private ITelegramNotification TelegramNotification { get; }
private ISickRageApi SickRageApi { get; } private ISickRageApi SickRageApi { get; }
private INewsletterJob Newsletter { get; } private INewsletterJob Newsletter { get; }
private IMobileNotification MobileNotification { get; }
/// <summary> /// <summary>
@ -388,5 +391,21 @@ namespace Ombi.Controllers.External
return false; return false;
} }
} }
[HttpPost("mobile")]
public async Task<bool> 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;
}
}
} }
} }

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

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

@ -191,11 +191,15 @@ namespace Ombi
} }
app.UseHangfireServer(new BackgroundJobServerOptions { WorkerCount = 1, ServerTimeout = TimeSpan.FromDays(1), ShutdownTimeout = TimeSpan.FromDays(1)}); app.UseHangfireServer(new BackgroundJobServerOptions { WorkerCount = 1, ServerTimeout = TimeSpan.FromDays(1), ShutdownTimeout = TimeSpan.FromDays(1)});
if (env.IsDevelopment())
{
app.UseHangfireDashboard(settings.BaseUrl.HasValue() ? $"{settings.BaseUrl}/hangfire" : "/hangfire", app.UseHangfireDashboard(settings.BaseUrl.HasValue() ? $"{settings.BaseUrl}/hangfire" : "/hangfire",
new DashboardOptions new DashboardOptions
{ {
Authorization = new[] { new HangfireAuthorizationFilter() } Authorization = new[] {new HangfireAuthorizationFilter()}
}); });
}
GlobalJobFilters.Filters.Add(new AutomaticRetryAttribute { Attempts = 3 }); GlobalJobFilters.Filters.Add(new AutomaticRetryAttribute { Attempts = 3 });
// Setup the scheduler // Setup the scheduler

@ -58,6 +58,21 @@
--> -->
@*<!-- Start SmartBanner configuration -->
<meta name="smartbanner:title" content="Smart Application">
<meta name="smartbanner:author" content="SmartBanner Contributors">
<meta name="smartbanner:price" content=" ">
<meta name="smartbanner:price-suffix-apple" content=" On the App Store">
<meta name="smartbanner:price-suffix-google" content=" In Google Play">
<meta name="smartbanner:icon-apple" content="http://a3.mzstatic.com/us/r30/Purple60/v4/c1/3b/b0/c13bb085-64c0-cc90-f97a-521b96963986/icon350x350.jpeg">
<meta name="smartbanner:icon-google" content="http://lh3.ggpht.com/f4oX61ljZ6x8aYDELZOgxlvdUEu73-wSQ4fy5bx6fCRISnZP8T353wdaM43RO_DbGg=w300">
<meta name="smartbanner:button" content="View">
<meta name="smartbanner:button-url-apple" content="https://itunes.apple.com/us/genre/ios/id36?mt=8">
<meta name="smartbanner:button-url-google" content="https://play.google.com/store">
<meta name="smartbanner:enabled-platforms" content="android,ios">e.com/store/apps/details?id=com.tidusjar.Ombi">
<meta name="smartbanner:enabled-platforms" content="android,ios">
<!-- End SmartBanner configuration -->*@
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta http-equiv="x-ua-compatible" content="ie=edge"> <meta http-equiv="x-ua-compatible" content="ie=edge">
<meta name="description" content="Ombi, media request tool"> <meta name="description" content="Ombi, media request tool">
@ -78,6 +93,7 @@
<meta name="apple-mobile-web-app-capable" content="yes"> <meta name="apple-mobile-web-app-capable" content="yes">
<meta name="mobile-web-app-capable" content="yes"> <meta name="mobile-web-app-capable" content="yes">
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet"> <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
<link rel="stylesheet" href="~/loading.css" asp-append-version="true" /> <link rel="stylesheet" href="~/loading.css" asp-append-version="true" />

@ -67,6 +67,8 @@ module.exports = (env: any) => {
"@ngx-translate/core", "@ngx-translate/core",
"@ngx-translate/http-loader", "@ngx-translate/http-loader",
"ngx-order-pipe", "ngx-order-pipe",
//"smartbanner.js/dist/smartbanner.js",
//"smartbanner.js/dist/smartbanner.css",
], ],
}, },
output: { output: {

Loading…
Cancel
Save