More on #865 TODO, Find out whats going on with the notifications and why exceptions are being thrown.

Bascailly custom notification messages are almost done
pull/1488/head
Jamie.Rees 7 years ago
parent 5e6032ecba
commit f193471b6c

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

@ -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
};
BackgroundJob.Enqueue(() => NotificationService.Publish(notificationModel).Wait());
var n = new NotificationOptions();
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();

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

@ -0,0 +1,21 @@
using System.Collections.Generic;
using Ombi.Settings.Settings.Models.Notifications;
using Ombi.Store.Entities;
namespace Ombi.Models.Notifications
{
/// <summary>
/// The view model for the notification settings page
/// </summary>
/// <seealso cref="Ombi.Settings.Settings.Models.Notifications.EmailNotificationSettings" />
public class EmailNotificationsViewModel : EmailNotificationSettings
{
/// <summary>
/// Gets or sets the notification templates.
/// </summary>
/// <value>
/// The notification templates.
/// </value>
public List<NotificationTemplates> NotificationTemplates { get; set; }
}
}

@ -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<IUserRepository, UserRepository>();
services.AddTransient<ISettingsResolver, SettingsResolver>();
services.AddTransient<IPlexContentRepository, PlexContentRepository>();
services.AddTransient<INotificationTemplatesRepository, NotificationTemplatesRepository>();
services.AddTransient(typeof(ISettingsService<>), typeof(SettingsServiceV2<>));
}
public static void RegisterServices(this IServiceCollection services)

@ -0,0 +1,12 @@
namespace Ombi.Helpers
{
public enum NotificationAgent
{
Email,
Discord,
Pushbullet,
Pushover,
Telegram,
Slack
}
}

@ -1,6 +1,4 @@
using System;
namespace Ombi.Notifications
namespace Ombi.Helpers
{
public enum NotificationType
{

@ -1,19 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard1.6</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="AutoMapper" Version="6.0.2" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Ombi.Api\Ombi.Api.csproj" />
<ProjectReference Include="..\Ombi.Core\Ombi.Core.csproj" />
<ProjectReference Include="..\Ombi.Helpers\Ombi.Helpers.csproj" />
<ProjectReference Include="..\Ombi.Store\Ombi.Store.csproj" />
<ProjectReference Include="..\Ombi\Ombi.csproj" />
</ItemGroup>
</Project>

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

@ -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<EmailNotificationsViewModel, EmailNotificationSettings>().ReverseMap();
}
}
}

@ -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<EmailNotificationSettings>
{
public EmailNotification(ISettingsService<EmailNotificationSettings> 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);
}
}
}

@ -1,15 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard1.6</TargetFramework>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Ombi.Api.Discord\Ombi.Api.Discord.csproj" />
<ProjectReference Include="..\Ombi.Core\Ombi.Core.csproj" />
<ProjectReference Include="..\Ombi.Helpers\Ombi.Helpers.csproj" />
<ProjectReference Include="..\Ombi.Notifications\Ombi.Notifications.csproj" />
<ProjectReference Include="..\Ombi.Settings\Ombi.Settings.csproj" />
</ItemGroup>
</Project>

@ -1,18 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard1.6</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="MailKit" Version="1.16.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Ombi.Core\Ombi.Core.csproj" />
<ProjectReference Include="..\Ombi.Notifications.Templates\Ombi.Notifications.Templates.csproj" />
<ProjectReference Include="..\Ombi.Notifications\Ombi.Notifications.csproj" />
<ProjectReference Include="..\Ombi.Settings\Ombi.Settings.csproj" />
</ItemGroup>
</Project>

