diff --git a/src/Ombi.Core/Engine/MovieRequestEngine.cs b/src/Ombi.Core/Engine/MovieRequestEngine.cs index 68cd27f57..c290f7fed 100644 --- a/src/Ombi.Core/Engine/MovieRequestEngine.cs +++ b/src/Ombi.Core/Engine/MovieRequestEngine.cs @@ -16,6 +16,8 @@ using System.Security.Principal; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Ombi.Core.Engine.Interfaces; +using Ombi.Core.Notifications; +using Ombi.Store; namespace Ombi.Core.Engine { @@ -136,10 +138,10 @@ namespace Ombi.Core.Engine { //Log.Fatal(e); //await FaultQueue.QueueItemAsync(model, movieInfo.Id.ToString(), RequestType.Movie, FaultType.RequestFault, e.Message); - var notification = new NotificationModel + var notification = new NotificationOptions { DateTime = DateTime.Now, - User = Username, + RequestedUser = Username, RequestType = RequestType.Movie, Title = model.Title, NotificationType = NotificationType.ItemAddedToFaultQueue @@ -208,10 +210,10 @@ namespace Ombi.Core.Engine if (ShouldSendNotification(model.Type)) { - var notificationModel = new NotificationModel + var notificationModel = new NotificationOptions { Title = model.Title, - User = Username, + RequestedUser = Username, DateTime = DateTime.Now, NotificationType = NotificationType.NewRequest, RequestType = model.Type, diff --git a/src/Ombi.Core/Engine/TvRequestEngine.cs b/src/Ombi.Core/Engine/TvRequestEngine.cs index 6ff32684c..f10ea56f0 100644 --- a/src/Ombi.Core/Engine/TvRequestEngine.cs +++ b/src/Ombi.Core/Engine/TvRequestEngine.cs @@ -15,6 +15,8 @@ using System.Linq; using System.Security.Principal; using System.Threading.Tasks; using Ombi.Core.Engine.Interfaces; +using Ombi.Core.Notifications; +using Ombi.Store; namespace Ombi.Core.Engine { @@ -272,17 +274,20 @@ namespace Ombi.Core.Engine { if (ShouldSendNotification(model.Type)) { - var notificationModel = new NotificationModel - { - Title = model.Title, - User = Username, - DateTime = DateTime.Now, - NotificationType = NotificationType.NewRequest, - RequestType = model.Type, - ImgSrc = model.PosterPath - }; + var n = new NotificationOptions(); - BackgroundJob.Enqueue(() => NotificationService.Publish(notificationModel).Wait()); + n.Title = model.Title; + n.RequestedUser = Username; + n.DateTime = DateTime.Now; + n.NotificationType = NotificationType.NewRequest; + n.RequestType = model.Type; + n.ImgSrc = model.PosterPath; + + + BackgroundJob.Enqueue(() => + + NotificationService.Publish(n).Wait() + ); } //var limit = await RequestLimitRepo.GetAllAsync(); diff --git a/src/Ombi.Core/Models/Requests/RequestModel.cs b/src/Ombi.Core/Models/Requests/RequestModel.cs index 390ef015c..48b55c4d2 100644 --- a/src/Ombi.Core/Models/Requests/RequestModel.cs +++ b/src/Ombi.Core/Models/Requests/RequestModel.cs @@ -4,24 +4,6 @@ using System.Collections.Generic; namespace Ombi.Core.Models.Requests { - public static class RequestTypeDisplay - { - public static string GetString(this RequestType type) - { - switch (type) - { - case RequestType.Movie: - return "Movie"; - - case RequestType.TvShow: - return "TV Show"; - - default: - return string.Empty; - } - } - } - public enum IssueState { None = 99, diff --git a/src/Ombi.Core/Models/UI/EmailNotificationsViewModel.cs b/src/Ombi.Core/Models/UI/EmailNotificationsViewModel.cs new file mode 100644 index 000000000..1a71c95c5 --- /dev/null +++ b/src/Ombi.Core/Models/UI/EmailNotificationsViewModel.cs @@ -0,0 +1,21 @@ +using System.Collections.Generic; +using Ombi.Settings.Settings.Models.Notifications; +using Ombi.Store.Entities; + +namespace Ombi.Models.Notifications +{ + /// + /// The view model for the notification settings page + /// + /// + public class EmailNotificationsViewModel : EmailNotificationSettings + { + /// + /// Gets or sets the notification templates. + /// + /// + /// The notification templates. + /// + public List NotificationTemplates { get; set; } + } +} diff --git a/src/Ombi.DependencyInjection/IocExtensions.cs b/src/Ombi.DependencyInjection/IocExtensions.cs index f88d41e81..f9246a7a6 100644 --- a/src/Ombi.DependencyInjection/IocExtensions.cs +++ b/src/Ombi.DependencyInjection/IocExtensions.cs @@ -16,6 +16,7 @@ using Ombi.Core.Engine; using Ombi.Core.Engine.Interfaces; using Ombi.Core.IdentityResolver; using Ombi.Core.Models.Requests; +using Ombi.Core.Notifications; using Ombi.Core.Requests.Models; using Ombi.Core.Rule; using Ombi.Core.Settings; @@ -75,6 +76,7 @@ namespace Ombi.DependencyInjection services.AddTransient(); services.AddTransient(); services.AddTransient(); + services.AddTransient(); services.AddTransient(typeof(ISettingsService<>), typeof(SettingsServiceV2<>)); } public static void RegisterServices(this IServiceCollection services) diff --git a/src/Ombi.Helpers/NotificationAgent.cs b/src/Ombi.Helpers/NotificationAgent.cs new file mode 100644 index 000000000..c1b3515dc --- /dev/null +++ b/src/Ombi.Helpers/NotificationAgent.cs @@ -0,0 +1,12 @@ +namespace Ombi.Helpers +{ + public enum NotificationAgent + { + Email, + Discord, + Pushbullet, + Pushover, + Telegram, + Slack + } +} \ No newline at end of file diff --git a/src/Ombi.Notifications/NotificationType.cs b/src/Ombi.Helpers/NotificationType.cs similarity index 82% rename from src/Ombi.Notifications/NotificationType.cs rename to src/Ombi.Helpers/NotificationType.cs index 7954f703b..08d57cfad 100644 --- a/src/Ombi.Notifications/NotificationType.cs +++ b/src/Ombi.Helpers/NotificationType.cs @@ -1,6 +1,4 @@ -using System; - -namespace Ombi.Notifications +namespace Ombi.Helpers { public enum NotificationType { diff --git a/src/Ombi.Mapping.Maps/Ombi.Mapping.Maps.csproj b/src/Ombi.Mapping.Maps/Ombi.Mapping.Maps.csproj deleted file mode 100644 index 19d3d3815..000000000 --- a/src/Ombi.Mapping.Maps/Ombi.Mapping.Maps.csproj +++ /dev/null @@ -1,19 +0,0 @@ - - - - netstandard1.6 - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/Ombi.Mapping.Maps/OmbiProfile.cs b/src/Ombi.Mapping.Maps/OmbiProfile.cs deleted file mode 100644 index b79d9f760..000000000 --- a/src/Ombi.Mapping.Maps/OmbiProfile.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; -using AutoMapper; - -namespace Ombi.Mapping.Maps -{ - public class OmbiProfile : Profile - { - public OmbiProfile() - { - // Add as many of these lines as you need to map your objects - - } - } - -} diff --git a/src/Ombi.Mapping/Profiles/SettingsProfile.cs b/src/Ombi.Mapping/Profiles/SettingsProfile.cs new file mode 100644 index 000000000..c75c16f81 --- /dev/null +++ b/src/Ombi.Mapping/Profiles/SettingsProfile.cs @@ -0,0 +1,14 @@ +using AutoMapper; +using Ombi.Models.Notifications; +using Ombi.Settings.Settings.Models.Notifications; + +namespace Ombi.Mapping.Profiles +{ + public class SettingsProfile : Profile + { + public SettingsProfile() + { + CreateMap().ReverseMap(); + } + } +} \ No newline at end of file diff --git a/src/Ombi.Notification.Agents/EmailNotification.cs b/src/Ombi.Notification.Agents/EmailNotification.cs deleted file mode 100644 index f5986dc34..000000000 --- a/src/Ombi.Notification.Agents/EmailNotification.cs +++ /dev/null @@ -1,227 +0,0 @@ -using System; -using System.Threading.Tasks; -using MailKit.Net.Smtp; -using MimeKit; -using Ombi.Core.Models.Requests; -using Ombi.Core.Settings; -using Ombi.Notifications.Models; -using Ombi.Notifications.Templates; -using Ombi.Settings.Settings.Models.Notifications; - -namespace Ombi.Notifications.Email -{ - public class EmailNotification : BaseNotification - { - public EmailNotification(ISettingsService settings) : base(settings) - { - } - - public override string NotificationName => nameof(EmailNotification); - - protected override bool ValidateConfiguration(EmailNotificationSettings settings) - { - if (!settings.Enabled) - { - return false; - } - if (settings.Authentication) - { - if (string.IsNullOrEmpty(settings.EmailUsername) || string.IsNullOrEmpty(settings.EmailPassword)) - { - return false; - } - } - if (string.IsNullOrEmpty(settings.EmailHost) || string.IsNullOrEmpty(settings.RecipientEmail) || string.IsNullOrEmpty(settings.EmailPort.ToString())) - { - return false; - } - - return true; - } - - protected override async Task NewRequest(NotificationModel model, EmailNotificationSettings settings) - { - var email = new EmailBasicTemplate(); - var html = email.LoadTemplate( - $"Ombi: New {model.RequestType.GetString()?.ToLower()} request for {model.Title}!", - $"Hello! The user '{model.User}' has requested the {model.RequestType.GetString()?.ToLower()} '{model.Title}'! Please log in to approve this request. Request Date: {model.DateTime:f}", - model.ImgSrc); - - - var message = new NotificationMessage - { - Message = html, - Subject = $"Ombi: New {model.RequestType.GetString()?.ToLower()} request for {model.Title}!", - To = settings.RecipientEmail, - }; - - message.Other.Add("PlainTextBody", $"Hello! The user '{model.User}' has requested the {model.RequestType.GetString()?.ToLower()} '{model.Title}'! Please log in to approve this request. Request Date: {model.DateTime:f}"); - - await Send(message, settings); - } - - protected override async Task Issue(NotificationModel model, EmailNotificationSettings settings) - { - var email = new EmailBasicTemplate(); - var html = email.LoadTemplate( - $"Ombi: New issue for {model.Title}!", - $"Hello! The user '{model.User}' has reported a new issue {model.Body} for the title {model.Title}!", - model.ImgSrc); - - var message = new NotificationMessage - { - Message = html, - Subject = $"Ombi: New issue for {model.Title}!", - To = settings.RecipientEmail, - }; - - message.Other.Add("PlainTextBody", $"Hello! The user '{model.User}' has reported a new issue {model.Body} for the title {model.Title}!"); - - await Send(message, settings); - } - - protected override async Task AddedToRequestQueue(NotificationModel model, EmailNotificationSettings settings) - { - var email = new EmailBasicTemplate(); - var html = email.LoadTemplate( - "Ombi: A request could not be added.", - $"Hello! The user '{model.User}' has requested {model.Title} but it could not be added. This has been added into the requests queue and will keep retrying", - model.ImgSrc); - - var message = new NotificationMessage - { - Message = html, - Subject = $"Ombi: A request could not be added", - To = settings.RecipientEmail, - }; - - message.Other.Add("PlainTextBody", $"Hello! The user '{model.User}' has requested {model.Title} but it could not be added. This has been added into the requests queue and will keep retrying"); - - - await Send(message, settings); - } - - protected override async Task RequestDeclined(NotificationModel model, EmailNotificationSettings settings) - { - var email = new EmailBasicTemplate(); - var html = email.LoadTemplate( - "Ombi: Your request has been declined", - $"Hello! Your request for {model.Title} has been declined, Sorry!", - model.ImgSrc); - - var message = new NotificationMessage - { - Message = html, - Subject = $"Ombi: Your request has been declined", - To = model.UserEmail, - }; - - message.Other.Add("PlainTextBody", $"Hello! Your request for {model.Title} has been declined, Sorry!"); - - - await Send(message, settings); - } - - protected override async Task RequestApproved(NotificationModel model, EmailNotificationSettings settings) - { - var email = new EmailBasicTemplate(); - var html = email.LoadTemplate( - "Ombi: Your request has been approved!", - $"Hello! Your request for {model.Title} has been approved!", - model.ImgSrc); - - var message = new NotificationMessage - { - Message = html, - Subject = $"Ombi: Your request has been approved!", - To = model.UserEmail, - }; - - message.Other.Add("PlainTextBody", $"Hello! Your request for {model.Title} has been approved!"); - - await Send(message, settings); - } - - protected override async Task AvailableRequest(NotificationModel model, EmailNotificationSettings settings) - { - var email = new EmailBasicTemplate(); - var html = email.LoadTemplate( - $"Ombi: {model.Title} is now available!", - $"Hello! You requested {model.Title} on Ombi! This is now available on Plex! :)", - model.ImgSrc); - - - var message = new NotificationMessage - { - Message = html, - Subject = $"Ombi: {model.Title} is now available!", - To = model.UserEmail, - }; - - message.Other.Add("PlainTextBody", $"Hello! You requested {model.Title} on Ombi! This is now available on Plex! :)"); - - await Send(message, settings); - } - - protected override async Task Send(NotificationMessage model, EmailNotificationSettings settings) - { - try - { - var body = new BodyBuilder - { - HtmlBody = model.Message, - TextBody = model.Other["PlainTextBody"] - }; - - var message = new MimeMessage - { - Body = body.ToMessageBody(), - Subject = model.Subject - }; - message.From.Add(new MailboxAddress(settings.EmailSender, settings.EmailSender)); - message.To.Add(new MailboxAddress(model.To, model.To)); - - using (var client = new SmtpClient()) - { - client.Connect(settings.EmailHost, settings.EmailPort); // Let MailKit figure out the correct SecureSocketOptions. - - // Note: since we don't have an OAuth2 token, disable - // the XOAUTH2 authentication mechanism. - client.AuthenticationMechanisms.Remove("XOAUTH2"); - - if (settings.Authentication) - { - client.Authenticate(settings.EmailUsername, settings.EmailPassword); - } - //Log.Info("sending message to {0} \r\n from: {1}\r\n Are we authenticated: {2}", message.To, message.From, client.IsAuthenticated); - await client.SendAsync(message); - await client.DisconnectAsync(true); - } - } - catch (Exception e) - { - //Log.Error(e); - throw new InvalidOperationException(e.Message); - } - } - - protected override async Task Test(NotificationModel model, EmailNotificationSettings settings) - { - var email = new EmailBasicTemplate(); - var html = email.LoadTemplate( - "Test Message", - "This is just a test! Success!", - model.ImgSrc); - var message = new NotificationMessage - { - Message = html, - Subject = $"Ombi: Test", - To = settings.RecipientEmail, - }; - - message.Other.Add("PlainTextBody", "This is just a test! Success!"); - - await Send(message, settings); - } - } -} diff --git a/src/Ombi.Notification.Agents/Ombi.Notification.Agents.csproj b/src/Ombi.Notification.Agents/Ombi.Notification.Agents.csproj deleted file mode 100644 index 29ae93c81..000000000 --- a/src/Ombi.Notification.Agents/Ombi.Notification.Agents.csproj +++ /dev/null @@ -1,15 +0,0 @@ - - - - netstandard1.6 - - - - - - - - - - - \ No newline at end of file diff --git a/src/Ombi.Notifications.Email/Ombi.Notifications.Email.csproj b/src/Ombi.Notifications.Email/Ombi.Notifications.Email.csproj deleted file mode 100644 index 6732c9c78..000000000 --- a/src/Ombi.Notifications.Email/Ombi.Notifications.Email.csproj +++ /dev/null @@ -1,18 +0,0 @@ - - - - netstandard1.6 - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/Ombi.Notification.Agents/DiscordNotification.cs b/src/Ombi.Notifications/Agents/DiscordNotification.cs similarity index 79% rename from src/Ombi.Notification.Agents/DiscordNotification.cs rename to src/Ombi.Notifications/Agents/DiscordNotification.cs index c82fb55d4..f3e94c3b3 100644 --- a/src/Ombi.Notification.Agents/DiscordNotification.cs +++ b/src/Ombi.Notifications/Agents/DiscordNotification.cs @@ -4,15 +4,15 @@ using Microsoft.Extensions.Logging; using Ombi.Api.Discord; using Ombi.Core.Settings; using Ombi.Helpers; -using Ombi.Notifications; using Ombi.Notifications.Models; using Ombi.Settings.Settings.Models.Notifications; +using Ombi.Store.Repository; -namespace Ombi.Notification.Agents +namespace Ombi.Notifications.Agents { public class DiscordNotification : BaseNotification { - public DiscordNotification(IDiscordApi api, ISettingsService sn, ILogger log) : base(sn) + public DiscordNotification(IDiscordApi api, ISettingsService sn, ILogger log, INotificationTemplatesRepository r) : base(sn, r) { Api = api; Logger = log; @@ -45,9 +45,9 @@ namespace Ombi.Notification.Agents return true; } - protected override async Task NewRequest(NotificationModel model, DiscordNotificationSettings settings) + protected override async Task NewRequest(NotificationOptions model, DiscordNotificationSettings settings) { - var message = $"{model.Title} has been requested by user: {model.User}"; + var message = $"{model.Title} has been requested by user: {model.RequestedUser}"; var notification = new NotificationMessage { @@ -56,9 +56,9 @@ namespace Ombi.Notification.Agents await Send(notification, settings); } - protected override async Task Issue(NotificationModel model, DiscordNotificationSettings settings) + protected override async Task Issue(NotificationOptions model, DiscordNotificationSettings settings) { - var message = $"A new issue: {model.Body} has been reported by user: {model.User} for the title: {model.Title}"; + var message = $"A new issue: {model.Body} has been reported by user: {model.RequestedUser} for the title: {model.Title}"; var notification = new NotificationMessage { Message = message, @@ -66,9 +66,9 @@ namespace Ombi.Notification.Agents await Send(notification, settings); } - protected override async Task AddedToRequestQueue(NotificationModel model, DiscordNotificationSettings settings) + protected override async Task AddedToRequestQueue(NotificationOptions model, DiscordNotificationSettings settings) { - var message = $"Hello! The user '{model.User}' has requested {model.Title} but it could not be added. This has been added into the requests queue and will keep retrying"; + var message = $"Hello! The user '{model.RequestedUser}' has requested {model.Title} but it could not be added. This has been added into the requests queue and will keep retrying"; var notification = new NotificationMessage { Message = message, @@ -76,7 +76,7 @@ namespace Ombi.Notification.Agents await Send(notification, settings); } - protected override async Task RequestDeclined(NotificationModel model, DiscordNotificationSettings settings) + protected override async Task RequestDeclined(NotificationOptions model, DiscordNotificationSettings settings) { var message = $"Hello! Your request for {model.Title} has been declined, Sorry!"; var notification = new NotificationMessage @@ -86,7 +86,7 @@ namespace Ombi.Notification.Agents await Send(notification, settings); } - protected override async Task RequestApproved(NotificationModel model, DiscordNotificationSettings settings) + protected override async Task RequestApproved(NotificationOptions model, DiscordNotificationSettings settings) { var message = $"Hello! The request for {model.Title} has now been approved!"; var notification = new NotificationMessage @@ -96,7 +96,7 @@ namespace Ombi.Notification.Agents await Send(notification, settings); } - protected override async Task AvailableRequest(NotificationModel model, DiscordNotificationSettings settings) + protected override async Task AvailableRequest(NotificationOptions model, DiscordNotificationSettings settings) { var message = $"Hello! The request for {model.Title} is now available!"; var notification = new NotificationMessage @@ -118,7 +118,7 @@ namespace Ombi.Notification.Agents } } - protected override async Task Test(NotificationModel model, DiscordNotificationSettings settings) + protected override async Task Test(NotificationOptions model, DiscordNotificationSettings settings) { var message = $"This is a test from Ombi, if you can see this then we have successfully pushed a notification!"; var notification = new NotificationMessage diff --git a/src/Ombi.Notifications.Email/EmailNotification.cs b/src/Ombi.Notifications/Agents/EmailNotification.cs similarity index 68% rename from src/Ombi.Notifications.Email/EmailNotification.cs rename to src/Ombi.Notifications/Agents/EmailNotification.cs index f5986dc34..76ee5cb00 100644 --- a/src/Ombi.Notifications.Email/EmailNotification.cs +++ b/src/Ombi.Notifications/Agents/EmailNotification.cs @@ -2,17 +2,18 @@ using System.Threading.Tasks; using MailKit.Net.Smtp; using MimeKit; -using Ombi.Core.Models.Requests; using Ombi.Core.Settings; +using Ombi.Helpers; using Ombi.Notifications.Models; using Ombi.Notifications.Templates; using Ombi.Settings.Settings.Models.Notifications; +using Ombi.Store.Repository; -namespace Ombi.Notifications.Email +namespace Ombi.Notifications.Agents { public class EmailNotification : BaseNotification { - public EmailNotification(ISettingsService settings) : base(settings) + public EmailNotification(ISettingsService settings, INotificationTemplatesRepository r) : base(settings, r) { } @@ -26,12 +27,12 @@ namespace Ombi.Notifications.Email } if (settings.Authentication) { - if (string.IsNullOrEmpty(settings.EmailUsername) || string.IsNullOrEmpty(settings.EmailPassword)) + if (string.IsNullOrEmpty(settings.Username) || string.IsNullOrEmpty(settings.Password)) { return false; } } - if (string.IsNullOrEmpty(settings.EmailHost) || string.IsNullOrEmpty(settings.RecipientEmail) || string.IsNullOrEmpty(settings.EmailPort.ToString())) + if (string.IsNullOrEmpty(settings.Host) || string.IsNullOrEmpty(settings.AdminEmail) || string.IsNullOrEmpty(settings.Port.ToString())) { return false; } @@ -39,69 +40,78 @@ namespace Ombi.Notifications.Email return true; } - protected override async Task NewRequest(NotificationModel model, EmailNotificationSettings settings) + private async Task LoadTemplate(NotificationType type, NotificationOptions model) { + var template = await TemplateRepository.GetTemplate(NotificationAgent.Email, type); + // Need to do the parsing + var resolver = new NotificationMessageResolver(); + return resolver.ParseMessage(template, new NotificationMessageCurlys(model.RequestedUser, model.Title, DateTime.Now.ToString("D"), + model.NotificationType.ToString(), null)); + + } + + protected override async Task NewRequest(NotificationOptions model, EmailNotificationSettings settings) + { + var template = await LoadTemplate(NotificationType.NewRequest, model); + var email = new EmailBasicTemplate(); - var html = email.LoadTemplate( - $"Ombi: New {model.RequestType.GetString()?.ToLower()} request for {model.Title}!", - $"Hello! The user '{model.User}' has requested the {model.RequestType.GetString()?.ToLower()} '{model.Title}'! Please log in to approve this request. Request Date: {model.DateTime:f}", - model.ImgSrc); + var html = email.LoadTemplate(template.Subject, template.Message, model.ImgSrc); var message = new NotificationMessage { Message = html, - Subject = $"Ombi: New {model.RequestType.GetString()?.ToLower()} request for {model.Title}!", - To = settings.RecipientEmail, + Subject = $"Ombi: New {model.RequestType} request for {model.Title}!", + To = settings.AdminEmail, }; - message.Other.Add("PlainTextBody", $"Hello! The user '{model.User}' has requested the {model.RequestType.GetString()?.ToLower()} '{model.Title}'! Please log in to approve this request. Request Date: {model.DateTime:f}"); + message.Other.Add("PlainTextBody", $"Hello! The user '{model.RequestedUser}' has requested the {model.RequestType} '{model.Title}'! Please log in to approve this request. Request Date: {model.DateTime:f}"); await Send(message, settings); } - protected override async Task Issue(NotificationModel model, EmailNotificationSettings settings) + protected override async Task Issue(NotificationOptions model, EmailNotificationSettings settings) { var email = new EmailBasicTemplate(); var html = email.LoadTemplate( $"Ombi: New issue for {model.Title}!", - $"Hello! The user '{model.User}' has reported a new issue {model.Body} for the title {model.Title}!", + $"Hello! The user '{model.RequestedUser}' has reported a new issue {model.Body} for the title {model.Title}!", model.ImgSrc); var message = new NotificationMessage { Message = html, Subject = $"Ombi: New issue for {model.Title}!", - To = settings.RecipientEmail, + To = settings.AdminEmail, }; - message.Other.Add("PlainTextBody", $"Hello! The user '{model.User}' has reported a new issue {model.Body} for the title {model.Title}!"); + message.Other.Add("PlainTextBody", $"Hello! The user '{model.RequestedUser}' has reported a new issue {model.Body} for the title {model.Title}!"); await Send(message, settings); } - protected override async Task AddedToRequestQueue(NotificationModel model, EmailNotificationSettings settings) + protected override async Task AddedToRequestQueue(NotificationOptions model, EmailNotificationSettings settings) { var email = new EmailBasicTemplate(); var html = email.LoadTemplate( "Ombi: A request could not be added.", - $"Hello! The user '{model.User}' has requested {model.Title} but it could not be added. This has been added into the requests queue and will keep retrying", + $"Hello! The user '{model.RequestedUser}' has requested {model.Title} but it could not be added. This has been added into the requests queue and will keep retrying", model.ImgSrc); var message = new NotificationMessage { Message = html, Subject = $"Ombi: A request could not be added", - To = settings.RecipientEmail, + To = settings.AdminEmail, }; - message.Other.Add("PlainTextBody", $"Hello! The user '{model.User}' has requested {model.Title} but it could not be added. This has been added into the requests queue and will keep retrying"); + message.Other.Add("PlainTextBody", $"Hello! The user '{model.RequestedUser}' has requested {model.Title} but it could not be added. This has been added into the requests queue and will keep retrying"); await Send(message, settings); } - protected override async Task RequestDeclined(NotificationModel model, EmailNotificationSettings settings) + protected override async Task RequestDeclined(NotificationOptions model, EmailNotificationSettings settings) { var email = new EmailBasicTemplate(); var html = email.LoadTemplate( @@ -122,7 +132,7 @@ namespace Ombi.Notifications.Email await Send(message, settings); } - protected override async Task RequestApproved(NotificationModel model, EmailNotificationSettings settings) + protected override async Task RequestApproved(NotificationOptions model, EmailNotificationSettings settings) { var email = new EmailBasicTemplate(); var html = email.LoadTemplate( @@ -142,7 +152,7 @@ namespace Ombi.Notifications.Email await Send(message, settings); } - protected override async Task AvailableRequest(NotificationModel model, EmailNotificationSettings settings) + protected override async Task AvailableRequest(NotificationOptions model, EmailNotificationSettings settings) { var email = new EmailBasicTemplate(); var html = email.LoadTemplate( @@ -178,12 +188,12 @@ namespace Ombi.Notifications.Email Body = body.ToMessageBody(), Subject = model.Subject }; - message.From.Add(new MailboxAddress(settings.EmailSender, settings.EmailSender)); + message.From.Add(new MailboxAddress(settings.Sender, settings.Sender)); message.To.Add(new MailboxAddress(model.To, model.To)); using (var client = new SmtpClient()) { - client.Connect(settings.EmailHost, settings.EmailPort); // Let MailKit figure out the correct SecureSocketOptions. + client.Connect(settings.Host, settings.Port); // Let MailKit figure out the correct SecureSocketOptions. // Note: since we don't have an OAuth2 token, disable // the XOAUTH2 authentication mechanism. @@ -191,7 +201,7 @@ namespace Ombi.Notifications.Email if (settings.Authentication) { - client.Authenticate(settings.EmailUsername, settings.EmailPassword); + client.Authenticate(settings.Username, settings.Password); } //Log.Info("sending message to {0} \r\n from: {1}\r\n Are we authenticated: {2}", message.To, message.From, client.IsAuthenticated); await client.SendAsync(message); @@ -205,7 +215,7 @@ namespace Ombi.Notifications.Email } } - protected override async Task Test(NotificationModel model, EmailNotificationSettings settings) + protected override async Task Test(NotificationOptions model, EmailNotificationSettings settings) { var email = new EmailBasicTemplate(); var html = email.LoadTemplate( @@ -216,7 +226,7 @@ namespace Ombi.Notifications.Email { Message = html, Subject = $"Ombi: Test", - To = settings.RecipientEmail, + To = settings.AdminEmail, }; message.Other.Add("PlainTextBody", "This is just a test! Success!"); diff --git a/src/Ombi.Notifications/BaseNotification.cs b/src/Ombi.Notifications/Interfaces/BaseNotification.cs similarity index 73% rename from src/Ombi.Notifications/BaseNotification.cs rename to src/Ombi.Notifications/Interfaces/BaseNotification.cs index fe81ac2c4..ad83ffb34 100644 --- a/src/Ombi.Notifications/BaseNotification.cs +++ b/src/Ombi.Notifications/Interfaces/BaseNotification.cs @@ -1,28 +1,32 @@ using System; using System.Threading.Tasks; using Ombi.Core.Settings; -using Ombi.Core.Settings.Models; +using Ombi.Helpers; using Ombi.Notifications.Models; +using Ombi.Store; +using Ombi.Store.Repository; namespace Ombi.Notifications { public abstract class BaseNotification : INotification where T : Settings.Settings.Models.Settings, new() { - protected BaseNotification(ISettingsService settings) + protected BaseNotification(ISettingsService settings, INotificationTemplatesRepository templateRepo) { Settings = settings; + TemplateRepository = templateRepo; } protected ISettingsService Settings { get; } + protected INotificationTemplatesRepository TemplateRepository { get; } public abstract string NotificationName { get; } - public async Task NotifyAsync(NotificationModel model) + public async Task NotifyAsync(NotificationOptions model) { var configuration = GetConfiguration(); await NotifyAsync(model, configuration); } - public async Task NotifyAsync(NotificationModel model, Settings.Settings.Models.Settings settings) + public async Task NotifyAsync(NotificationOptions model, Settings.Settings.Models.Settings settings) { if (settings == null) await NotifyAsync(model); @@ -79,13 +83,13 @@ namespace Ombi.Notifications protected abstract bool ValidateConfiguration(T settings); - protected abstract Task NewRequest(NotificationModel model, T settings); - protected abstract Task Issue(NotificationModel model, T settings); - protected abstract Task AddedToRequestQueue(NotificationModel model, T settings); - protected abstract Task RequestDeclined(NotificationModel model, T settings); - protected abstract Task RequestApproved(NotificationModel model, T settings); - protected abstract Task AvailableRequest(NotificationModel model, T settings); + protected abstract Task NewRequest(NotificationOptions model, T settings); + protected abstract Task Issue(NotificationOptions model, T settings); + protected abstract Task AddedToRequestQueue(NotificationOptions model, T settings); + protected abstract Task RequestDeclined(NotificationOptions model, T settings); + protected abstract Task RequestApproved(NotificationOptions model, T settings); + protected abstract Task AvailableRequest(NotificationOptions model, T settings); protected abstract Task Send(NotificationMessage model, T settings); - protected abstract Task Test(NotificationModel model, T settings); + protected abstract Task Test(NotificationOptions model, T settings); } } \ No newline at end of file diff --git a/src/Ombi.Notifications/Interfaces/INotification.cs b/src/Ombi.Notifications/Interfaces/INotification.cs index 441373da6..31e395fce 100644 --- a/src/Ombi.Notifications/Interfaces/INotification.cs +++ b/src/Ombi.Notifications/Interfaces/INotification.cs @@ -8,7 +8,7 @@ namespace Ombi.Notifications { string NotificationName { get; } - Task NotifyAsync(NotificationModel model); + Task NotifyAsync(NotificationOptions model); /// /// Sends a notification to the user, this is usually for testing the settings. @@ -16,6 +16,6 @@ namespace Ombi.Notifications /// The model. /// The settings. /// - Task NotifyAsync(NotificationModel model, Settings.Settings.Models.Settings settings); + Task NotifyAsync(NotificationOptions model, Settings.Settings.Models.Settings settings); } } \ No newline at end of file diff --git a/src/Ombi.Notifications/Interfaces/INotificationService.cs b/src/Ombi.Notifications/Interfaces/INotificationService.cs index 35e239764..f8731b4af 100644 --- a/src/Ombi.Notifications/Interfaces/INotificationService.cs +++ b/src/Ombi.Notifications/Interfaces/INotificationService.cs @@ -1,14 +1,13 @@ -using System.Collections.Concurrent; -using System.Threading.Tasks; -using Ombi.Core.Settings.Models; +using System.Threading.Tasks; +using Ombi.Notifications; using Ombi.Notifications.Models; -namespace Ombi.Notifications +namespace Ombi.Core.Notifications { public interface INotificationService { - Task Publish(NotificationModel model); - Task Publish(NotificationModel model, Settings.Settings.Models.Settings settings); - Task PublishTest(NotificationModel model, Settings.Settings.Models.Settings settings, INotification type); + Task Publish(NotificationOptions model); + Task Publish(NotificationOptions model, Ombi.Settings.Settings.Models.Settings settings); + Task PublishTest(NotificationOptions model, Ombi.Settings.Settings.Models.Settings settings, INotification type); } } \ No newline at end of file diff --git a/src/Ombi.Notifications/Models/NotificationModel.cs b/src/Ombi.Notifications/Models/NotificationOptions.cs similarity index 67% rename from src/Ombi.Notifications/Models/NotificationModel.cs rename to src/Ombi.Notifications/Models/NotificationOptions.cs index 80479c018..5982d7e59 100644 --- a/src/Ombi.Notifications/Models/NotificationModel.cs +++ b/src/Ombi.Notifications/Models/NotificationOptions.cs @@ -1,15 +1,17 @@ using System; +using Ombi.Helpers; +using Ombi.Store; using Ombi.Store.Entities; namespace Ombi.Notifications.Models { - public class NotificationModel + public class NotificationOptions { public string Title { get; set; } public string Body { get; set; } - public DateTime DateTime { get; set; } + public DateTime DateTime { get; set; } = DateTime.Now; public NotificationType NotificationType { get; set; } - public string User { get; set; } + public string RequestedUser { get; set; } public string UserEmail { get; set; } public RequestType RequestType { get; set; } public string ImgSrc { get; set; } diff --git a/src/Ombi.Notifications/NotificationMessageResolver.cs b/src/Ombi.Notifications/NotificationMessageResolver.cs new file mode 100644 index 000000000..473089d48 --- /dev/null +++ b/src/Ombi.Notifications/NotificationMessageResolver.cs @@ -0,0 +1,145 @@ +using System.Collections.Generic; +using Ombi.Store.Entities; + +namespace Ombi.Notifications +{ + public class NotificationMessageContent + { + public string Subject { get; set; } + public string Message { get; set; } + } + public class NotificationMessageCurlys + { + public NotificationMessageCurlys(string requestedUser, string title, string requestedDateTime, string type, string issue) + { + RequestedUser = requestedUser; + Title = title; + RequestedDate = requestedDateTime; + Type = type; + Issue = issue; + } + private string RequestedUser { get; } + private string Title { get; } + private string RequestedDate { get; } + private string Type { get; } + private string Issue { get; } + + public Dictionary Curlys => new Dictionary + { + {nameof(RequestedUser), RequestedUser }, + {nameof(Title), Title }, + {nameof(RequestedDate), RequestedDate }, + {nameof(Type), Type }, + {nameof(Issue), Issue } + }; + } + + public class NotificationMessageResolver + { + /// + /// The start character '{' + /// + private const char StartChar = (char)123; + /// + /// The end character '}' + /// + private const char EndChar = (char)125; + + /// + /// Parses the message. + /// + /// The notification. + /// The c. + /// + public NotificationMessageContent ParseMessage(NotificationTemplates notification, NotificationMessageCurlys c) + { + return Resolve(notification.Message, notification.Subject, c.Curlys); + } + + /// + /// Resolves the specified message curly fields. + /// + /// The body. + /// The subject. + /// The parameters. + /// + private NotificationMessageContent Resolve(string body, string subject, IReadOnlyDictionary parameters) + { + // Find the fields + var bodyFields = FindCurlyFields(body); + var subjectFields = FindCurlyFields(subject); + + body = ReplaceFields(bodyFields, parameters, body); + subject = ReplaceFields(subjectFields, parameters, subject); + + return new NotificationMessageContent { Message = body ?? string.Empty, Subject = subject ?? string.Empty }; + } + + /// + /// Finds the curly fields. + /// + /// The message. + /// + private IEnumerable FindCurlyFields(string message) + { + if (string.IsNullOrEmpty(message)) + { + return new List(); + } + var insideCurly = false; + var fields = new List(); + var currentWord = string.Empty; + var chars = message.ToCharArray(); + + foreach (var c in chars) + { + if (char.IsWhiteSpace(c)) + { + currentWord = string.Empty; + continue; + } + + if (c == StartChar) // Start of curly '{' + { + insideCurly = true; + continue; + } + + if (c == EndChar) // End of curly '}' + { + fields.Add(currentWord); // We have finished the curly, add the word into the list + currentWord = string.Empty; + insideCurly = false; + continue; + } + + if (insideCurly) + { + currentWord += c.ToString(); // Add the character onto the word. + } + } + + return fields; + } + + /// + /// Replaces the fields. + /// + /// The fields. + /// The parameters. + /// The main text. + /// + private string ReplaceFields(IEnumerable fields, IReadOnlyDictionary parameters, string mainText) + { + foreach (var field in fields) + { + string outString; + if (parameters.TryGetValue(field, out outString)) + { + mainText = mainText.Replace($"{{{field}}}", outString); + } + } + return mainText; + } + } +} \ No newline at end of file diff --git a/src/Ombi.Notifications/NotificationService.cs b/src/Ombi.Notifications/NotificationService.cs index f513f263a..263651491 100644 --- a/src/Ombi.Notifications/NotificationService.cs +++ b/src/Ombi.Notifications/NotificationService.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Reflection; using System.Threading.Tasks; using Microsoft.Extensions.Logging; +using Ombi.Core.Notifications; using Ombi.Helpers; using Ombi.Notifications.Models; @@ -16,13 +17,13 @@ namespace Ombi.Notifications Log = log; NotificationAgents = new List(); - var baseSearchType = typeof(BaseNotification<>).FullName; + var baseSearchType = typeof(BaseNotification<>).Name; var ass = typeof(NotificationService).GetTypeInfo().Assembly; foreach (var ti in ass.DefinedTypes) { - if (ti?.BaseType?.FullName == baseSearchType) + if (ti?.BaseType?.Name == baseSearchType) { var type = ti?.AsType(); var ctors = type.GetConstructors(); @@ -48,7 +49,7 @@ namespace Ombi.Notifications /// /// The model. /// - public async Task Publish(NotificationModel model) + public async Task Publish(NotificationOptions model) { var notificationTasks = NotificationAgents.Select(notification => NotifyAsync(notification, model)); @@ -61,7 +62,7 @@ namespace Ombi.Notifications /// The model. /// The settings. /// - public async Task Publish(NotificationModel model, Settings.Settings.Models.Settings settings) + public async Task Publish(NotificationOptions model, Ombi.Settings.Settings.Models.Settings settings) { var notificationTasks = NotificationAgents.Select(notification => NotifyAsync(notification, model, settings)); @@ -69,7 +70,7 @@ namespace Ombi.Notifications } - private async Task NotifyAsync(INotification notification, NotificationModel model) + private async Task NotifyAsync(INotification notification, NotificationOptions model) { try { @@ -82,7 +83,7 @@ namespace Ombi.Notifications } - private async Task NotifyAsync(INotification notification, NotificationModel model, Settings.Settings.Models.Settings settings) + private async Task NotifyAsync(INotification notification, NotificationOptions model, Ombi.Settings.Settings.Models.Settings settings) { try { @@ -94,7 +95,7 @@ namespace Ombi.Notifications } } - public async Task PublishTest(NotificationModel model, Settings.Settings.Models.Settings settings, INotification type) + public async Task PublishTest(NotificationOptions model, Ombi.Settings.Settings.Models.Settings settings, INotification type) { await type.NotifyAsync(model, settings); } diff --git a/src/Ombi.Notifications/Ombi.Notifications.csproj b/src/Ombi.Notifications/Ombi.Notifications.csproj index ea899160a..675b772b6 100644 --- a/src/Ombi.Notifications/Ombi.Notifications.csproj +++ b/src/Ombi.Notifications/Ombi.Notifications.csproj @@ -9,6 +9,7 @@ + diff --git a/src/Ombi.Settings/Settings/Models/Notifications/EmailNotificationSettings.cs b/src/Ombi.Settings/Settings/Models/Notifications/EmailNotificationSettings.cs index bdcc1dbd9..581062662 100644 --- a/src/Ombi.Settings/Settings/Models/Notifications/EmailNotificationSettings.cs +++ b/src/Ombi.Settings/Settings/Models/Notifications/EmailNotificationSettings.cs @@ -1,14 +1,14 @@ namespace Ombi.Settings.Settings.Models.Notifications { - public sealed class EmailNotificationSettings : Settings + public class EmailNotificationSettings : Settings { public bool Enabled { get; set; } - public string EmailHost { get; set; } - public string EmailPassword { get; set; } - public int EmailPort { get; set; } - public string EmailSender { get; set; } - public string EmailUsername { get; set; } + public string Host { get; set; } + public string Password { get; set; } + public int Port { get; set; } + public string Sender { get; set; } + public string Username { get; set; } public bool Authentication { get; set; } - public string RecipientEmail { get; set; } + public string AdminEmail { get; set; } } } \ No newline at end of file diff --git a/src/Ombi.Store/Context/IOmbiContext.cs b/src/Ombi.Store/Context/IOmbiContext.cs index d9cad51e5..0dfda13ce 100644 --- a/src/Ombi.Store/Context/IOmbiContext.cs +++ b/src/Ombi.Store/Context/IOmbiContext.cs @@ -21,5 +21,6 @@ namespace Ombi.Store.Context EntityEntry Entry(T entry) where T : class; EntityEntry Attach(TEntity entity) where TEntity : class; DbSet Set() where TEntity : class; + DbSet NotificationTemplates { get; set; } } } \ No newline at end of file diff --git a/src/Ombi.Store/Context/OmbiContext.cs b/src/Ombi.Store/Context/OmbiContext.cs index fbc5e49de..3cf2db736 100644 --- a/src/Ombi.Store/Context/OmbiContext.cs +++ b/src/Ombi.Store/Context/OmbiContext.cs @@ -1,10 +1,14 @@ -using System.IO; +using System; +using System.IO; +using System.Linq; +using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; +using Ombi.Helpers; using Ombi.Store.Entities; namespace Ombi.Store.Context { - public class OmbiContext : DbContext, IOmbiContext + public sealed class OmbiContext : DbContext, IOmbiContext { private static bool _created; public OmbiContext() @@ -26,6 +30,9 @@ namespace Ombi.Store.Context // Run Script Database.ExecuteSqlCommand(file, 0); + + // Add the notifcation templates + AddAllTemplates(); } public DbSet Requests { get; set; } @@ -33,10 +40,93 @@ namespace Ombi.Store.Context public DbSet Users { get; set; } public DbSet PlexContent { get; set; } public DbSet RadarrCache { get; set; } + public DbSet NotificationTemplates { get; set; } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { optionsBuilder.UseSqlite("Data Source=Ombi.db"); } + + + private void AddAllTemplates() + { + // Check if templates exist + var templates = NotificationTemplates.ToList(); + if (templates.Any()) + { + return; + } + + var allAgents = Enum.GetValues(typeof(NotificationAgent)).Cast().ToList(); + var allTypes = Enum.GetValues(typeof(NotificationType)).Cast().ToList(); + + foreach (var agent in allAgents) + { + foreach (var notificationType in allTypes) + { + var notificationToAdd = new NotificationTemplates(); + switch (notificationType) + { + case NotificationType.NewRequest: + notificationToAdd = new NotificationTemplates + { + NotificationType = notificationType, + Message = "Hello! The user '{requestedUser}' has requested the {Type} '{Title}'! Please log in to approve this request. Request Date: {RequestedDate}", + Subject = "Ombi: New {Type} request for {Title}!", + Agent = agent, + }; + break; + case NotificationType.Issue: + notificationToAdd = new NotificationTemplates + { + NotificationType = notificationType, + Message = "Hello! The user '{requestedUser}' has reported a new issue for the title {Title}!
{Issue}", + Subject = "Ombi: New issue for {Title}!", + Agent = agent, + }; + break; + case NotificationType.RequestAvailable: + notificationToAdd = new NotificationTemplates + { + NotificationType = notificationType, + Message = "Hello! You requested {Title} on Ombi! This is now available! :)", + Subject = "Ombi: {Title} is now available!", + Agent = agent, + }; + break; + case NotificationType.RequestApproved: + notificationToAdd = new NotificationTemplates + { + NotificationType = notificationType, + Message = "Hello! Your request for {Title} has been approved!", + Subject = "Ombi: your request has been approved", + Agent = agent, + }; + break; + case NotificationType.AdminNote: + continue; + case NotificationType.Test: + continue; + case NotificationType.RequestDeclined: + notificationToAdd = new NotificationTemplates + { + //"Ombi: Your request has been declined", + //$"Hello! Your request for {model.Title} has been declined, Sorry!", + NotificationType = notificationType, + Message = "Hello! Your request for {Title} has been declined, Sorry!", + Subject = "Ombi: your request has been declined", + Agent = agent, + }; + break; + case NotificationType.ItemAddedToFaultQueue: + continue; + default: + throw new ArgumentOutOfRangeException(); + } + NotificationTemplates.Add(notificationToAdd); + } + } + SaveChanges(); + } } } \ No newline at end of file diff --git a/src/Ombi.Store/Entities/NotificationTemplates.cs b/src/Ombi.Store/Entities/NotificationTemplates.cs new file mode 100644 index 000000000..c5abbd8f1 --- /dev/null +++ b/src/Ombi.Store/Entities/NotificationTemplates.cs @@ -0,0 +1,14 @@ +using System.ComponentModel.DataAnnotations.Schema; +using Ombi.Helpers; + +namespace Ombi.Store.Entities +{ + [Table("NotificationTemplates")] + public class NotificationTemplates : Entity + { + public NotificationType NotificationType { get; set; } + public NotificationAgent Agent { get; set; } + public string Subject { get; set; } + public string Message { get; set; } + } +} \ No newline at end of file diff --git a/src/Ombi.Store/Repository/INotificationTemplatesRepository.cs b/src/Ombi.Store/Repository/INotificationTemplatesRepository.cs new file mode 100644 index 000000000..861c6b9bd --- /dev/null +++ b/src/Ombi.Store/Repository/INotificationTemplatesRepository.cs @@ -0,0 +1,19 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Ombi.Helpers; +using Ombi.Store.Entities; + +namespace Ombi.Store.Repository +{ + public interface INotificationTemplatesRepository + { + IQueryable All(); + Task> GetAllTemplates(); + Task> GetAllTemplates(NotificationAgent agent); + Task Insert(NotificationTemplates entity); + Task Update(NotificationTemplates template); + Task UpdateRange(IEnumerable template); + Task GetTemplate(NotificationAgent agent, NotificationType type); + } +} \ No newline at end of file diff --git a/src/Ombi.Store/Repository/NotificationTemplatesRepository.cs b/src/Ombi.Store/Repository/NotificationTemplatesRepository.cs new file mode 100644 index 000000000..e4b484967 --- /dev/null +++ b/src/Ombi.Store/Repository/NotificationTemplatesRepository.cs @@ -0,0 +1,64 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Ombi.Helpers; +using Ombi.Store.Context; +using Ombi.Store.Entities; + +namespace Ombi.Store.Repository +{ + public class NotificationTemplatesRepository : INotificationTemplatesRepository + { + public NotificationTemplatesRepository(IOmbiContext ctx) + { + Db = ctx; + } + + private IOmbiContext Db { get; } + + public IQueryable All() + { + return Db.NotificationTemplates.AsQueryable(); + } + + public async Task> GetAllTemplates() + { + return await Db.NotificationTemplates.ToListAsync(); + } + + public async Task> GetAllTemplates(NotificationAgent agent) + { + return await Db.NotificationTemplates.Where(x => x.Agent == agent).ToListAsync(); + } + + public async Task GetTemplate(NotificationAgent agent, NotificationType type) + { + return await Db.NotificationTemplates.FirstOrDefaultAsync(x => x.Agent == agent && x.NotificationType == type); + } + + public async Task Update(NotificationTemplates template) + { + await Db.SaveChangesAsync(); + } + + public async Task UpdateRange(IEnumerable templates) + { + foreach (var t in templates) + { + + Db.Attach(t); + Db.Entry(t).State = EntityState.Modified; + } + await Db.SaveChangesAsync(); + } + + public async Task Insert(NotificationTemplates entity) + { + var settings = await Db.NotificationTemplates.AddAsync(entity).ConfigureAwait(false); + await Db.SaveChangesAsync().ConfigureAwait(false); + return settings.Entity; + } + } +} \ No newline at end of file diff --git a/src/Ombi.Store/SqlTables.sql b/src/Ombi.Store/SqlTables.sql index 87b594bdd..56a46e5ec 100644 --- a/src/Ombi.Store/SqlTables.sql +++ b/src/Ombi.Store/SqlTables.sql @@ -63,4 +63,15 @@ CREATE TABLE IF NOT EXISTS RequestHistory RequestedDate varchar(50) NOT NULL, RequestId INTEGER NOT NULL +); + +CREATE TABLE IF NOT EXISTS NotificationTemplates +( + + Id INTEGER PRIMARY KEY AUTOINCREMENT, + NotificationType INTEGER NOT NULL, + Agent INTEGER NOT NULL, + Subject BLOB NULL, + Message BLOB NULL + ); \ No newline at end of file diff --git a/src/Ombi.sln b/src/Ombi.sln index 9d31c29d4..146b3ea8d 100644 --- a/src/Ombi.sln +++ b/src/Ombi.sln @@ -61,8 +61,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ombi.Api.Radarr", "Ombi.Api EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ombi.Api.Discord", "Ombi.Api.Discord\Ombi.Api.Discord.csproj", "{5AF2B6D2-5CC6-49FE-928A-BA27AF52B194}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ombi.Notification.Agents", "Ombi.Notification.Agents\Ombi.Notification.Agents.csproj", "{DC9E3E44-802A-4D9B-A70F-D198CFE4C73D}" -EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -149,10 +147,6 @@ Global {5AF2B6D2-5CC6-49FE-928A-BA27AF52B194}.Debug|Any CPU.Build.0 = Debug|Any CPU {5AF2B6D2-5CC6-49FE-928A-BA27AF52B194}.Release|Any CPU.ActiveCfg = Release|Any CPU {5AF2B6D2-5CC6-49FE-928A-BA27AF52B194}.Release|Any CPU.Build.0 = Release|Any CPU - {DC9E3E44-802A-4D9B-A70F-D198CFE4C73D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {DC9E3E44-802A-4D9B-A70F-D198CFE4C73D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {DC9E3E44-802A-4D9B-A70F-D198CFE4C73D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {DC9E3E44-802A-4D9B-A70F-D198CFE4C73D}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -172,6 +166,5 @@ Global {FC6A8F7C-9722-4AE4-960D-277ACB0E81CB} = {6F42AB98-9196-44C4-B888-D5E409F415A1} {94D04C1F-E35A-499C-B0A0-9FADEBDF8336} = {9293CA11-360A-4C20-A674-B9E794431BF5} {5AF2B6D2-5CC6-49FE-928A-BA27AF52B194} = {9293CA11-360A-4C20-A674-B9E794431BF5} - {DC9E3E44-802A-4D9B-A70F-D198CFE4C73D} = {EA30DD15-6280-4687-B370-2956EC2E54E5} EndGlobalSection EndGlobal diff --git a/src/Ombi/ClientApp/app/interfaces/INotifcationSettings.ts b/src/Ombi/ClientApp/app/interfaces/INotifcationSettings.ts new file mode 100644 index 000000000..2d3336fe4 --- /dev/null +++ b/src/Ombi/ClientApp/app/interfaces/INotifcationSettings.ts @@ -0,0 +1,44 @@ +export interface ISettings { + id: number, +} + +export interface INotificationSettings extends ISettings { + enabled: boolean, +} + +export interface IEmailNotificationSettings extends INotificationSettings { + host: string, + password: string, + port: number, + sender: string, + username: string, + authentication: boolean, + adminEmail: string, + notificationTemplates: INotificationTemplates[], +} + +export interface INotificationTemplates { + subject: string, + message: string, + notificationType: NotificationType, + notificationAgent: NotificationAgent, +} + +export enum NotificationAgent { + Email, + Discord, + Pushbullet, + Pushover, + Telegram, +} + +export enum NotificationType { + NewRequest, + Issue, + RequestAvailable, + RequestApproved, + AdminNote, + Test, + RequestDeclined, + ItemAddedToFaultQueue +} diff --git a/src/Ombi/ClientApp/app/pipes/HumanizePipe.ts b/src/Ombi/ClientApp/app/pipes/HumanizePipe.ts new file mode 100644 index 000000000..5c04028d1 --- /dev/null +++ b/src/Ombi/ClientApp/app/pipes/HumanizePipe.ts @@ -0,0 +1,15 @@ +import { Pipe, PipeTransform } from '@angular/core'; + +@Pipe({ + name: 'humanize' +}) +export class HumanizePipe implements PipeTransform { + transform(value: string) { + if ((typeof value) !== 'string') { + return value; + } + value = value.split(/(?=[A-Z])/).join(' '); + value = value[0].toUpperCase() + value.slice(1); + return value; + } +} \ No newline at end of file diff --git a/src/Ombi/ClientApp/app/requests/tvrequests.component.html b/src/Ombi/ClientApp/app/requests/tvrequests.component.html index ac248faef..1827032a8 100644 --- a/src/Ombi/ClientApp/app/requests/tvrequests.component.html +++ b/src/Ombi/ClientApp/app/requests/tvrequests.component.html @@ -222,6 +222,7 @@
+

diff --git a/src/Ombi/ClientApp/app/requests/tvrequests.component.ts b/src/Ombi/ClientApp/app/requests/tvrequests.component.ts index a5120a5a8..bb9924e39 100644 --- a/src/Ombi/ClientApp/app/requests/tvrequests.component.ts +++ b/src/Ombi/ClientApp/app/requests/tvrequests.component.ts @@ -5,7 +5,6 @@ import 'rxjs/add/operator/distinctUntilChanged'; import 'rxjs/add/operator/map'; import "rxjs/add/operator/takeUntil"; - import 'rxjs/add/operator/debounceTime'; import 'rxjs/add/operator/distinctUntilChanged'; import 'rxjs/add/operator/map'; @@ -62,7 +61,7 @@ export class TvRequestsComponent implements OnInit, OnDestroy { - loadMore() { + public loadMore() { this.requestService.getTvRequests(this.amountToLoad, this.currentlyLoaded + 1) .takeUntil(this.subscriptions) .subscribe(x => { @@ -71,28 +70,28 @@ export class TvRequestsComponent implements OnInit, OnDestroy { }); } - search(text: any) { + public search(text: any) { this.searchChanged.next(text.target.value); } - removeRequest(request: ITvRequestModel) { + public removeRequest(request: ITvRequestModel) { this.requestService.removeTvRequest(request); this.removeRequestFromUi(request); } - changeAvailability(request: ITvRequestModel, available: boolean) { + public changeAvailability(request: ITvRequestModel, available: boolean) { request.available = available; this.updateRequest(request); } - approve(request: ITvRequestModel) { + public approve(request: ITvRequestModel) { request.approved = true; request.denied = false; this.updateRequest(request); } - deny(request: ITvRequestModel) { + public deny(request: ITvRequestModel) { request.approved = false; request.denied = true; this.updateRequest(request); @@ -100,6 +99,14 @@ export class TvRequestsComponent implements OnInit, OnDestroy { public approveSeasonRequest(request: IChildTvRequest) { request.approved = true; + request.denied = false; + this.requestService.updateTvRequest(this.selectedSeason) + .subscribe(); + } + + public denySeasonRequest(request: IChildTvRequest) { + request.approved = false; + request.denied = true; this.requestService.updateTvRequest(this.selectedSeason) .subscribe(); } diff --git a/src/Ombi/ClientApp/app/services/settings.service.ts b/src/Ombi/ClientApp/app/services/settings.service.ts index 5e5fffa93..3d389b052 100644 --- a/src/Ombi/ClientApp/app/services/settings.service.ts +++ b/src/Ombi/ClientApp/app/services/settings.service.ts @@ -13,6 +13,7 @@ import { ICustomizationSettings, IRadarrSettings } from '../interfaces/ISettings'; +import { IEmailNotificationSettings } from '../interfaces/INotifcationSettings'; @Injectable() export class SettingsService extends ServiceAuthHelpers { @@ -80,6 +81,11 @@ export class SettingsService extends ServiceAuthHelpers { return this.httpAuth.post(`${this.url}/customization`, JSON.stringify(settings), { headers: this.headers }).map(this.extractData).catch(this.handleError) } + getEmailNotificationSettings(): Observable { + return this.httpAuth.get(`${this.url}/notifications/email`).map(this.extractData).catch(this.handleError) + } - + saveEmailNotificationSettings(settings: IEmailNotificationSettings): Observable { + return this.httpAuth.post(`${this.url}/notifications/email`, JSON.stringify(settings), { headers: this.headers }).map(this.extractData).catch(this.handleError) + } } \ No newline at end of file diff --git a/src/Ombi/ClientApp/app/settings/customization/customization.component.html b/src/Ombi/ClientApp/app/settings/customization/customization.component.html index 41c837290..2ef7107b0 100644 --- a/src/Ombi/ClientApp/app/settings/customization/customization.component.html +++ b/src/Ombi/ClientApp/app/settings/customization/customization.component.html @@ -12,10 +12,9 @@
- +
- This will be used on all of the notifications e.g. Newsletter, email notification and also the Landing page
diff --git a/src/Ombi/ClientApp/app/settings/landingpage/landingpage.component.html b/src/Ombi/ClientApp/app/settings/landingpage/landingpage.component.html index d01ec3af1..8a4842363 100644 --- a/src/Ombi/ClientApp/app/settings/landingpage/landingpage.component.html +++ b/src/Ombi/ClientApp/app/settings/landingpage/landingpage.component.html @@ -16,10 +16,9 @@
- +
- If enabled then this will show the landing page before the login page, if this is disabled the user will log in first and then see the landing page.

Notice Message

diff --git a/src/Ombi/ClientApp/app/settings/notifications/emailnotification.component.html b/src/Ombi/ClientApp/app/settings/notifications/emailnotification.component.html new file mode 100644 index 000000000..b7c3f884a --- /dev/null +++ b/src/Ombi/ClientApp/app/settings/notifications/emailnotification.component.html @@ -0,0 +1,82 @@ + + +
+
+ Email Notifications +
+
+
+ + +
+
+ +
+
+ +
+
+ +
+ +
+ +
+
+ +
+ +
+ +
+
+ +
+ +
+ +
+
+ +
+ +
+ +
+
+ + +
+ +
+ +
+
+ +
+ +
+ +
+
+ +
+
+ +
+
+ + + +
+
+ +
+
+
+ +
+ +
+
+
\ No newline at end of file diff --git a/src/Ombi/ClientApp/app/settings/notifications/emailnotification.component.ts b/src/Ombi/ClientApp/app/settings/notifications/emailnotification.component.ts new file mode 100644 index 000000000..49aa03bf6 --- /dev/null +++ b/src/Ombi/ClientApp/app/settings/notifications/emailnotification.component.ts @@ -0,0 +1,34 @@ +import { Component, OnInit } from '@angular/core'; + +import { IEmailNotificationSettings, NotificationType } from '../../interfaces/INotifcationSettings'; +import { SettingsService } from '../../services/settings.service'; +import { NotificationService } from "../../services/notification.service"; + +@Component({ + templateUrl: './emailnotification.component.html', +}) +export class EmailNotificationComponent implements OnInit { + + constructor(private settingsService: SettingsService, private notificationService: NotificationService) { } + + settings: IEmailNotificationSettings; + NotificationType = NotificationType; + + ngOnInit(): void { + this.settingsService.getEmailNotificationSettings().subscribe(x => this.settings = x); + } + + test() { + // TODO Emby Service + } + + save() { + this.settingsService.saveEmailNotificationSettings(this.settings).subscribe(x => { + if (x) { + this.notificationService.success("Settings Saved", "Successfully saved Email settings"); + } else { + this.notificationService.success("Settings Saved", "There was an error when saving the Email settings"); + } + }); + } +} \ No newline at end of file diff --git a/src/Ombi/ClientApp/app/settings/notifications/notificationtemplate.component.html b/src/Ombi/ClientApp/app/settings/notifications/notificationtemplate.component.html new file mode 100644 index 000000000..9a9ec4ca1 --- /dev/null +++ b/src/Ombi/ClientApp/app/settings/notifications/notificationtemplate.component.html @@ -0,0 +1,25 @@ + + + + +
+
+
+ +
+ +
+
+ +
+ +
+ +
+
+
+
+ +
+
+
diff --git a/src/Ombi/ClientApp/app/settings/notifications/notificationtemplate.component.ts b/src/Ombi/ClientApp/app/settings/notifications/notificationtemplate.component.ts new file mode 100644 index 000000000..943f9bca1 --- /dev/null +++ b/src/Ombi/ClientApp/app/settings/notifications/notificationtemplate.component.ts @@ -0,0 +1,12 @@ +import { Component, Input } from '@angular/core'; + +import { INotificationTemplates, NotificationType } from '../../interfaces/INotifcationSettings'; + +@Component({ + selector:'notification-templates', + templateUrl: './notificationtemplate.component.html', +}) +export class NotificationTemplate { + @Input() templates: INotificationTemplates[]; + NotificationType = NotificationType; +} \ No newline at end of file diff --git a/src/Ombi/ClientApp/app/settings/ombi/ombi.component.html b/src/Ombi/ClientApp/app/settings/ombi/ombi.component.html index 1d589c3a7..efbdde1aa 100644 --- a/src/Ombi/ClientApp/app/settings/ombi/ombi.component.html +++ b/src/Ombi/ClientApp/app/settings/ombi/ombi.component.html @@ -6,10 +6,9 @@
- +
- You will have to restart after changing the port.