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
This commit is contained in:
Jamie.Rees 2017-06-14 16:30:06 +01:00
parent 5e6032ecba
commit f193471b6c
46 changed files with 888 additions and 457 deletions

View file

@ -16,6 +16,8 @@ using System.Security.Principal;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Ombi.Core.Engine.Interfaces; using Ombi.Core.Engine.Interfaces;
using Ombi.Core.Notifications;
using Ombi.Store;
namespace Ombi.Core.Engine namespace Ombi.Core.Engine
{ {
@ -136,10 +138,10 @@ namespace Ombi.Core.Engine
{ {
//Log.Fatal(e); //Log.Fatal(e);
//await FaultQueue.QueueItemAsync(model, movieInfo.Id.ToString(), RequestType.Movie, FaultType.RequestFault, e.Message); //await FaultQueue.QueueItemAsync(model, movieInfo.Id.ToString(), RequestType.Movie, FaultType.RequestFault, e.Message);
var notification = new NotificationModel var notification = new NotificationOptions
{ {
DateTime = DateTime.Now, DateTime = DateTime.Now,
User = Username, RequestedUser = Username,
RequestType = RequestType.Movie, RequestType = RequestType.Movie,
Title = model.Title, Title = model.Title,
NotificationType = NotificationType.ItemAddedToFaultQueue NotificationType = NotificationType.ItemAddedToFaultQueue
@ -208,10 +210,10 @@ namespace Ombi.Core.Engine
if (ShouldSendNotification(model.Type)) if (ShouldSendNotification(model.Type))
{ {
var notificationModel = new NotificationModel var notificationModel = new NotificationOptions
{ {
Title = model.Title, Title = model.Title,
User = Username, RequestedUser = Username,
DateTime = DateTime.Now, DateTime = DateTime.Now,
NotificationType = NotificationType.NewRequest, NotificationType = NotificationType.NewRequest,
RequestType = model.Type, RequestType = model.Type,

View file

@ -15,6 +15,8 @@ using System.Linq;
using System.Security.Principal; using System.Security.Principal;
using System.Threading.Tasks; using System.Threading.Tasks;
using Ombi.Core.Engine.Interfaces; using Ombi.Core.Engine.Interfaces;
using Ombi.Core.Notifications;
using Ombi.Store;
namespace Ombi.Core.Engine namespace Ombi.Core.Engine
{ {
@ -272,17 +274,20 @@ namespace Ombi.Core.Engine
{ {
if (ShouldSendNotification(model.Type)) if (ShouldSendNotification(model.Type))
{ {
var notificationModel = new NotificationModel var n = new NotificationOptions();
{
Title = model.Title,
User = Username,
DateTime = DateTime.Now,
NotificationType = NotificationType.NewRequest,
RequestType = model.Type,
ImgSrc = model.PosterPath
};
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(); //var limit = await RequestLimitRepo.GetAllAsync();

View file

@ -4,24 +4,6 @@ using System.Collections.Generic;
namespace Ombi.Core.Models.Requests 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 public enum IssueState
{ {
None = 99, None = 99,

View file

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

View file

@ -16,6 +16,7 @@ using Ombi.Core.Engine;
using Ombi.Core.Engine.Interfaces; using Ombi.Core.Engine.Interfaces;
using Ombi.Core.IdentityResolver; using Ombi.Core.IdentityResolver;
using Ombi.Core.Models.Requests; using Ombi.Core.Models.Requests;
using Ombi.Core.Notifications;
using Ombi.Core.Requests.Models; using Ombi.Core.Requests.Models;
using Ombi.Core.Rule; using Ombi.Core.Rule;
using Ombi.Core.Settings; using Ombi.Core.Settings;
@ -75,6 +76,7 @@ namespace Ombi.DependencyInjection
services.AddTransient<IUserRepository, UserRepository>(); services.AddTransient<IUserRepository, UserRepository>();
services.AddTransient<ISettingsResolver, SettingsResolver>(); services.AddTransient<ISettingsResolver, SettingsResolver>();
services.AddTransient<IPlexContentRepository, PlexContentRepository>(); services.AddTransient<IPlexContentRepository, PlexContentRepository>();
services.AddTransient<INotificationTemplatesRepository, NotificationTemplatesRepository>();
services.AddTransient(typeof(ISettingsService<>), typeof(SettingsServiceV2<>)); services.AddTransient(typeof(ISettingsService<>), typeof(SettingsServiceV2<>));
} }
public static void RegisterServices(this IServiceCollection services) public static void RegisterServices(this IServiceCollection services)

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -4,15 +4,15 @@ using Microsoft.Extensions.Logging;
using Ombi.Api.Discord; using Ombi.Api.Discord;
using Ombi.Core.Settings; using Ombi.Core.Settings;
using Ombi.Helpers; using Ombi.Helpers;
using Ombi.Notifications;
using Ombi.Notifications.Models; using Ombi.Notifications.Models;
using Ombi.Settings.Settings.Models.Notifications; using Ombi.Settings.Settings.Models.Notifications;
using Ombi.Store.Repository;
namespace Ombi.Notification.Agents namespace Ombi.Notifications.Agents
{ {
public class DiscordNotification : BaseNotification<DiscordNotificationSettings> 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; Api = api;
Logger = log; Logger = log;
@ -45,9 +45,9 @@ namespace Ombi.Notification.Agents
return true; 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 var notification = new NotificationMessage
{ {
@ -56,9 +56,9 @@ namespace Ombi.Notification.Agents
await Send(notification, settings); 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 var notification = new NotificationMessage
{ {
Message = message, Message = message,
@ -66,9 +66,9 @@ namespace Ombi.Notification.Agents
await Send(notification, settings); 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 var notification = new NotificationMessage
{ {
Message = message, Message = message,
@ -76,7 +76,7 @@ namespace Ombi.Notification.Agents
await Send(notification, settings); 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 message = $"Hello! Your request for {model.Title} has been declined, Sorry!";
var notification = new NotificationMessage var notification = new NotificationMessage
@ -86,7 +86,7 @@ namespace Ombi.Notification.Agents
await Send(notification, settings); 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 message = $"Hello! The request for {model.Title} has now been approved!";
var notification = new NotificationMessage var notification = new NotificationMessage
@ -96,7 +96,7 @@ namespace Ombi.Notification.Agents
await Send(notification, settings); 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 message = $"Hello! The request for {model.Title} is now available!";
var notification = new NotificationMessage 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 message = $"This is a test from Ombi, if you can see this then we have successfully pushed a notification!";
var notification = new NotificationMessage var notification = new NotificationMessage

View file

@ -2,17 +2,18 @@
using System.Threading.Tasks; using System.Threading.Tasks;
using MailKit.Net.Smtp; using MailKit.Net.Smtp;
using MimeKit; using MimeKit;
using Ombi.Core.Models.Requests;
using Ombi.Core.Settings; using Ombi.Core.Settings;
using Ombi.Helpers;
using Ombi.Notifications.Models; using Ombi.Notifications.Models;
using Ombi.Notifications.Templates; using Ombi.Notifications.Templates;
using Ombi.Settings.Settings.Models.Notifications; using Ombi.Settings.Settings.Models.Notifications;
using Ombi.Store.Repository;
namespace Ombi.Notifications.Email namespace Ombi.Notifications.Agents
{ {
public class EmailNotification : BaseNotification<EmailNotificationSettings> 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 (settings.Authentication)
{ {
if (string.IsNullOrEmpty(settings.EmailUsername) || string.IsNullOrEmpty(settings.EmailPassword)) if (string.IsNullOrEmpty(settings.Username) || string.IsNullOrEmpty(settings.Password))
{ {
return false; 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; return false;
} }
@ -39,69 +40,78 @@ namespace Ombi.Notifications.Email
return true; 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 email = new EmailBasicTemplate();
var html = email.LoadTemplate( var html = email.LoadTemplate(template.Subject, template.Message, model.ImgSrc);
$"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 var message = new NotificationMessage
{ {
Message = html, Message = html,
Subject = $"Ombi: New {model.RequestType.GetString()?.ToLower()} request for {model.Title}!", Subject = $"Ombi: New {model.RequestType} request for {model.Title}!",
To = settings.RecipientEmail, 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); 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 email = new EmailBasicTemplate();
var html = email.LoadTemplate( var html = email.LoadTemplate(
$"Ombi: New issue for {model.Title}!", $"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); model.ImgSrc);
var message = new NotificationMessage var message = new NotificationMessage
{ {
Message = html, Message = html,
Subject = $"Ombi: New issue for {model.Title}!", 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); 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 email = new EmailBasicTemplate();
var html = email.LoadTemplate( var html = email.LoadTemplate(
"Ombi: A request could not be added.", "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); model.ImgSrc);
var message = new NotificationMessage var message = new NotificationMessage
{ {
Message = html, Message = html,
Subject = $"Ombi: A request could not be added", 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); 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 email = new EmailBasicTemplate();
var html = email.LoadTemplate( var html = email.LoadTemplate(
@ -122,7 +132,7 @@ namespace Ombi.Notifications.Email
await Send(message, settings); 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 email = new EmailBasicTemplate();
var html = email.LoadTemplate( var html = email.LoadTemplate(
@ -142,7 +152,7 @@ namespace Ombi.Notifications.Email
await Send(message, settings); 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 email = new EmailBasicTemplate();
var html = email.LoadTemplate( var html = email.LoadTemplate(
@ -178,12 +188,12 @@ namespace Ombi.Notifications.Email
Body = body.ToMessageBody(), Body = body.ToMessageBody(),
Subject = model.Subject 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)); message.To.Add(new MailboxAddress(model.To, model.To));
using (var client = new SmtpClient()) 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 // Note: since we don't have an OAuth2 token, disable
// the XOAUTH2 authentication mechanism. // the XOAUTH2 authentication mechanism.
@ -191,7 +201,7 @@ namespace Ombi.Notifications.Email
if (settings.Authentication) 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); //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.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 email = new EmailBasicTemplate();
var html = email.LoadTemplate( var html = email.LoadTemplate(
@ -216,7 +226,7 @@ namespace Ombi.Notifications.Email
{ {
Message = html, Message = html,
Subject = $"Ombi: Test", Subject = $"Ombi: Test",
To = settings.RecipientEmail, To = settings.AdminEmail,
}; };
message.Other.Add("PlainTextBody", "This is just a test! Success!"); message.Other.Add("PlainTextBody", "This is just a test! Success!");

View file

@ -1,28 +1,32 @@
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using Ombi.Core.Settings; using Ombi.Core.Settings;
using Ombi.Core.Settings.Models; using Ombi.Helpers;
using Ombi.Notifications.Models; using Ombi.Notifications.Models;
using Ombi.Store;
using Ombi.Store.Repository;
namespace Ombi.Notifications namespace Ombi.Notifications
{ {
public abstract class BaseNotification<T> : INotification where T : Settings.Settings.Models.Settings, new() public abstract class BaseNotification<T> : INotification where T : Settings.Settings.Models.Settings, new()
{ {
protected BaseNotification(ISettingsService<T> settings) protected BaseNotification(ISettingsService<T> settings, INotificationTemplatesRepository templateRepo)
{ {
Settings = settings; Settings = settings;
TemplateRepository = templateRepo;
} }
protected ISettingsService<T> Settings { get; } protected ISettingsService<T> Settings { get; }
protected INotificationTemplatesRepository TemplateRepository { get; }
public abstract string NotificationName { get; } public abstract string NotificationName { get; }
public async Task NotifyAsync(NotificationModel model) public async Task NotifyAsync(NotificationOptions model)
{ {
var configuration = GetConfiguration(); var configuration = GetConfiguration();
await NotifyAsync(model, configuration); 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); if (settings == null) await NotifyAsync(model);
@ -79,13 +83,13 @@ namespace Ombi.Notifications
protected abstract bool ValidateConfiguration(T settings); protected abstract bool ValidateConfiguration(T settings);
protected abstract Task NewRequest(NotificationModel model, T settings); protected abstract Task NewRequest(NotificationOptions model, T settings);
protected abstract Task Issue(NotificationModel model, T settings); protected abstract Task Issue(NotificationOptions model, T settings);
protected abstract Task AddedToRequestQueue(NotificationModel model, T settings); protected abstract Task AddedToRequestQueue(NotificationOptions model, T settings);
protected abstract Task RequestDeclined(NotificationModel model, T settings); protected abstract Task RequestDeclined(NotificationOptions model, T settings);
protected abstract Task RequestApproved(NotificationModel model, T settings); protected abstract Task RequestApproved(NotificationOptions model, T settings);
protected abstract Task AvailableRequest(NotificationModel model, T settings); protected abstract Task AvailableRequest(NotificationOptions model, T settings);
protected abstract Task Send(NotificationMessage 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);
} }
} }

View file

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

View file

@ -1,14 +1,13 @@
using System.Collections.Concurrent; using System.Threading.Tasks;
using System.Threading.Tasks; using Ombi.Notifications;
using Ombi.Core.Settings.Models;
using Ombi.Notifications.Models; using Ombi.Notifications.Models;
namespace Ombi.Notifications namespace Ombi.Core.Notifications
{ {
public interface INotificationService public interface INotificationService
{ {
Task Publish(NotificationModel model); Task Publish(NotificationOptions model);
Task Publish(NotificationModel model, Settings.Settings.Models.Settings settings); Task Publish(NotificationOptions model, Ombi.Settings.Settings.Models.Settings settings);
Task PublishTest(NotificationModel model, Settings.Settings.Models.Settings settings, INotification type); Task PublishTest(NotificationOptions model, Ombi.Settings.Settings.Models.Settings settings, INotification type);
} }
} }

View file

@ -1,15 +1,17 @@
using System; using System;
using Ombi.Helpers;
using Ombi.Store;
using Ombi.Store.Entities; using Ombi.Store.Entities;
namespace Ombi.Notifications.Models namespace Ombi.Notifications.Models
{ {
public class NotificationModel public class NotificationOptions
{ {
public string Title { get; set; } public string Title { get; set; }
public string Body { 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 NotificationType NotificationType { get; set; }
public string User { get; set; } public string RequestedUser { get; set; }
public string UserEmail { get; set; } public string UserEmail { get; set; }
public RequestType RequestType { get; set; } public RequestType RequestType { get; set; }
public string ImgSrc { get; set; } public string ImgSrc { get; set; }

View file

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

View file

@ -4,6 +4,7 @@ using System.Linq;
using System.Reflection; using System.Reflection;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Ombi.Core.Notifications;
using Ombi.Helpers; using Ombi.Helpers;
using Ombi.Notifications.Models; using Ombi.Notifications.Models;
@ -16,13 +17,13 @@ namespace Ombi.Notifications
Log = log; Log = log;
NotificationAgents = new List<INotification>(); NotificationAgents = new List<INotification>();
var baseSearchType = typeof(BaseNotification<>).FullName; var baseSearchType = typeof(BaseNotification<>).Name;
var ass = typeof(NotificationService).GetTypeInfo().Assembly; var ass = typeof(NotificationService).GetTypeInfo().Assembly;
foreach (var ti in ass.DefinedTypes) foreach (var ti in ass.DefinedTypes)
{ {
if (ti?.BaseType?.FullName == baseSearchType) if (ti?.BaseType?.Name == baseSearchType)
{ {
var type = ti?.AsType(); var type = ti?.AsType();
var ctors = type.GetConstructors(); var ctors = type.GetConstructors();
@ -48,7 +49,7 @@ namespace Ombi.Notifications
/// </summary> /// </summary>
/// <param name="model">The model.</param> /// <param name="model">The model.</param>
/// <returns></returns> /// <returns></returns>
public async Task Publish(NotificationModel model) public async Task Publish(NotificationOptions model)
{ {
var notificationTasks = NotificationAgents.Select(notification => NotifyAsync(notification, model)); var notificationTasks = NotificationAgents.Select(notification => NotifyAsync(notification, model));
@ -61,7 +62,7 @@ namespace Ombi.Notifications
/// <param name="model">The model.</param> /// <param name="model">The model.</param>
/// <param name="settings">The settings.</param> /// <param name="settings">The settings.</param>
/// <returns></returns> /// <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)); 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 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 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); await type.NotifyAsync(model, settings);
} }

View file

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

View file

@ -1,14 +1,14 @@
namespace Ombi.Settings.Settings.Models.Notifications namespace Ombi.Settings.Settings.Models.Notifications
{ {
public sealed class EmailNotificationSettings : Settings public class EmailNotificationSettings : Settings
{ {
public bool Enabled { get; set; } public bool Enabled { get; set; }
public string EmailHost { get; set; } public string Host { get; set; }
public string EmailPassword { get; set; } public string Password { get; set; }
public int EmailPort { get; set; } public int Port { get; set; }
public string EmailSender { get; set; } public string Sender { get; set; }
public string EmailUsername { get; set; } public string Username { get; set; }
public bool Authentication { get; set; } public bool Authentication { get; set; }
public string RecipientEmail { get; set; } public string AdminEmail { get; set; }
} }
} }

View file

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

View file

@ -1,10 +1,14 @@
using System.IO; using System;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Ombi.Helpers;
using Ombi.Store.Entities; using Ombi.Store.Entities;
namespace Ombi.Store.Context namespace Ombi.Store.Context
{ {
public class OmbiContext : DbContext, IOmbiContext public sealed class OmbiContext : DbContext, IOmbiContext
{ {
private static bool _created; private static bool _created;
public OmbiContext() public OmbiContext()
@ -26,6 +30,9 @@ namespace Ombi.Store.Context
// Run Script // Run Script
Database.ExecuteSqlCommand(file, 0); Database.ExecuteSqlCommand(file, 0);
// Add the notifcation templates
AddAllTemplates();
} }
public DbSet<RequestBlobs> Requests { get; set; } public DbSet<RequestBlobs> Requests { get; set; }
@ -33,10 +40,93 @@ namespace Ombi.Store.Context
public DbSet<User> Users { get; set; } public DbSet<User> Users { get; set; }
public DbSet<PlexContent> PlexContent { get; set; } public DbSet<PlexContent> PlexContent { get; set; }
public DbSet<RadarrCache> RadarrCache { get; set; } public DbSet<RadarrCache> RadarrCache { get; set; }
public DbSet<NotificationTemplates> NotificationTemplates { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{ {
optionsBuilder.UseSqlite("Data Source=Ombi.db"); 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();
}
} }
} }

View file

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

View file

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

View file

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

View file

@ -63,4 +63,15 @@ CREATE TABLE IF NOT EXISTS RequestHistory
RequestedDate varchar(50) NOT NULL, RequestedDate varchar(50) NOT NULL,
RequestId INTEGER 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
); );

View file

@ -61,8 +61,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ombi.Api.Radarr", "Ombi.Api
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ombi.Api.Discord", "Ombi.Api.Discord\Ombi.Api.Discord.csproj", "{5AF2B6D2-5CC6-49FE-928A-BA27AF52B194}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ombi.Api.Discord", "Ombi.Api.Discord\Ombi.Api.Discord.csproj", "{5AF2B6D2-5CC6-49FE-928A-BA27AF52B194}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ombi.Notification.Agents", "Ombi.Notification.Agents\Ombi.Notification.Agents.csproj", "{DC9E3E44-802A-4D9B-A70F-D198CFE4C73D}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU 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}.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.ActiveCfg = Release|Any CPU
{5AF2B6D2-5CC6-49FE-928A-BA27AF52B194}.Release|Any CPU.Build.0 = 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 EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
@ -172,6 +166,5 @@ Global
{FC6A8F7C-9722-4AE4-960D-277ACB0E81CB} = {6F42AB98-9196-44C4-B888-D5E409F415A1} {FC6A8F7C-9722-4AE4-960D-277ACB0E81CB} = {6F42AB98-9196-44C4-B888-D5E409F415A1}
{94D04C1F-E35A-499C-B0A0-9FADEBDF8336} = {9293CA11-360A-4C20-A674-B9E794431BF5} {94D04C1F-E35A-499C-B0A0-9FADEBDF8336} = {9293CA11-360A-4C20-A674-B9E794431BF5}
{5AF2B6D2-5CC6-49FE-928A-BA27AF52B194} = {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 EndGlobalSection
EndGlobal EndGlobal

View file

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

View file

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

View file

@ -222,6 +222,7 @@
</div> </div>
<div class="col-md-3"> <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" 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>
</div> </div>
<hr /> <hr />

View file

@ -5,7 +5,6 @@ import 'rxjs/add/operator/distinctUntilChanged';
import 'rxjs/add/operator/map'; import 'rxjs/add/operator/map';
import "rxjs/add/operator/takeUntil"; import "rxjs/add/operator/takeUntil";
import 'rxjs/add/operator/debounceTime'; import 'rxjs/add/operator/debounceTime';
import 'rxjs/add/operator/distinctUntilChanged'; import 'rxjs/add/operator/distinctUntilChanged';
import 'rxjs/add/operator/map'; 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) this.requestService.getTvRequests(this.amountToLoad, this.currentlyLoaded + 1)
.takeUntil(this.subscriptions) .takeUntil(this.subscriptions)
.subscribe(x => { .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); this.searchChanged.next(text.target.value);
} }
removeRequest(request: ITvRequestModel) { public removeRequest(request: ITvRequestModel) {
this.requestService.removeTvRequest(request); this.requestService.removeTvRequest(request);
this.removeRequestFromUi(request); this.removeRequestFromUi(request);
} }
changeAvailability(request: ITvRequestModel, available: boolean) { public changeAvailability(request: ITvRequestModel, available: boolean) {
request.available = available; request.available = available;
this.updateRequest(request); this.updateRequest(request);
} }
approve(request: ITvRequestModel) { public approve(request: ITvRequestModel) {
request.approved = true; request.approved = true;
request.denied = false; request.denied = false;
this.updateRequest(request); this.updateRequest(request);
} }
deny(request: ITvRequestModel) { public deny(request: ITvRequestModel) {
request.approved = false; request.approved = false;
request.denied = true; request.denied = true;
this.updateRequest(request); this.updateRequest(request);
@ -100,6 +99,14 @@ export class TvRequestsComponent implements OnInit, OnDestroy {
public approveSeasonRequest(request: IChildTvRequest) { public approveSeasonRequest(request: IChildTvRequest) {
request.approved = true; 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) this.requestService.updateTvRequest(this.selectedSeason)
.subscribe(); .subscribe();
} }

View file

@ -13,6 +13,7 @@ import {
ICustomizationSettings, ICustomizationSettings,
IRadarrSettings IRadarrSettings
} from '../interfaces/ISettings'; } from '../interfaces/ISettings';
import { IEmailNotificationSettings } from '../interfaces/INotifcationSettings';
@Injectable() @Injectable()
export class SettingsService extends ServiceAuthHelpers { 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) 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)
}
} }

View file

@ -12,10 +12,9 @@
<div class="form-group"> <div class="form-group">
<label for="logo" class="control-label">Custom Logo</label> <label for="logo" class="control-label">Custom Logo</label>
<div> <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>
</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"> <div class="form-group">

View file

@ -16,10 +16,9 @@
<div class="form-group"> <div class="form-group">
<div class="checkbox"> <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> <label for="BeforeLogin">Show before the login</label>
</div> </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> </div>
<p class="form-group">Notice Message</p> <p class="form-group">Notice Message</p>

View file

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

View file

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

View file

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

View file

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

View file

@ -6,10 +6,9 @@
<div class="form-group"> <div class="form-group">
<label for="portNumber" class="control-label">Port</label> <label for="portNumber" class="control-label">Port</label>
<div> <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>
</div> </div>
<small class="control-label">You will have to restart after changing the port.</small>
<!--<div class="form-group"> <!--<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> <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>

View file

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

View file

@ -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.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Ombi.Attributes; using Ombi.Attributes;
using Ombi.Core.Settings; using Ombi.Core.Settings;
using Ombi.Core.Settings.Models; using Ombi.Core.Settings.Models;
using Ombi.Core.Settings.Models.External; using Ombi.Core.Settings.Models.External;
using Ombi.Helpers;
using Ombi.Models.Notifications;
using Ombi.Settings.Settings.Models; using Ombi.Settings.Settings.Models;
using Ombi.Settings.Settings.Models.External; using Ombi.Settings.Settings.Models.External;
using Ombi.Settings.Settings.Models.Notifications;
using Ombi.Store.Entities;
using Ombi.Store.Repository;
namespace Ombi.Controllers namespace Ombi.Controllers
{ {
/// <summary>
/// The Settings Controller
/// </summary>
/// <seealso cref="Ombi.Controllers.BaseV1ApiController" />
[Admin] [Admin]
public class SettingsController : BaseV1ApiController 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; SettingsResolver = resolver;
Mapper = mapper;
TemplateRepository = templateRepo;
} }
private ISettingsResolver SettingsResolver { get; } private ISettingsResolver SettingsResolver { get; }
private IMapper Mapper { get; }
private INotificationTemplatesRepository TemplateRepository { get; }
/// <summary> /// <summary>
/// Gets the Ombi settings. /// Gets the Ombi settings.
@ -169,16 +191,58 @@ namespace Ombi.Controllers
return await Save(settings); 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>(); // Save the email settings
return await settings.GetSettingsAsync(); var settings = Mapper.Map<EmailNotificationSettings>(model);
} var result = await Save(settings);
private async Task<bool> Save<T>(T settingsModel) // Save the templates
{ await TemplateRepository.UpdateRange(model.NotificationTemplates);
var settings = SettingsResolver.Resolve<T>();
return await settings.SaveSettingsAsync(settingsModel); 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);
} }
} }
}

View file

@ -247,4 +247,29 @@ button.list-group-item:focus {
.nav .open > a:focus { .nav .open > a:focus {
background-color: $bg-colour; background-color: $bg-colour;
border-color: $bg-colour-disabled; 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);
} }

View file

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