@ -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<DiscordNotificationSettings>
{
public DiscordNotification(IDiscordApi api, ISettingsService<DiscordNotificationSettings> sn, ILogger<DiscordNotification> log) : base(sn)
public DiscordNotification(IDiscordApi api, ISettingsService<DiscordNotificationSettings> sn, ILogger<DiscordNotification> 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

@ -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<EmailNotificationSettings>
{
public EmailNotification(ISettingsService<EmailNotificationSettings> settings) : base(settings)
public EmailNotification(ISettingsService<EmailNotificationSettings> 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<NotificationMessageContent> 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!");

@ -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<T> : INotification where T : Settings.Settings.Models.Settings, new()
{
protected BaseNotification(ISettingsService<T> settings)
protected BaseNotification(ISettingsService<T> settings, INotificationTemplatesRepository templateRepo)
{
Settings = settings;
TemplateRepository = templateRepo;
}
protected ISettingsService<T> 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);
}
}

@ -8,7 +8,7 @@ namespace Ombi.Notifications
{
string NotificationName { get; }
Task NotifyAsync(NotificationModel model);
Task NotifyAsync(NotificationOptions model);
/// <summary>
/// Sends a notification to the user, this is usually for testing the settings.
@ -16,6 +16,6 @@ namespace Ombi.Notifications
/// <param name="model">The model.</param>
/// <param name="settings">The settings.</param>
/// <returns></returns>
Task NotifyAsync(NotificationModel model, Settings.Settings.Models.Settings settings);
Task NotifyAsync(NotificationOptions model, Settings.Settings.Models.Settings settings);
}
}

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

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

@ -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<string, string> Curlys => new Dictionary<string, string>
{
{nameof(RequestedUser), RequestedUser },
{nameof(Title), Title },
{nameof(RequestedDate), RequestedDate },
{nameof(Type), Type },
{nameof(Issue), Issue }
};
}
public class NotificationMessageResolver
{
/// <summary>
/// The start character '{'
/// </summary>
private const char StartChar = (char)123;
/// <summary>
/// The end character '}'
/// </summary>
private const char EndChar = (char)125;
/// <summary>
/// Parses the message.
/// </summary>
/// <param name="notification">The notification.</param>
/// <param name="c">The c.</param>
/// <returns></returns>
public NotificationMessageContent ParseMessage(NotificationTemplates notification, NotificationMessageCurlys c)
{
return Resolve(notification.Message, notification.Subject, c.Curlys);
}
/// <summary>
/// Resolves the specified message curly fields.
/// </summary>
/// <param name="body">The body.</param>
/// <param name="subject">The subject.</param>
/// <param name="parameters">The parameters.</param>
/// <returns></returns>
private NotificationMessageContent Resolve(string body, string subject, IReadOnlyDictionary<string, string> 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 };
}
/// <summary>
/// Finds the curly fields.
/// </summary>
/// <param name="message">The message.</param>
/// <returns></returns>
private IEnumerable<string> FindCurlyFields(string message)
{
if (string.IsNullOrEmpty(message))
{
return new List<string>();
}
var insideCurly = false;
var fields = new List<string>();
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;
}
/// <summary>
/// Replaces the fields.
/// </summary>
/// <param name="fields">The fields.</param>
/// <param name="parameters">The parameters.</param>
/// <param name="mainText">The main text.</param>
/// <returns></returns>
private string ReplaceFields(IEnumerable<string> fields, IReadOnlyDictionary<string, string> parameters, string mainText)
{
foreach (var field in fields)
{
string outString;
if (parameters.TryGetValue(field, out outString))
{
mainText = mainText.Replace($"{{{field}}}", outString);
}
}
return mainText;
}
}
}

@ -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<INotification>();
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
/// </summary>
/// <param name="model">The model.</param>
/// <returns></returns>
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
/// <param name="model">The model.</param>
/// <param name="settings">The settings.</param>
/// <returns></returns>
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);
}

@ -9,6 +9,7 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Ombi.Api.Discord\Ombi.Api.Discord.csproj" />
<ProjectReference Include="..\Ombi.Notifications.Templates\Ombi.Notifications.Templates.csproj" />
<ProjectReference Include="..\Ombi.Settings\Ombi.Settings.csproj" />
<ProjectReference Include="..\Ombi.Store\Ombi.Store.csproj" />

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

@ -21,5 +21,6 @@ namespace Ombi.Store.Context
EntityEntry<T> Entry<T>(T entry) where T : class;
EntityEntry<TEntity> Attach<TEntity>(TEntity entity) where TEntity : class;
DbSet<TEntity> Set<TEntity>() where TEntity : class;
DbSet<NotificationTemplates> NotificationTemplates { get; set; }
}
}

@ -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<RequestBlobs> Requests { get; set; }
@ -33,10 +40,93 @@ namespace Ombi.Store.Context
public DbSet<User> Users { get; set; }
public DbSet<PlexContent> PlexContent { get; set; }
public DbSet<RadarrCache> RadarrCache { get; set; }
public DbSet<NotificationTemplates> 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<NotificationAgent>().ToList();
var allTypes = Enum.GetValues(typeof(NotificationType)).Cast<NotificationType>().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}! </br> {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();
}
}
}

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

@ -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<NotificationTemplates> All();
Task<IEnumerable<NotificationTemplates>> GetAllTemplates();
Task<IEnumerable<NotificationTemplates>> GetAllTemplates(NotificationAgent agent);
Task<NotificationTemplates> Insert(NotificationTemplates entity);
Task Update(NotificationTemplates template);
Task UpdateRange(IEnumerable<NotificationTemplates> template);
Task<NotificationTemplates> GetTemplate(NotificationAgent agent, NotificationType type);
}
}

@ -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<NotificationTemplates> All()
{
return Db.NotificationTemplates.AsQueryable();
}
public async Task<IEnumerable<NotificationTemplates>> GetAllTemplates()
{
return await Db.NotificationTemplates.ToListAsync();
}
public async Task<IEnumerable<NotificationTemplates>> GetAllTemplates(NotificationAgent agent)
{
return await Db.NotificationTemplates.Where(x => x.Agent == agent).ToListAsync();
}
public async Task<NotificationTemplates> 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<NotificationTemplates> templates)
{
foreach (var t in templates)
{
Db.Attach(t);
Db.Entry(t).State = EntityState.Modified;
}
await Db.SaveChangesAsync();
}
public async Task<NotificationTemplates> Insert(NotificationTemplates entity)
{
var settings = await Db.NotificationTemplates.AddAsync(entity).ConfigureAwait(false);
await Db.SaveChangesAsync().ConfigureAwait(false);
return settings.Entity;
}
}
}

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

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

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

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

@ -222,6 +222,7 @@
</div>
<div class="col-md-3">
<button *ngIf="!child.approved" type="button" (click)="approveSeasonRequest(child)" class="btn btn-sm btn-success-outline" style="text-align: right"><i class="fa fa-plus"></i> Approve</button>
<button *ngIf="!child.approved && !child.available && !child.denied" type="button" (click)="denySeasonRequest(child)" class="btn btn-sm btn-danger-outline" style="text-align: right"><i class="fa fa-plus"></i> Deny</button>
</div>
</div>
<hr />

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

@ -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<IEmailNotificationSettings> {
return this.httpAuth.get(`${this.url}/notifications/email`).map(this.extractData).catch(this.handleError)
}
saveEmailNotificationSettings(settings: IEmailNotificationSettings): Observable<boolean> {
return this.httpAuth.post(`${this.url}/notifications/email`, JSON.stringify(settings), { headers: this.headers }).map(this.extractData).catch(this.handleError)
}
}

@ -12,10 +12,9 @@
<div class="form-group">
<label for="logo" class="control-label">Custom Logo</label>
<div>
<input type="text" [(ngModel)]="settings.logo" class="form-control form-control-custom " id="logo" name="logo" value="{{settings.logo}}">
<input type="text" [(ngModel)]="settings.logo" class="form-control form-control-custom " id="logo" name="logo" value="{{settings.logo}}" tooltipPosition="top" pTooltip="This will be used on all of the notifications e.g. Newsletter, email notification and also the Landing page">
</div>
</div>
<small>This will be used on all of the notifications e.g. Newsletter, email notification and also the Landing page</small>
<div class="form-group">

@ -16,10 +16,9 @@
<div class="form-group">
<div class="checkbox">
<input type="checkbox" id="BeforeLogin" name="BeforeLogin" [(ngModel)]="settings.beforeLogin" ng-checked="settings.beforeLogin">
<input type="checkbox" id="BeforeLogin" name="BeforeLogin" [(ngModel)]="settings.beforeLogin" ng-checked="settings.beforeLogin" tooltipPosition="top" pTooltip="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.">
<label for="BeforeLogin">Show before the login</label>
</div>
<small>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.</small>
</div>
<p class="form-group">Notice Message</p>

@ -0,0 +1,82 @@

<settings-menu></settings-menu>
<div *ngIf="settings">
<fieldset>
<legend>Email Notifications</legend>
<div class="col-md-6">
<div class="form-group">
<div class="checkbox">
<input type="checkbox" id="enable" [(ngModel)]="settings.enabled" ng-checked="settings.enabled">
<label for="enable">Enabled</label>
</div>
</div>
<div class="form-group">
<div class="checkbox">
<input type="checkbox" id="Authentication" [(ngModel)]="settings.authentication" ng-checked="settings.authentication"><label for="Authentication">Enable SMTP Authentication</label>
</div>
</div>
<div class="form-group">
<label for="host" class="control-label">SMTP Host</label>
<div>
<input type="text" class="form-control form-control-custom " id="host" name="host" placeholder="localhost" [(ngModel)]="settings.host" value="{{settings.host}}">
</div>
</div>
<div class="form-group">
<label for="portNumber" class="control-label">SMTP Port</label>
<div>
<input type="text" [(ngModel)]="settings.port" class="form-control form-control-custom " id="portNumber" name="Port" placeholder="Port Number" value="{{settings.port}}">
</div>
</div>
<div class="form-group">
<label for="sender" class="control-label">Email Sender</label>
<div>
<input type="text" class="form-control form-control-custom " id="sender" name="sender" [(ngModel)]="settings.sender" value="{{settings.sender}}" tooltipPosition="top" pTooltip="The email address that the emails will be sent from">
</div>
</div>
<div class="form-group">
<label for="adminEmail" class="control-label">Admin Email</label>
<div>
<input type="text" class="form-control form-control-custom " id="adminEmail" name="adminEmail" [(ngModel)]="settings.adminEmail" value="{{settings.adminEmail}}" tooltipPosition="top" pTooltip="The administrator email will be used to send emails for admin only notifications (e.g. New Requests that require approvals)">
</div>
</div>
<div class="form-group" *ngIf="settings.authentication">
<label for="username" class="control-label">Username</label>
<div>
<input type="text" class="form-control form-control-custom " id="username" name="username" [(ngModel)]="settings.username" value="{{settings.username}}" pTooltip="The username if authentication is enabled" tooltipPosition="top">
</div>
</div>
<div class="form-group" *ngIf="settings.authentication">
<label for="password" class="control-label">Password</label>
<div>
<input type="password" class="form-control form-control-custom " id="password" name="password" [(ngModel)]="settings.password" value="{{settings.password}}" pTooltip="The password if authentication is enabled" tooltipPosition="top">
</div>
</div>
<div class="form-group">
<div>
<button id="testPlex" type="submit" (click)="test()" class="btn btn-primary-outline">Test Connectivity <div id="spinner"></div></button>
</div>
</div>
<div class="form-group">
<div>
<button (click)="save()" type="submit" id="save" class="btn btn-primary-outline">Submit</button>
</div>
</div>
</div>
<div class="col-md-6">
<notification-templates [templates]="settings.notificationTemplates"></notification-templates>
</div>
</fieldset>
</div>

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

@ -0,0 +1,25 @@

<ngb-accordion [closeOthers]="true" activeIds="0-header">
<ngb-panel *ngFor="let template of templates" id="{{template.notificationType}}" title="{{NotificationType[template.notificationType] | humanize}}">
<ng-template ngbPanelContent>
<div class="panel panel-default">
<div class="panel-body">
<div class="form-group">
<label for="password" class="control-label">Subject</label>
<div>
<input type="text" class="form-control form-control-custom" [(ngModel)]="template.subject" value="{{template.subject}}">
</div>
</div>
<div class="form-group">
<label for="password" class="control-label">Message</label>
<div>
<textarea type="text" class="form-control form-control-custom" [(ngModel)]="template.message" value="{{template.message}}"></textarea>
</div>
</div>
</div>
</div>
</ng-template>
</ngb-panel>
</ngb-accordion>

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

@ -6,10 +6,9 @@
<div class="form-group">
<label for="portNumber" class="control-label">Port</label>
<div>
<input type="text" [(ngModel)]="settings.port" class="form-control form-control-custom " id="portNumber" name="Port" placeholder="Port Number" value="{{settings.port}}">
<input type="text" [(ngModel)]="settings.port" class="form-control form-control-custom " id="portNumber" name="Port" placeholder="Port Number" value="{{settings.port}}" pTooltip="You will have to restart after changing the port.">
</div>
</div>
<small class="control-label">You will have to restart after changing the port.</small>
<!--<div class="form-group">
<label for="BaseUrl" class="control-label">Base Url @Html.ToolTip("This will make Ombi run with a base url, usually used in reverse proxy scenarios")</label>

@ -2,7 +2,7 @@
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { RouterModule, Routes } from '@angular/router';
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
import { NgbModule, NgbAccordionModule } from '@ng-bootstrap/ng-bootstrap';
import { AuthService } from '../auth/auth.service';
import { AuthGuard } from '../auth/auth.guard';
@ -17,10 +17,13 @@ import { SonarrComponent } from './sonarr/sonarr.component';
import { RadarrComponent } from './radarr/radarr.component';
import { LandingPageComponent } from './landingpage/landingpage.component';
import { CustomizationComponent } from './customization/customization.component';
import { EmailNotificationComponent } from './notifications/emailnotification.component';
import { NotificationTemplate } from './notifications/notificationtemplate.component';
import { SettingsMenuComponent } from './settingsmenu.component';
import { HumanizePipe } from '../pipes/HumanizePipe';
import { MenuModule, InputSwitchModule, InputTextModule } from 'primeng/primeng';
import { MenuModule, InputSwitchModule, InputTextModule, TooltipModule } from 'primeng/primeng';
const routes: Routes = [
{ path: 'Settings/Ombi', component: OmbiComponent, canActivate: [AuthGuard] },
@ -30,6 +33,7 @@ const routes: Routes = [
{ path: 'Settings/Radarr', component: RadarrComponent, canActivate: [AuthGuard] },
{ path: 'Settings/LandingPage', component: LandingPageComponent, canActivate: [AuthGuard] },
{ path: 'Settings/Customization', component: CustomizationComponent, canActivate: [AuthGuard] },
{ path: 'Settings/Email', component: EmailNotificationComponent, canActivate: [AuthGuard] },
];
@NgModule({
@ -41,7 +45,9 @@ const routes: Routes = [
InputSwitchModule,
InputTextModule,
AuthModule,
NgbModule
NgbModule,
TooltipModule,
NgbAccordionModule
],
declarations: [
SettingsMenuComponent,
@ -51,7 +57,10 @@ const routes: Routes = [
LandingPageComponent,
CustomizationComponent,
SonarrComponent,
RadarrComponent
RadarrComponent,
EmailNotificationComponent,
HumanizePipe,
NotificationTemplate
],
exports: [
RouterModule

@ -1,24 +1,46 @@
using System.Threading.Tasks;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using AutoMapper;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Ombi.Attributes;
using Ombi.Core.Settings;
using Ombi.Core.Settings.Models;
using Ombi.Core.Settings.Models.External;
using Ombi.Helpers;
using Ombi.Models.Notifications;
using Ombi.Settings.Settings.Models;
using Ombi.Settings.Settings.Models.External;
using Ombi.Settings.Settings.Models.Notifications;
using Ombi.Store.Entities;
using Ombi.Store.Repository;
namespace Ombi.Controllers
{
/// <summary>
/// The Settings Controller
/// </summary>
/// <seealso cref="Ombi.Controllers.BaseV1ApiController" />
[Admin]
public class SettingsController : BaseV1ApiController
{
public SettingsController(ISettingsResolver resolver)
/// <summary>
/// Initializes a new instance of the <see cref="SettingsController" /> class.
/// </summary>
/// <param name="resolver">The resolver.</param>
/// <param name="mapper">The mapper.</param>
public SettingsController(ISettingsResolver resolver, IMapper mapper, INotificationTemplatesRepository templateRepo)
{
SettingsResolver = resolver;
Mapper = mapper;
TemplateRepository = templateRepo;
}
private ISettingsResolver SettingsResolver { get; }
private IMapper Mapper { get; }
private INotificationTemplatesRepository TemplateRepository { get; }
/// <summary>
/// Gets the Ombi settings.
@ -169,16 +191,58 @@ namespace Ombi.Controllers
return await Save(settings);
}
private async Task<T> Get<T>()
/// <summary>
/// Saves the email notification settings.
/// </summary>
/// <param name="model">The model.</param>
/// <returns></returns>
[HttpPost("notifications/email")]
public async Task<bool> EmailNotificationSettings([FromBody] EmailNotificationsViewModel model)
{
var settings = SettingsResolver.Resolve<T>();
return await settings.GetSettingsAsync();
}
// Save the email settings
var settings = Mapper.Map<EmailNotificationSettings>(model);
var result = await Save(settings);
private async Task<bool> Save<T>(T settingsModel)
{
var settings = SettingsResolver.Resolve<T>();
return await settings.SaveSettingsAsync(settingsModel);
// Save the templates
await TemplateRepository.UpdateRange(model.NotificationTemplates);
return result;
}
/// <summary>
/// Gets the Email Notification Settings.
/// </summary>
/// <returns></returns>
[HttpGet("notifications/email")]
public async Task<EmailNotificationsViewModel> EmailNotificationSettings()
{
var emailSettings = await Get<EmailNotificationSettings>();
var model = Mapper.Map<EmailNotificationsViewModel>(emailSettings);
// Lookup to see if we have any templates saved
model.NotificationTemplates = await BuildTemplates(NotificationAgent.Email);
return model;
}
private async Task<List<NotificationTemplates>> BuildTemplates(NotificationAgent agent)
{
var templates = await TemplateRepository.GetAllTemplates(agent);
return templates.ToList();
}
private async Task<T> Get<T>()
{
var settings = SettingsResolver.Resolve<T>();
return await settings.GetSettingsAsync();
}
private async Task<bool> Save<T>(T settingsModel)
{
var settings = SettingsResolver.Resolve<T>();
return await settings.SaveSettingsAsync(settingsModel);
}
}
}

@ -247,4 +247,29 @@ button.list-group-item:focus {
.nav .open > a:focus {
background-color: $bg-colour;
border-color: $bg-colour-disabled;
}
.card-header {
background-color: $bg-colour;
color: #ebebeb;
padding: 10px 15px;
border-bottom: 1px solid #1f1f1f;
}
.card-header > a {
color: white;
}
.card-block {
background: $bg-colour-disabled;
}
.panel {
margin-bottom: 21px;
background-color: $bg-colour-disabled;
border: 1px solid transparent;
border-radius: 0;
-webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.05);
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.05);
}

@ -68,6 +68,15 @@ hr {
box-shadow: 0 0 0 !important;
}
.form-small {
width: 25%;
}
.form-half {
width: 50%;
}
h1 {
font-size: 3.5rem $i;
@ -236,14 +245,6 @@ label {
border-color: $success-colour $i;
}
#movieList .mix {
display: none;
}
#tvList .mix {
display: none;
}
$border-radius: 10px;
.scroll-top-wrapper {
@ -715,9 +716,22 @@ body {
background: $form-color $i;
}
// PrimeNg Overide
.card-header {
color: #ebebeb;
padding: 10px 15px;
border-bottom: 1px solid transparent;
background: $form-color;
}
.card-header > a{
color:white;
}
.ui-growl-item{
margin-top:35px $i;
// PrimeNg Overide
.ui-growl-item {
margin-top: 35px $i;
}
textarea {
resize: vertical;
}
Loading…
Cancel
Save